|
// 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.Linq;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Runtime.CompilerServices;
using System.Xml.Linq;
using Microsoft.CodeAnalysis.CodeGen;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Symbols;
using Microsoft.DiaSymReader.Tools;
using Microsoft.Metadata.Tools;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
public sealed class CompilationDifference
{
public readonly ImmutableArray<byte> MetadataDelta;
public readonly ImmutableArray<byte> ILDelta;
public readonly ImmutableArray<byte> PdbDelta;
internal readonly CompilationTestData TestData;
public readonly EmitDifferenceResult EmitResult;
internal CompilationDifference(
ImmutableArray<byte> metadata,
ImmutableArray<byte> il,
ImmutableArray<byte> pdb,
CompilationTestData testData,
EmitDifferenceResult result)
{
MetadataDelta = metadata;
ILDelta = il;
PdbDelta = pdb;
TestData = testData;
EmitResult = result;
}
public EmitBaseline NextGeneration
{
get
{
return EmitResult.Baseline;
}
}
internal PinnedMetadata GetMetadata()
{
return new PinnedMetadata(MetadataDelta);
}
public void VerifyIL(
string expectedIL,
[CallerLineNumber] int callerLine = 0,
[CallerFilePath] string callerPath = null)
{
string actualIL = ILDelta.GetMethodIL();
AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: false, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine);
}
public void VerifyLocalSignature(
string qualifiedMethodName,
string expectedSignature,
[CallerLineNumber] int callerLine = 0,
[CallerFilePath] string callerPath = null)
{
var ilBuilder = TestData.GetMethodData(qualifiedMethodName).ILBuilder;
string actualSignature = ILBuilderVisualizer.LocalSignatureToString(ilBuilder, ToLocalInfo);
AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedSignature, actualSignature, escapeQuotes: true, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine);
}
internal void VerifyIL(
string qualifiedMethodName,
string expectedIL,
Func<Cci.ILocalDefinition, ILVisualizer.LocalInfo> mapLocal = null,
MethodDefinitionHandle methodToken = default,
[CallerFilePath] string callerPath = null,
[CallerLineNumber] int callerLine = 0)
{
var ilBuilder = TestData.GetMethodData(qualifiedMethodName).ILBuilder;
Dictionary<int, string> sequencePointMarkers = null;
if (!methodToken.IsNil)
{
string actualPdb = PdbToXmlConverter.DeltaPdbToXml(new ImmutableMemoryStream(PdbDelta), new[] { MetadataTokens.GetToken(methodToken) });
sequencePointMarkers = ILValidation.GetSequencePointMarkers(XElement.Parse(actualPdb));
Assert.True(sequencePointMarkers.Count > 0, $"No sequence points found in:{Environment.NewLine}{actualPdb}");
}
string actualIL = ILBuilderVisualizer.ILBuilderToString(ilBuilder, mapLocal ?? ToLocalInfo, sequencePointMarkers);
AssertEx.AssertEqualToleratingWhitespaceDifferences(expectedIL, actualIL, escapeQuotes: false, expectedValueSourcePath: callerPath, expectedValueSourceLine: callerLine);
}
internal string GetMethodIL(string qualifiedMethodName)
{
return ILBuilderVisualizer.ILBuilderToString(this.TestData.GetMethodData(qualifiedMethodName).ILBuilder, ToLocalInfo);
}
private static ILVisualizer.LocalInfo ToLocalInfo(Cci.ILocalDefinition local)
{
var signature = local.Signature;
if (signature == null)
{
return new ILVisualizer.LocalInfo(local.Name, local.Type, local.IsPinned, local.IsReference);
}
else
{
// Decode simple types only.
var typeName = (signature.Length == 1) ? GetTypeName((SignatureTypeCode)signature[0]) : null;
return new ILVisualizer.LocalInfo(null, typeName ?? "[unchanged]", false, false);
}
}
private static string GetTypeName(SignatureTypeCode typeCode)
{
switch (typeCode)
{
case SignatureTypeCode.Boolean: return "[bool]";
case SignatureTypeCode.Int32: return "[int]";
case SignatureTypeCode.String: return "[string]";
case SignatureTypeCode.Object: return "[object]";
default: return null;
}
}
public void VerifySynthesizedMembers(params string[] expectedSynthesizedTypesAndMemberCounts)
=> VerifySynthesizedMembers(EmitResult.Baseline.SynthesizedMembers, displayTypeKind: false, expectedSynthesizedTypesAndMemberCounts);
internal static void VerifySynthesizedMembers(IReadOnlyDictionary<ISymbolInternal, ImmutableArray<ISymbolInternal>> actualMembers, bool displayTypeKind, params string[] expected)
{
// Synthesized namespaces are not interesting. Explode them and display types contained in them as separate fully qualified names in the list.
var actual = new List<string>();
foreach (var (container, members) in actualMembers)
{
if (container is INamespaceSymbolInternal ns)
{
actual.AddRange(members
.Where(m => m is not INamespaceSymbolInternal)
.Select(m => m.GetISymbol().ToDisplayString(SymbolDisplayFormat.TestFormat)));
}
}
foreach (var (container, members) in actualMembers)
{
if (container is not INamespaceSymbolInternal)
{
actual.Add(
$"{(displayTypeKind && container is INamedTypeSymbolInternal type ? (type.TypeKind == TypeKind.Struct ? "struct " : "class ") : "")}{container}: " +
$"{{{string.Join(", ", members.Select(v => v.Name))}}}");
}
}
AssertEx.SetEqual(expected, actual, itemSeparator: ",\r\n", itemInspector: s => $"\"{s}\"");
}
public void VerifySynthesizedFields(string typeName, params string[] expectedSynthesizedTypesAndMemberCounts)
{
var actual = EmitResult.Baseline.SynthesizedMembers.Single(e => e.Key.ToString() == typeName).Value.Where(s => s.Kind == SymbolKind.Field).Select(s => (IFieldSymbol)s.GetISymbol()).Select(f => f.Name + ": " + f.Type);
AssertEx.SetEqual(expectedSynthesizedTypesAndMemberCounts, actual, itemSeparator: "\r\n");
}
public void VerifyUpdatedMethods(params string[] expectedMethodTokens)
{
AssertEx.Equal(
expectedMethodTokens,
EmitResult.UpdatedMethods.Select(methodHandle => $"0x{MetadataTokens.GetToken(methodHandle):X8}"));
}
}
}
|