/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/Test/Core/Compilation/CompilationExtensions.cs
521 строка
22 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. #nullable disable // Uncomment to enable the IOperation test hook on all test runs. Do not commit this uncommented. //#define ROSLYN_TEST_IOPERATION // Uncomment to enable the Used Assemblies test hook on all test runs. Do not commit this uncommented. //#define ROSLYN_TEST_USEDASSEMBLIES using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; using System.Text; using System.Threading; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.FlowAnalysis; using Microsoft.CodeAnalysis.Operations; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; using SeparatedWithManyChildren = Microsoft.CodeAnalysis.Syntax.SyntaxList.SeparatedWithManyChildren; namespace Microsoft.CodeAnalysis.Test.Utilities { public static class CompilationExtensions { internal static bool EnableVerifyIOperation { get; } = #if ROSLYN_TEST_IOPERATION true; #else !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ROSLYN_TEST_IOPERATION")); #endif internal static bool EnableVerifyUsedAssemblies { get; } = #if ROSLYN_TEST_USEDASSEMBLIES true; #else !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ROSLYN_TEST_USEDASSEMBLIES")); #endif internal static ImmutableArray<byte> EmitToArray( this Compilation compilation, EmitOptions options = null, CompilationTestData testData = null, DiagnosticDescription[] expectedWarnings = null, Stream pdbStream = null, IMethodSymbol debugEntryPoint = null, Stream sourceLinkStream = null, IEnumerable<EmbeddedText> embeddedTexts = null, IEnumerable<ResourceDescription> manifestResources = null, Stream metadataPEStream = null) { var peStream = new MemoryStream(); if (pdbStream == null && compilation.Options.OptimizationLevel == OptimizationLevel.Debug && options?.DebugInformationFormat != DebugInformationFormat.Embedded) { if (MonoHelpers.IsRunningOnMono() || PathUtilities.IsUnixLikePlatform) { options = (options ?? EmitOptions.Default).WithDebugInformationFormat(DebugInformationFormat.PortablePdb); } var discretePdb = (object)options != null && options.DebugInformationFormat != DebugInformationFormat.Embedded; pdbStream = discretePdb ? new MemoryStream() : null; } var emitResult = compilation.Emit( peStream: peStream, metadataPEStream: metadataPEStream, pdbStream: pdbStream, xmlDocumentationStream: null, win32Resources: null, manifestResources: manifestResources, options: options, debugEntryPoint: debugEntryPoint, sourceLinkStream: sourceLinkStream, embeddedTexts: embeddedTexts, rebuildData: null, testData: testData, cancellationToken: default(CancellationToken)); Assert.True(emitResult.Success, "Diagnostics:\r\n" + string.Join("\r\n", emitResult.Diagnostics.Select(d => d.ToString()))); if (expectedWarnings != null) { emitResult.Diagnostics.Verify(expectedWarnings); } return peStream.ToImmutable(); } public static MemoryStream EmitToStream(this Compilation compilation, EmitOptions options = null, DiagnosticDescription[] expectedWarnings = null) { var stream = new MemoryStream(); var emitResult = compilation.Emit(stream, options: options); Assert.True(emitResult.Success, "Diagnostics: " + string.Join(", ", emitResult.Diagnostics.Select(d => d.ToString()))); if (expectedWarnings != null) { emitResult.Diagnostics.Verify(expectedWarnings); } stream.Position = 0; return stream; } public static MetadataReference EmitToImageReference( this Compilation comp, EmitOptions options = null, bool embedInteropTypes = false, ImmutableArray<string> aliases = default, DiagnosticDescription[] expectedWarnings = null, DocumentationProvider documentation = null) => EmitToPortableExecutableReference(comp, options, embedInteropTypes, aliases, expectedWarnings, documentation); public static PortableExecutableReference EmitToPortableExecutableReference( this Compilation comp, EmitOptions options = null, bool embedInteropTypes = false, ImmutableArray<string> aliases = default, DiagnosticDescription[] expectedWarnings = null, DocumentationProvider documentation = null) { var image = comp.EmitToArray(options, expectedWarnings: expectedWarnings); if (comp.Options.OutputKind == OutputKind.NetModule) { return ModuleMetadata.CreateFromImage(image).GetReference(documentation, display: comp.MakeSourceModuleName()); } else { return AssemblyMetadata.CreateFromImage(image).GetReference(documentation, aliases: aliases, embedInteropTypes: embedInteropTypes, display: comp.MakeSourceAssemblySimpleName()); } } internal static CompilationDifference EmitDifference( this Compilation compilation, EmitBaseline baseline, ImmutableArray<SemanticEdit> edits, IEnumerable<ISymbol> allAddedSymbols = null, EmitDifferenceOptions? options = null, CompilationTestData testData = null) { testData ??= new CompilationTestData(); var isAddedSymbol = new Func<ISymbol, bool>(s => allAddedSymbols?.Contains(s) ?? false); using var mdStream = new MemoryStream(); using var ilStream = new MemoryStream(); using var pdbStream = new MemoryStream(); var result = compilation.EmitDifference( baseline, edits, isAddedSymbol, mdStream, ilStream, pdbStream, options ?? EmitDifferenceOptions.Default, testData, CancellationToken.None); return new CompilationDifference( mdStream.ToImmutable(), ilStream.ToImmutable(), pdbStream.ToImmutable(), testData, result); } internal static void VerifyAssemblyVersionsAndAliases(this Compilation compilation, params string[] expectedAssembliesAndAliases) { var actual = compilation.GetBoundReferenceManager().GetReferencedAssemblyAliases(). Select(t => $"{t.Item1.Identity.Name}, Version={t.Item1.Identity.Version}{(t.Item2.IsEmpty ? "" : ": " + string.Join(",", t.Item2))}"); AssertEx.Equal(expectedAssembliesAndAliases, actual, itemInspector: s => '"' + s + '"'); } internal static void VerifyAssemblyAliases(this Compilation compilation, params string[] expectedAssembliesAndAliases) { var actual = compilation.GetBoundReferenceManager().GetReferencedAssemblyAliases(). Select(t => $"{t.Item1.Identity.Name}{(t.Item2.IsEmpty ? "" : ": " + string.Join(",", t.Item2))}"); AssertEx.Equal(expectedAssembliesAndAliases, actual, itemInspector: s => '"' + s + '"'); } internal static void VerifyOperationTree(this Compilation compilation, SyntaxNode node, string expectedOperationTree) { SemanticModel model = compilation.GetSemanticModel(node.SyntaxTree); model.VerifyOperationTree(node, expectedOperationTree); } internal static void VerifyOperationTree(this Compilation compilation, string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false) { VerifyOperationTree(compilation, symbolToVerify: null, expectedOperationTree: expectedOperationTree, skipImplicitlyDeclaredSymbols: skipImplicitlyDeclaredSymbols); } internal static void VerifyOperationTree(this Compilation compilation, string symbolToVerify, string expectedOperationTree, bool skipImplicitlyDeclaredSymbols = false) { SyntaxTree tree = compilation.SyntaxTrees.First(); SyntaxNode root = tree.GetRoot(); SemanticModel model = compilation.GetSemanticModel(tree); var declarationsBuilder = ArrayBuilder<DeclarationInfo>.GetInstance(); model.ComputeDeclarationsInNode(root, associatedSymbol: null, getSymbol: true, builder: declarationsBuilder, cancellationToken: CancellationToken.None); var actualTextBuilder = new StringBuilder(); foreach (DeclarationInfo declaration in declarationsBuilder.ToArrayAndFree().Where(d => d.DeclaredSymbol != null).OrderBy(d => d.DeclaredSymbol.ToTestDisplayString())) { if (!CanHaveExecutableCodeBlock(declaration.DeclaredSymbol)) { continue; } if (skipImplicitlyDeclaredSymbols && declaration.DeclaredSymbol.IsImplicitlyDeclared) { continue; } if (!string.IsNullOrEmpty(symbolToVerify) && !declaration.DeclaredSymbol.Name.Equals(symbolToVerify, StringComparison.Ordinal)) { continue; } actualTextBuilder.Append(declaration.DeclaredSymbol.ToTestDisplayString()); if (declaration.ExecutableCodeBlocks.Length == 0) { actualTextBuilder.Append($" ('0' executable code blocks)"); } else { // Workaround for https://github.com/dotnet/roslyn/issues/11903 - skip the IOperation for EndBlockStatement. ImmutableArray<SyntaxNode> executableCodeBlocks = declaration.ExecutableCodeBlocks; if (declaration.DeclaredSymbol.Kind == SymbolKind.Method && compilation.Language == LanguageNames.VisualBasic) { executableCodeBlocks = executableCodeBlocks.RemoveAt(executableCodeBlocks.Length - 1); } foreach (SyntaxNode executableCodeBlock in executableCodeBlocks) { actualTextBuilder.Append(Environment.NewLine); model.AppendOperationTree(executableCodeBlock, actualTextBuilder, initialIndent: 2); } } actualTextBuilder.Append(Environment.NewLine); } OperationTreeVerifier.Verify(expectedOperationTree, actualTextBuilder.ToString()); } internal static bool CanHaveExecutableCodeBlock(ISymbol symbol) { switch (symbol.Kind) { case SymbolKind.Field: case SymbolKind.Event: case SymbolKind.Method: case SymbolKind.NamedType: case SymbolKind.Property: return true; default: return false; } } public static void ValidateIOperations(Func<Compilation> createCompilation) { if (!EnableVerifyIOperation) { return; } var compilation = createCompilation(); var roots = ArrayBuilder<(IOperation operation, ISymbol associatedSymbol)>.GetInstance(); var stopWatch = new Stopwatch(); start(stopWatch); void checkTimeout() { const int timeout = 15000; Assert.False(stopWatch.ElapsedMilliseconds > timeout, $"ValidateIOperations took too long: {stopWatch.ElapsedMilliseconds} ms"); } foreach (var tree in compilation.SyntaxTrees) { var semanticModel = compilation.GetSemanticModel(tree); var root = tree.GetRoot(); foreach (var node in root.DescendantNodesAndSelf()) { checkTimeout(); var operation = semanticModel.GetOperation(node); if (operation != null) { // Make sure IOperation returned by GetOperation(syntaxnode) will have same syntaxnode as the given syntaxnode(IOperation.Syntax == syntaxnode). Assert.True(node == operation.Syntax, $"Expected : {node} - Actual : {operation.Syntax}"); Assert.True(operation.Type == null || !operation.MustHaveNullType(), $"Unexpected non-null type: {operation.Type}"); Assert.Same(semanticModel, operation.SemanticModel); Assert.NotSame(semanticModel, ((Operation)operation).OwningSemanticModel); Assert.NotNull(((Operation)operation).OwningSemanticModel); Assert.Same(semanticModel, ((Operation)operation).OwningSemanticModel.ContainingPublicModelOrSelf); Assert.Same(semanticModel, semanticModel.ContainingPublicModelOrSelf); if (operation.Parent == null) { roots.Add((operation, semanticModel.GetDeclaredSymbol(operation.Syntax))); } } } tree.VerifyChildNodePositions(); } var explicitNodeMap = new Dictionary<SyntaxNode, IOperation>(); var visitor = TestOperationVisitor.Singleton; foreach (var (root, associatedSymbol) in roots) { foreach (var operation in root.DescendantsAndSelf()) { checkTimeout(); if (!operation.IsImplicit) { try { explicitNodeMap.Add(operation.Syntax, operation); } catch (ArgumentException) { Assert.Fail($"Duplicate explicit node for syntax ({operation.Syntax.RawKind}): {operation.Syntax.ToString()}"); } } visitor.Visit(operation); } stopWatch.Stop(); checkControlFlowGraph(root, associatedSymbol); start(stopWatch); } roots.Free(); stopWatch.Stop(); return; static void start(Stopwatch stopWatch) { if (!System.Diagnostics.Debugger.IsAttached) { stopWatch.Start(); } } void checkControlFlowGraph(IOperation root, ISymbol associatedSymbol) { switch (root) { case IBlockOperation blockOperation: // https://github.com/dotnet/roslyn/issues/27593 tracks adding ControlFlowGraph support in script code. if (blockOperation.Syntax.SyntaxTree.Options.Kind != SourceCodeKind.Script) { ControlFlowGraphVerifier.GetFlowGraph(compilation, ControlFlowGraphBuilder.Create(blockOperation), associatedSymbol); } break; case IMethodBodyOperation methodBody: case IConstructorBodyOperation constructorBody: case IFieldInitializerOperation fieldInitializerOperation: case IPropertyInitializerOperation propertyInitializerOperation: ControlFlowGraphVerifier.GetFlowGraph(compilation, ControlFlowGraphBuilder.Create(root), associatedSymbol); break; case IParameterInitializerOperation parameterInitializerOperation: // https://github.com/dotnet/roslyn/issues/27594 tracks adding support for getting ControlFlowGraph for parameter initializers for local and anonymous functions. if ((parameterInitializerOperation.Parameter.ContainingSymbol as IMethodSymbol)?.MethodKind is not (MethodKind.LocalFunction or MethodKind.AnonymousFunction)) { ControlFlowGraphVerifier.GetFlowGraph(compilation, ControlFlowGraphBuilder.Create(root), associatedSymbol); } break; } } } internal static void VerifyChildNodePositions(this SyntaxTree tree) { var nodes = tree.GetRoot().DescendantNodesAndSelf(); foreach (var node in nodes) { var childNodesAndTokens = node.ChildNodesAndTokens(); if (childNodesAndTokens.Node is { } container) { for (int i = 0; i < childNodesAndTokens.Count; i++) { if (container.GetNodeSlot(i) is SeparatedWithManyChildren separatedList) { verifyPositions(separatedList); } } } } static void verifyPositions(SeparatedWithManyChildren separatedList) { var green = (Microsoft.CodeAnalysis.Syntax.InternalSyntax.SyntaxList)separatedList.Green; // Calculate positions from start, using existing cache. int[] positions = getPositionsFromStart(separatedList); // Calculate positions from end, using existing cache. AssertEx.Equal(positions, getPositionsFromEnd(separatedList)); // Avoid testing without caches if the number of children is large. if (separatedList.SlotCount > 100) { return; } // Calculate positions from start, with empty cache. AssertEx.Equal(positions, getPositionsFromStart(new SeparatedWithManyChildren(green, null, separatedList.Position))); // Calculate positions from end, with empty cache. AssertEx.Equal(positions, getPositionsFromEnd(new SeparatedWithManyChildren(green, null, separatedList.Position))); } // Calculate positions from start, using any existing cache of red nodes on separated list. static int[] getPositionsFromStart(SeparatedWithManyChildren separatedList) { int n = separatedList.SlotCount; var positions = new int[n]; for (int i = 0; i < n; i++) { positions[i] = separatedList.GetChildPosition(i); } return positions; } // Calculate positions from end, using any existing cache of red nodes on separated list. static int[] getPositionsFromEnd(SeparatedWithManyChildren separatedList) { int n = separatedList.SlotCount; var positions = new int[n]; for (int i = n - 1; i >= 0; i--) { positions[i] = separatedList.GetChildPositionFromEnd(i); } return positions; } } /// <summary> /// The reference assembly System.Runtime.InteropServices.WindowsRuntime was removed in net5.0. This builds /// up <see cref="CompilationReference"/> which contains all of the well known types that were used from that /// reference by the compiler. /// </summary> public static PortableExecutableReference CreateWindowsRuntimeMetadataReference(TargetFramework targetFramework = TargetFramework.NetCoreApp) { var source = @" namespace System.Runtime.InteropServices.WindowsRuntime { public struct EventRegistrationToken { } public sealed class EventRegistrationTokenTable<T> where T : class { public T InvocationList { get; set; } public static EventRegistrationTokenTable<T> GetOrCreateEventRegistrationTokenTable(ref EventRegistrationTokenTable<T> refEventTable) { throw null; } public void RemoveEventHandler(EventRegistrationToken token) { } public void RemoveEventHandler(T handler) { } } public static class WindowsRuntimeMarshal { public static void AddEventHandler<T>(Func<T, EventRegistrationToken> addMethod, Action<EventRegistrationToken> removeMethod, T handler) { } public static void RemoveAllEventHandlers(Action<EventRegistrationToken> removeMethod) { } public static void RemoveEventHandler<T>(Action<EventRegistrationToken> removeMethod, T handler) { } } } "; // The actual System.Runtime.InteropServices.WindowsRuntime DLL has a public key of // b03f5f7f11d50a3a and version 4.0.4.0. The compiler just looks at these via // WellKnownTypes and WellKnownMembers so it can be safely skipped here. var compilation = CSharpCompilation.Create( "System.Runtime.InteropServices.WindowsRuntime", new[] { CSharpSyntaxTree.ParseText(SourceText.From(source, encoding: null, SourceHashAlgorithms.Default)) }, references: TargetFrameworkUtil.GetReferences(targetFramework), options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); compilation.VerifyEmitDiagnostics(); return compilation.EmitToPortableExecutableReference(); } } }