/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Core/RebuildTest/RoundTripUtil.cs
274 строки
12 KB
Jason Malinowski
Switch code that was using Assert.True/False as a replacement for .Fail
21 апр 2026, 23:28
21 апр 2026, 23:28
3bf59ba
Код
Авторство
О чём код?
// 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; using System.Collections.Immutable; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using System.Text; using System.Threading; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Rebuild; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.VisualBasic; using Microsoft.Extensions.Logging; using Microsoft.Metadata.Tools; using Roslyn.Utilities; using Xunit; using Roslyn.Test.Utilities; using System.Collections.Generic; namespace Microsoft.CodeAnalysis.Rebuild.UnitTests { internal static class RoundTripUtil { public static void VerifyRoundTrip( MemoryStream peStream, MemoryStream? pdbStream, string assemblyFileName, IRebuildArtifactResolver rebuildArtifactResolver, CancellationToken cancellationToken = default) { using var peReader = new PEReader(peStream); var embeddedPdbReader = peReader.GetEmbeddedPdbMetadataReader(); var portablePdbReader = pdbStream is not null ? MetadataReaderProvider.FromPortablePdbStream(pdbStream).GetMetadataReader() : null; Assert.True(embeddedPdbReader == null ^ portablePdbReader == null); var pdbReader = embeddedPdbReader ?? portablePdbReader ?? throw ExceptionUtilities.Unreachable(); var factory = LoggerFactory.Create(configure => { }); var logger = factory.CreateLogger("RoundTripVerification"); var optionsReader = new CompilationOptionsReader(logger, pdbReader, peReader); var compilationFactory = CompilationFactory.Create( assemblyFileName, optionsReader); using var rebuildPeStream = new MemoryStream(); using var rebuildPdbStream = optionsReader.HasEmbeddedPdb ? null : new MemoryStream(); var emitResult = compilationFactory.Emit( rebuildPeStream, rebuildPdbStream, rebuildArtifactResolver, cancellationToken); Assert.True(emitResult.Success); Assert.Equal(peStream.ToArray(), rebuildPeStream.ToArray()); Assert.Equal(pdbStream?.ToArray(), rebuildPdbStream?.ToArray()); } private record EmitInfo(ImmutableArray<byte> PEBytes, PEReader PEReader, ImmutableArray<byte> PdbBytes, MetadataReader PdbReader) : IDisposable { public void Dispose() => PEReader.Dispose(); } public static unsafe void VerifyRoundTrip<TCompilation>(TCompilation original, EmitOptions? emitOptions = null) where TCompilation : Compilation { Assert.True(original.SyntaxTrees.All(x => !string.IsNullOrEmpty(x.FilePath))); Assert.True(original.Options.Deterministic); original.VerifyDiagnostics(); emitOptions ??= new EmitOptions(debugInformationFormat: DebugInformationFormat.Embedded); using var originalEmit = emitOriginal(); var factory = LoggerFactory.Create(configure => { }); var logger = factory.CreateLogger("RoundTripVerification"); var optionsReader = new CompilationOptionsReader(logger, originalEmit.PdbReader, originalEmit.PEReader); var assemblyFileName = original.AssemblyName!; if (typeof(TCompilation) == typeof(CSharpCompilation)) { var assemblyFileExtension = original.Options.OutputKind switch { OutputKind.DynamicallyLinkedLibrary => ".dll", OutputKind.ConsoleApplication => ".exe", _ => throw new InvalidOperationException(), }; assemblyFileName += assemblyFileExtension; } var compilationFactory = CompilationFactory.Create(assemblyFileName, optionsReader); var rebuild = compilationFactory.CreateCompilation( original.SyntaxTrees.SelectAsArray(x => compilationFactory.CreateSyntaxTree(x.FilePath, x.GetText())), original.References.ToImmutableArray()); Assert.IsType<TCompilation>(rebuild); VerifyCompilationOptions(original.Options, rebuild.Options); using var rebuildStream = new MemoryStream(); using var rebuildPdbStream = optionsReader.HasEmbeddedPdb ? null : new MemoryStream(); var result = compilationFactory.Emit( rebuildStream, rebuildPdbStream, rebuild, embeddedTexts: ImmutableArray<EmbeddedText>.Empty, CancellationToken.None); Assert.Empty(result.Diagnostics); Assert.True(result.Success); var rebuildBytes = rebuildStream.ToImmutable(); using var rebuildReader = new PEReader(rebuildBytes); ImmutableArray<byte> rebuildPdbBytes; MetadataReader rebuildPdbReader; if (rebuildPdbStream is null) { rebuildPdbReader = rebuildReader.GetEmbeddedPdbMetadataReader() ?? throw new InvalidOperationException(); rebuildPdbBytes = new ReadOnlySpan<byte>(rebuildPdbReader.MetadataPointer, rebuildPdbReader.MetadataLength).ToArray().ToImmutableArray(); } else { rebuildPdbBytes = rebuildPdbStream.ToImmutable(); rebuildPdbReader = MetadataReaderProvider.FromPortablePdbImage(rebuildPdbBytes).GetMetadataReader(); } var rebuildEmit = new EmitInfo(rebuildBytes, rebuildReader, rebuildPdbBytes, rebuildPdbReader); // https://github.com/dotnet/roslyn/issues/52327 // This should be replaced with a CompilationDiff-based helper in MS.CA.RB which writes diffs // out to the test output, not necessarily entire visualizations. AssertImagesEqual(originalEmit, rebuildEmit); unsafe EmitInfo emitOriginal() { switch (emitOptions.DebugInformationFormat) { case DebugInformationFormat.Embedded: { var originalBytes = original.EmitToArray(emitOptions); var originalReader = new PEReader(originalBytes); var originalPdbReader = originalReader.GetEmbeddedPdbMetadataReader(); Assert.NotNull(originalPdbReader); var pdbSpan = new ReadOnlySpan<byte>(originalPdbReader!.MetadataPointer, originalPdbReader.MetadataLength); return new EmitInfo(originalBytes, originalReader, pdbSpan.ToArray().ToImmutableArray(), originalPdbReader); } case DebugInformationFormat.PortablePdb: { using var pdbStream = new MemoryStream(); var originalBytes = original.EmitToArray(emitOptions, pdbStream: pdbStream); var originalReader = new PEReader(originalBytes); var pdbBytes = pdbStream.ToImmutable(); var originalPdbReader = MetadataReaderProvider.FromPortablePdbImage(pdbBytes).GetMetadataReader(); return new EmitInfo(originalBytes, originalReader, pdbBytes, originalPdbReader); } default: throw new ArgumentException("Unsupported DebugInformationFormat: " + emitOptions.DebugInformationFormat); } } } private static void AssertImagesEqual( EmitInfo originalEmit, EmitInfo rebuildEmit) { if (!originalEmit.PEBytes.SequenceEqual(rebuildEmit.PEBytes)) { var originalMdv = getMdv(originalEmit.PEReader.GetMetadataReader()); var rebuildMdv = getMdv(rebuildEmit.PEReader.GetMetadataReader()); // At this point the bytes were not equal, so the MDV output should also not be equal. Assert.NotEqual(originalMdv, rebuildMdv); // https://github.com/dotnet/roslyn/issues/52327 // this is not all that useful without manual copy/pasting. Can we diff this during the test and show the differences? Assert.Fail($@" Expected: {originalMdv} Actual: {rebuildMdv} "); } if (!originalEmit.PdbBytes.SequenceEqual(rebuildEmit.PdbBytes)) { var originalMdv = getMdv(originalEmit.PdbReader); var rebuildMdv = getMdv(rebuildEmit.PdbReader); Assert.Fail($@" Expected: {originalMdv} Actual: {rebuildMdv} "); } static string getMdv(MetadataReader metadataReader) { using var stream = new MemoryStream(); var writer = new StreamWriter(stream); var visualizer = new MetadataVisualizer(metadataReader, writer); visualizer.Visualize(); writer.Flush(); return Encoding.UTF8.GetString(stream.ToArray()); } } #pragma warning disable 612 // 'CompilationOptions.Features' is obsolete public static void VerifyCompilationOptions(CompilationOptions originalOptions, CompilationOptions rebuildOptions) { var type = originalOptions.GetType(); foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { switch (propertyInfo.Name) { case nameof(CompilationOptions.GeneralDiagnosticOption): case nameof(CompilationOptions.Features): case nameof(CompilationOptions.ModuleName): case nameof(CompilationOptions.MainTypeName): case nameof(CompilationOptions.ConcurrentBuild): case nameof(CompilationOptions.WarningLevel): case nameof(CompilationOptions.SyntaxTreeOptionsProvider): case nameof(CompilationOptions.MetadataReferenceResolver): case nameof(CompilationOptions.XmlReferenceResolver): case nameof(CompilationOptions.SourceReferenceResolver): case nameof(CompilationOptions.StrongNameProvider): case nameof(CompilationOptions.AssemblyIdentityComparer): // Can be different and are special cased break; case nameof(VisualBasicCompilationOptions.ParseOptions): { var originalValue = propertyInfo.GetValue(originalOptions)!; var rebuildValue = propertyInfo.GetValue(rebuildOptions)!; VerifyParseOptions((ParseOptions)originalValue, (ParseOptions)rebuildValue); } break; default: { var originalValue = propertyInfo.GetValue(originalOptions); var rebuildValue = propertyInfo.GetValue(rebuildOptions); Assert.Equal(originalValue, rebuildValue); } break; } } } #pragma warning restore 612 private static void VerifyParseOptions(ParseOptions originalOptions, ParseOptions rebuildOptions) { var type = originalOptions.GetType(); foreach (var propertyInfo in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) { // Several options are expected to be different and they are special cased here. if (propertyInfo.Name == nameof(VisualBasicParseOptions.SpecifiedLanguageVersion)) { continue; } var originalValue = propertyInfo.GetValue(originalOptions); var rebuildValue = propertyInfo.GetValue(rebuildOptions); Assert.Equal(originalValue, rebuildValue); } } } }