File: Emit\EditAndContinue\EditAndContinueTestBase.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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.UnitTests;
using Microsoft.CodeAnalysis.EditAndContinue.UnitTests;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.Metadata.Tools;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests
{
    using static EditAndContinueTestUtilities;
 
    public abstract class EditAndContinueTestBase : EmitMetadataTestBase
    {
        // PDB reader can only be accessed from a single thread, so avoid concurrent compilation:
        internal static readonly CSharpCompilationOptions ComSafeDebugDll = TestOptions.DebugDll.WithConcurrentBuild(false);
 
        internal static readonly Func<MethodDefinitionHandle, EditAndContinueMethodDebugInformation> EmptyLocalsProvider = handle => default(EditAndContinueMethodDebugInformation);
 
        internal static string Visualize(ModuleMetadata baseline, params PinnedMetadata[] deltas)
        {
            var result = new StringWriter();
            new MetadataVisualizer(new[] { baseline.MetadataReader }.Concat(deltas.Select(d => d.Reader)).ToArray(), result).VisualizeAllGenerations();
            return result.ToString();
        }
 
        public static EmitBaseline CreateInitialBaseline(Compilation compilation, ModuleMetadata module, Func<MethodDefinitionHandle, EditAndContinueMethodDebugInformation> debugInformationProvider)
            => EditAndContinueTestUtilities.CreateInitialBaseline(compilation, module, debugInformationProvider);
 
        internal static SourceWithMarkedNodes MarkedSource(string markedSource, string fileName = "", CSharpParseOptions options = null, bool removeTags = false)
            => new SourceWithMarkedNodes(
                WithWindowsLineBreaks(markedSource),
                parser: s => Parse(s, fileName, options ?? TestOptions.Regular.WithNoRefSafetyRulesAttribute()),
                getSyntaxKind: s => (int)(SyntaxKind)typeof(SyntaxKind).GetField(s).GetValue(null),
                removeTags);
 
        internal static Func<SyntaxNode, SyntaxNode> GetSyntaxMapFromMarkers(SourceWithMarkedNodes source0, SourceWithMarkedNodes source1)
            => SourceWithMarkedNodes.GetSyntaxMap(source0, source1);
 
        internal static ImmutableArray<SyntaxNode> GetAllLocals(MethodSymbol method)
        {
            var sourceMethod = method as SourceMemberMethodSymbol;
            if (sourceMethod == null)
            {
                return ImmutableArray<SyntaxNode>.Empty;
            }
 
            return LocalVariableDeclaratorsCollector.GetDeclarators(sourceMethod);
        }
 
        internal static Func<SyntaxNode, SyntaxNode> GetSyntaxMapByKind(MethodSymbol method0, params SyntaxKind[] kinds)
        {
            return newNode =>
            {
                foreach (SyntaxKind kind in kinds)
                {
                    if (newNode.IsKind(kind))
                    {
                        return method0.DeclaringSyntaxReferences.Single().SyntaxTree.GetRoot().DescendantNodes().Single(n => n.IsKind(kind));
                    }
                }
 
                return null;
            };
        }
 
        internal static Func<SyntaxNode, SyntaxNode> GetEquivalentNodesMap(MethodSymbol method1, MethodSymbol method0)
        {
            var tree1 = method1.Locations[0].SourceTree;
            var tree0 = method0.Locations[0].SourceTree;
            Assert.NotEqual(tree1, tree0);
 
            var locals0 = GetAllLocals(method0);
            return s =>
            {
                var s1 = s;
                Assert.Equal(s1.SyntaxTree, tree1);
                foreach (var s0 in locals0)
                {
                    if (!SyntaxFactory.AreEquivalent(s0, s1))
                    {
                        continue;
                    }
                    // Make sure the containing statements are the same.
                    var p0 = GetNearestStatement(s0);
                    var p1 = GetNearestStatement(s1);
                    if (SyntaxFactory.AreEquivalent(p0, p1))
                    {
                        return s0;
                    }
                }
                return null;
            };
        }
 
        internal static string GetLocalName(SyntaxNode node)
        {
            switch (node.Kind())
            {
                case SyntaxKind.VariableDeclarator:
                    return ((VariableDeclaratorSyntax)node).Identifier.ToString();
                default:
                    throw new NotImplementedException();
            }
        }
 
        internal static StatementSyntax GetNearestStatement(SyntaxNode node)
        {
            while (node != null)
            {
                var statement = node as StatementSyntax;
                if (statement != null)
                {
                    return statement;
                }
                node = node.Parent;
            }
            return null;
        }
 
#nullable enable
        internal static SemanticEditDescription Edit(
            SemanticEditKind kind,
            Func<Compilation, ISymbol> symbolProvider,
            Func<Compilation, ISymbol>? newSymbolProvider = null,
            bool preserveLocalVariables = false,
            Func<SyntaxNode, RuntimeRudeEdit?>? rudeEdits = null)
            => new(kind, symbolProvider, newSymbolProvider, rudeEdits, preserveLocalVariables);
#nullable disable
        internal static EditAndContinueLogEntry Row(int rowNumber, TableIndex table, EditAndContinueOperation operation)
        {
            return new EditAndContinueLogEntry(MetadataTokens.Handle(table, rowNumber), operation);
        }
 
        internal static EntityHandle Handle(int rowNumber, TableIndex table)
        {
            return MetadataTokens.Handle(table, rowNumber);
        }
 
        /// <summary>
        /// Checks that the EncLog contains specified rows.
        /// Any default values in the expected <paramref name="rows"/> are ignored to facilitate conditional code.
        /// </summary>
        internal static void CheckEncLog(MetadataReader reader, params EditAndContinueLogEntry[] rows)
        {
            AssertEx.Equal(
                rows.Where(r => r.Handle != default),
                reader.GetEditAndContinueLogEntries(), itemInspector: EncLogRowToString);
        }
 
        /// <summary>
        /// Checks that the EncLog contains specified definition rows. References are ignored as they are usually not interesting to validate. They are emitted as needed.
        /// Any default values in the expected <paramref name="rows"/> are ignored to facilitate conditional code.
        /// </summary>
        internal static void CheckEncLogDefinitions(MetadataReader reader, params EditAndContinueLogEntry[] rows)
        {
            AssertEx.Equal(
                rows.Where(r => r.Handle != default),
                reader.GetEditAndContinueLogEntries().Where(e => IsDefinition(e.Handle.Kind)), itemInspector: EncLogRowToString);
        }
 
        /// <summary>
        /// Checks that the EncMap contains specified handles.
        /// Any default values in the expected <paramref name="handles"/> are ignored to facilitate conditional code.
        /// </summary>
        internal static void CheckEncMap(MetadataReader reader, params EntityHandle[] handles)
        {
            AssertEx.Equal(
                handles.Where(h => h != default),
                reader.GetEditAndContinueMapEntries(), itemInspector: EncMapRowToString);
        }
 
        /// <summary>
        /// Checks that the EncMap contains specified definition handles. References are ignored as they are usually not interesting to validate. They are emitted as needed.
        /// Any default values in the expected <paramref name="handles"/> are ignored to facilitate conditional code.
        /// </summary>
        internal static void CheckEncMapDefinitions(MetadataReader reader, params EntityHandle[] handles)
        {
            AssertEx.Equal(
                handles.Where(h => h != default),
                reader.GetEditAndContinueMapEntries().Where(e => IsDefinition(e.Kind)), itemInspector: EncMapRowToString);
        }
 
        internal static void CheckAttributes(MetadataReader reader, params CustomAttributeRow[] rows)
        {
            AssertEx.Equal(rows, reader.GetCustomAttributeRows(), itemInspector: AttributeRowToString);
        }
 
        internal static void CheckNames(MetadataReader reader, IEnumerable<StringHandle> handles, params string[] expectedNames)
            => EditAndContinueTestUtilities.CheckNames(reader, handles, expectedNames);
 
        internal static void CheckNames(IEnumerable<MetadataReader> readers, IEnumerable<StringHandle> handles, params string[] expectedNames)
            => EditAndContinueTestUtilities.CheckNames(readers, handles, expectedNames);
 
        internal static void CheckNames(IReadOnlyList<MetadataReader> readers, IEnumerable<(StringHandle Namespace, StringHandle Name)> handles, params string[] expectedNames)
            => EditAndContinueTestUtilities.CheckNames(readers, handles, expectedNames);
 
        public static void CheckNames(IReadOnlyList<MetadataReader> readers, ImmutableArray<TypeDefinitionHandle> typeHandles, params string[] expectedNames)
            => EditAndContinueTestUtilities.CheckNames(readers, typeHandles, expectedNames);
 
        public static void CheckNames(IReadOnlyList<MetadataReader> readers, ImmutableArray<MethodDefinitionHandle> methodHandles, params string[] expectedNames)
            => EditAndContinueTestUtilities.CheckNames(readers, methodHandles, expectedNames);
 
        public static void CheckBlobValue(IList<MetadataReader> readers, BlobHandle valueHandle, byte[] expectedValue)
        {
            var aggregator = GetAggregator(readers);
 
            var genHandle = (BlobHandle)aggregator.GetGenerationHandle(valueHandle, out int blobGeneration);
            var attributeData = readers[blobGeneration].GetBlobBytes(genHandle);
            AssertEx.Equal(expectedValue, attributeData);
        }
 
        public static void CheckStringValue(IList<MetadataReader> readers, StringHandle valueHandle, string expectedValue)
        {
            var aggregator = GetAggregator(readers);
 
            var genHandle = (StringHandle)aggregator.GetGenerationHandle(valueHandle, out int blobGeneration);
            var attributeData = readers[blobGeneration].GetString(genHandle);
            AssertEx.Equal(expectedValue, attributeData);
        }
 
        public static MetadataAggregator GetAggregator(IList<MetadataReader> readers)
            => new MetadataAggregator(readers[0], readers.Skip(1).ToArray());
 
        internal static void SaveImages(string outputDirectory, CompilationVerifier baseline, params CompilationDifference[] diffs)
        {
            bool IsPortablePdb(ImmutableArray<byte> image) => image[0] == 'B' && image[1] == 'S' && image[2] == 'J' && image[3] == 'B';
 
            string baseName = baseline.Compilation.AssemblyName;
            string extSuffix = IsPortablePdb(baseline.EmittedAssemblyPdb) ? "x" : "";
 
            Directory.CreateDirectory(outputDirectory);
 
            File.WriteAllBytes(Path.Combine(outputDirectory, baseName + ".dll" + extSuffix), baseline.EmittedAssemblyData.ToArray());
            File.WriteAllBytes(Path.Combine(outputDirectory, baseName + ".pdb" + extSuffix), baseline.EmittedAssemblyPdb.ToArray());
 
            for (int i = 0; i < diffs.Length; i++)
            {
                File.WriteAllBytes(Path.Combine(outputDirectory, $"{baseName}.{i + 1}.metadata{extSuffix}"), diffs[i].MetadataDelta.ToArray());
                File.WriteAllBytes(Path.Combine(outputDirectory, $"{baseName}.{i + 1}.pdb{extSuffix}"), diffs[i].PdbDelta.ToArray());
            }
        }
    }
 
    public static class EditAndContinueTestExtensions
    {
        internal static CSharpCompilation WithSource(this CSharpCompilation compilation, CSharpTestSource newSource)
        {
            var previousParseOptions = (CSharpParseOptions)compilation.SyntaxTrees.FirstOrDefault()?.Options;
            return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newSource.GetSyntaxTrees(previousParseOptions));
        }
 
        internal static CSharpCompilation WithSource(this CSharpCompilation compilation, SyntaxTree newTree)
        {
            return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newTree);
        }
 
        internal static CSharpCompilation WithSource(this CSharpCompilation compilation, SyntaxTree[] newTrees)
        {
            return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(newTrees);
        }
    }
}