File: PDB\TypeDefinitionDocumentTests.cs
Web Access
Project: src\src\Compilers\CSharp\Test\Emit2\Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests.csproj (Microsoft.CodeAnalysis.CSharp.Emit2.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Text;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Debugging;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.UnitTests.PDB
{
    public class TypeDefinitionDocumentTests : CSharpTestBase
    {
        [Fact]
        public void ClassWithMethod()
        {
            string source = @"
class M
{
    public static void A()
    {
        System.Console.WriteLine();
    }
}
";
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void NestedClassWithMethod()
        {
            string source = @"
class C
{
    class N
    {
        public static void A()
        {
            System.Console.WriteLine();
        }
    }
}
";
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void MultiNestedClassWithMethod()
        {
            string source = @"
class C
{
    class N
    {
        class N2
        {
            public static void A()
            {
                System.Console.WriteLine();
            }
        }
    }
}
";
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void PartialNestedClassWithMethod()
        {
            string source1 = @"
partial class C
{
    partial class N
    {
        public static void A()
        {
            System.Console.WriteLine();
        }
    }
}
";
            string source2 = @"
partial class C
{
    partial class N
    {
    }
}
";
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("C", "2.cs"));
        }
 
        [Fact]
        public void EmptyClass()
        {
            string source = @"
class O
{
}
";
            TestTypeDefinitionDocuments(new[] { source },
                ("O", "1.cs"));
        }
 
        [Fact]
        public void EmptyNestedClass()
        {
            string source = @"
class O
{
    class N
    {
    }
}
";
            TestTypeDefinitionDocuments(new[] { source },
                ("O", "1.cs"));
        }
 
        [Fact]
        public void EmptyMultiNestedClass()
        {
            string source = @"
class O
{
    class N
    {
        class N2
        {
        }
    }
}
";
            TestTypeDefinitionDocuments(new[] { source },
                ("O", "1.cs"));
        }
 
        [Fact]
        public void MultipleClassesAndFiles()
        {
            string source1 = @"
class M
{
    public static void A()
    {
        System.Console.WriteLine();
    }
}
 
class N
{
}
 
class O
{
}
";
 
            string source2 = @"
class C
{
}
 
class D
{
}
";
 
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("N", "1.cs"),
                ("O", "1.cs"),
                ("C", "2.cs"),
                ("D", "2.cs"));
        }
 
        [Fact]
        public void PartialClasses()
        {
            string source1 = @"
partial class C
{
}
";
            string source2 = @"
partial class C
{
}
";
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("C", "1.cs, 2.cs"));
        }
 
        [Fact]
        public void PartialClasses2()
        {
            string source1 = @"
partial class C
{
}
";
            string source2 = @"
partial class C
{
    int x = 1;
    void M() { }
}
";
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("C", "1.cs"));
        }
 
        [Fact]
        public void PartialClasses3()
        {
            string source1 = @"
partial class C
{
}
";
            string source2 = @"
partial class C
{
    int x;
    void M() { }
}
";
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("C", "1.cs"));
        }
 
        [Fact]
        public void Property()
        {
            string source = @"
class C
{
    public int X { get; set; }
}
";
 
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void Fields()
        {
            string source = @"
class C
{
    int x;
    const int z = 2;
    int y;
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("C", "1.cs"));
        }
 
        [Fact]
        public void Fields_WithInitializer()
        {
            string source = @"
class C
{
    int x;
    const int z = 2;
    int y = 1;
}
";
 
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void AbstractMethod()
        {
            string source = @"
abstract class C
{
    public abstract void M();
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("C", "1.cs"));
        }
 
        [Fact]
        public void ExternMethod()
        {
            string source = @"
class C
{
    public extern void M();
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("C", "1.cs"));
        }
 
        [Fact]
        public void Interfaces()
        {
            string source1 = @"
interface I1
{
}
 
partial interface I2
{
    public void F();
}
";
            string source2 = @"
partial interface I2
{
}
";
            TestTypeDefinitionDocuments(new[] { source1, source2 },
                ("I1", "1.cs"),
                ("I2", "1.cs, 2.cs"));
        }
 
        [Fact]
        public void Record()
        {
            string source = @"
record R(int X);
";
 
            // The compiler synthesized methods have document info so we don't expect a type document
            TestTypeDefinitionDocuments(new[] { source, IsExternalInitTypeDefinition },
                ("IsExternalInit", "2.cs"));
        }
 
        [Fact]
        public void Record_SynthesizedMember()
        {
            string source = @"
record R(int X)
{
    protected virtual bool PrintMembers(System.Text.StringBuilder builder)
    {
        return true;
    }
}
";
 
            TestTypeDefinitionDocuments(new[] { source, IsExternalInitTypeDefinition },
                ("IsExternalInit", "2.cs"));
        }
 
        [Fact]
        public void Enum()
        {
            string source = @"
enum E
{
}
 
enum E2
{
    A,
    B
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("E", "1.cs"),
                ("E2", "1.cs"));
        }
 
        [Fact]
        public void Delegate()
        {
            string source = @"
delegate void D(int a);
 
class C
{
    void M()
    {
        var x = (int a, ref int b) => a;
    }
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("D", "1.cs"));
        }
 
        [Fact]
        public void AnonymousTypes()
        {
            string source = @"
class C
{
    void M()
    {
        var x = new { Goo = 1, Bar = ""Hi"" };
    }
}
";
 
            TestTypeDefinitionDocuments(new[] { source });
        }
 
        [Fact]
        public void LineDirectives()
        {
            string source = @"
class C
{
#line 1 ""C.cs""
    void M()
    {
    }
#line default
}
 
class D
{
#line 1 ""D.cs""
    private int _x = 1;
    private int X { get; set; } = 1;
}
 
#line 1 ""E.cs""
class E
{
    void M()
    {
    }
}
 
#line 1 ""F.cs""
class F
{
}
";
 
            TestTypeDefinitionDocuments(new[] { source },
                ("C", "1.cs"),
                ("D", "1.cs"),
                ("E", "1.cs"),
                ("F", "1.cs"));
        }
 
        private static void TestTypeDefinitionDocuments(string[] sources, params (string typeName, string documentName)[] expected)
        {
            var trees = sources.Select((s, i) => SyntaxFactory.ParseSyntaxTree(s, path: $"{i + 1}.cs", encoding: Encoding.UTF8)).ToArray();
            var compilation = CreateCompilation(trees, options: TestOptions.DebugDll);
 
            var pdbStream = new MemoryStream();
            var pe = compilation.EmitToArray(EmitOptions.Default.WithDebugInformationFormat(DebugInformationFormat.PortablePdb), pdbStream: pdbStream);
            pdbStream.Position = 0;
 
            var metadata = ModuleMetadata.CreateFromImage(pe);
            var metadataReader = metadata.GetMetadataReader();
 
            using var provider = MetadataReaderProvider.FromPortablePdbStream(pdbStream);
            var pdbReader = provider.GetMetadataReader();
 
            var actual = from handle in pdbReader.CustomDebugInformation
                         let entry = pdbReader.GetCustomDebugInformation(handle)
                         where pdbReader.GetGuid(entry.Kind).Equals(PortableCustomDebugInfoKinds.TypeDefinitionDocuments)
                         select (typeName: GetTypeName(entry.Parent), documentName: GetDocumentNames(entry.Value));
 
            AssertEx.Equal(expected, actual, itemSeparator: ",\n", itemInspector: i => $"(\"{i.typeName}\", \"{i.documentName}\")");
 
            string GetTypeName(EntityHandle handle)
            {
                var typeHandle = (TypeDefinitionHandle)handle;
                var type = metadataReader.GetTypeDefinition(typeHandle);
                return metadataReader.GetString(type.Name);
            }
 
            string GetDocumentNames(BlobHandle value)
            {
                var result = new List<string>();
 
                var reader = pdbReader.GetBlobReader(value);
                while (reader.RemainingBytes > 0)
                {
                    var documentRow = reader.ReadCompressedInteger();
                    if (documentRow > 0)
                    {
                        var doc = pdbReader.GetDocument(MetadataTokens.DocumentHandle(documentRow));
                        result.Add(pdbReader.GetString(doc.Name));
                    }
                }
 
                return string.Join(", ", result);
            }
        }
    }
}