/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Features/CSharpTest/EditAndContinue/Helpers/EditingTestBase.cs
286 строк
15 KB
Jared Parsons
Port remaining unit test projects to Linux (#83153)
17 апр 2026, 19:00
Не верифицирован
17 апр 2026, 19:00
8b511b9
Код
Авторство
О чём код?
// 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.Generic; using System.IO; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.CSharp.Test.Utilities; using Microsoft.CodeAnalysis.CSharp.UnitTests; using Microsoft.CodeAnalysis.Differencing; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Emit; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue.UnitTests; public abstract class EditingTestBase : CSharpTestBase { public static readonly string ReloadableAttributeDefSrc = "namespace System.Runtime.CompilerServices { class CreateNewOnMetadataUpdateAttribute : Attribute {} }"; public static readonly string ReloadableAttributeSrc = $""" using System.Runtime.CompilerServices; {ReloadableAttributeDefSrc} """; public static readonly string RestartRequiredOnMetadataUpdateAttributeDefSrc = """ namespace System.Runtime.CompilerServices { class RestartRequiredOnMetadataUpdateAttribute : Attribute {} } """; public static readonly string RestartRequiredOnMetadataUpdateAttributeSrc = $""" using System.Runtime.CompilerServices; {RestartRequiredOnMetadataUpdateAttributeDefSrc} """; internal enum MethodKind { Regular, Async, Iterator, ConstructorWithParameters } public static string GetResource(string keyword, string symbolDisplayName) => string.Format(FeaturesResources.member_kind_and_name, TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword), symbolDisplayName); public static string GetResource(string keyword, string symbolDisplayName, string containerKeyword, string containerDisplayName) => string.Format( FeaturesResources.symbol_kind_and_name_of_member_kind_and_name, TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword), symbolDisplayName, TryGetResource(containerKeyword) ?? throw ExceptionUtilities.UnexpectedValue(containerKeyword), containerDisplayName); public static string GetResource(string keyword) => TryGetResource(keyword) ?? throw ExceptionUtilities.UnexpectedValue(keyword); public static string? TryGetResource(string keyword) => keyword switch { "enum" => FeaturesResources.enum_, "enum value" => FeaturesResources.enum_value, "class" => FeaturesResources.class_, "interface" => FeaturesResources.interface_, "delegate" => FeaturesResources.delegate_, "struct" => FeaturesResources.struct_, "record" or "record class" => CSharpFeaturesResources.record_, "record struct" => CSharpFeaturesResources.record_struct, "static constructor" => FeaturesResources.static_constructor, "constructor" => FeaturesResources.constructor, "field" => FeaturesResources.field, "const field" => FeaturesResources.const_field, "method" => FeaturesResources.method, "property" => FeaturesResources.property_, "property getter" => CSharpFeaturesResources.property_getter, "property setter" => CSharpFeaturesResources.property_setter, "auto-property" => FeaturesResources.auto_property, "event" => FeaturesResources.event_, "event field" => CSharpFeaturesResources.event_field, "event accessor" => FeaturesResources.event_accessor, "indexer" => CSharpFeaturesResources.indexer, "indexer getter" => CSharpFeaturesResources.indexer_getter, "indexer setter" => CSharpFeaturesResources.indexer_setter, "parameter" => FeaturesResources.parameter, "type parameter" => FeaturesResources.type_parameter, "lambda" => CSharpFeaturesResources.lambda, "local function" => FeaturesResources.local_function, "where clause" => CSharpFeaturesResources.where_clause, "select clause" => CSharpFeaturesResources.select_clause, "groupby clause" => CSharpFeaturesResources.groupby_clause, "orderby clause" => CSharpFeaturesResources.orderby_clause, "join clause" => CSharpFeaturesResources.join_clause, "from clause" => CSharpFeaturesResources.from_clause, "let clause" => CSharpFeaturesResources.let_clause, "top-level statement" => CSharpFeaturesResources.top_level_statement, "top-level code" => CSharpFeaturesResources.top_level_code, "class with explicit or sequential layout" => string.Format(FeaturesResources.class_with_explicit_or_sequential_layout), "extension block" => FeaturesResources.extension_block, _ => null }; internal static SemanticEditDescription[] NoSemanticEdits = []; internal static RudeEditDiagnosticDescription Diagnostic(RudeEditKind rudeEditKind, string squiggle, params string[] arguments) => new(rudeEditKind, squiggle, arguments, firstLine: null); internal static RuntimeRudeEditDescription RuntimeRudeEdit(int marker, RudeEditKind rudeEditKind, LinePosition position, params string[] arguments) => new(marker, rudeEditKind, position, arguments); internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func<Compilation, ISymbol> symbolProvider, SyntaxMapDescription.Mapping? syntaxMap, IEnumerable<RuntimeRudeEditDescription>? rudeEdits = null, string? partialType = null) => SemanticEdit(kind, symbolProvider, syntaxMap?.Spans, rudeEdits, partialType); internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func<Compilation, ISymbol> symbolProvider, IEnumerable<(TextSpan, TextSpan)>? syntaxMap, IEnumerable<RuntimeRudeEditDescription>? rudeEdits = null, string? partialType = null) => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember<INamedTypeSymbol>(partialType) : null, syntaxMap, rudeEdits, hasSyntaxMap: syntaxMap != null, deletedSymbolContainerProvider: null); internal static SemanticEditDescription SemanticEdit(SemanticEditKind kind, Func<Compilation, ISymbol> symbolProvider, string? partialType = null, bool preserveLocalVariables = false, Func<Compilation, ISymbol>? deletedSymbolContainerProvider = null) => new(kind, symbolProvider, (partialType != null) ? c => c.GetMember<INamedTypeSymbol>(partialType) : null, syntaxMap: null, rudeEdits: null, preserveLocalVariables, deletedSymbolContainerProvider); internal static string DeletedSymbolDisplay(string kind, string displayName) => string.Format(FeaturesResources.member_kind_and_name, kind, displayName); internal static DocumentAnalysisResultsDescription DocumentResults( ActiveStatementsDescription? activeStatements = null, SemanticEditDescription[]? semanticEdits = null, RudeEditDiagnosticDescription[]? diagnostics = null) => new(activeStatements, semanticEdits, lineEdits: null, diagnostics); internal static string GetDocumentFilePath(int documentIndex) => Path.Combine(TempRoot.Root, documentIndex.ToString() + ".cs"); private static SyntaxTree ParseSource(string markedSource, int documentIndex = 0) => SyntaxFactory.ParseSyntaxTree( SourceMarkers.Clear(markedSource), CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Preview), path: GetDocumentFilePath(documentIndex)); internal static EditScriptDescription GetTopEdits(string methodBody1, string methodBody2, MethodKind kind) => GetTopEdits(WrapMethodBodyWithClass(methodBody1, kind), WrapMethodBodyWithClass(methodBody2, kind)); internal static EditScriptDescription GetTopEdits(string src1, string src2, int documentIndex = 0) { // Normalize line endings to \r\n so that character offsets in VerifyEdits // assertions are consistent across Windows and Unix. src1 = src1.NormalizeLineEndings(); src2 = src2.NormalizeLineEndings(); var tree1 = ParseSource(src1, documentIndex); var tree2 = ParseSource(src2, documentIndex); tree1.GetDiagnostics().Verify(); tree2.GetDiagnostics().Verify(); var match = SyntaxComparer.TopLevel.ComputeMatch(tree1.GetRoot(), tree2.GetRoot()); return new(src1, src2, match.GetTreeEdits()); } internal static EditScriptDescription GetTopEdits(EditScriptDescription methodEdits) { var oldMethodSource = methodEdits.Match.OldRoot.ToFullString(); var newMethodSource = methodEdits.Match.NewRoot.ToFullString(); return GetTopEdits(WrapMethodBodyWithClass(oldMethodSource), WrapMethodBodyWithClass(newMethodSource)); } /// <summary> /// Gets method edits on the current level of the source hierarchy. This means that edits on lower labeled levels of the hierarchy are not expected to be returned. /// </summary> internal static EditScriptDescription GetMethodEdits(string src1, string src2, MethodKind kind = MethodKind.Regular) { src1 = src1.NormalizeLineEndings(); src2 = src2.NormalizeLineEndings(); var match = GetMethodMatch(src1, src2, kind); return new(src1, src2, match.GetTreeEdits()); } internal static Match<SyntaxNode> GetMethodMatch(string src1, string src2, MethodKind kind = MethodKind.Regular) { var m1 = MakeMethodBody(src1, kind); var m2 = MakeMethodBody(src2, kind); var match = m1.ComputeSingleRootMatch(m2, knownMatches: null); Contract.ThrowIfNull(match); var stateMachineInfo1 = m1.GetStateMachineInfo(); var stateMachineInfo2 = m2.GetStateMachineInfo(); var needsSyntaxMap = stateMachineInfo1.HasSuspensionPoints && stateMachineInfo2.HasSuspensionPoints; Assert.Equal(kind is not MethodKind.Regular and not MethodKind.ConstructorWithParameters, needsSyntaxMap); return match; } internal static IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>> GetMethodMatches(string src1, string src2, MethodKind kind = MethodKind.Regular) { var methodMatch = GetMethodMatch(src1, src2, kind); var analyzer = EditAndContinueTestVerifier.CreateAnalyzer(faultInjector: null, LanguageNames.CSharp); return EditAndContinueTestVerifier.GetMethodMatches(analyzer, methodMatch); } public static MatchingPairs ToMatchingPairs(Match<SyntaxNode> match) => EditAndContinueTestVerifier.ToMatchingPairs(match); public static MatchingPairs ToMatchingPairs(IEnumerable<KeyValuePair<SyntaxNode, SyntaxNode>> matches) => EditAndContinueTestVerifier.ToMatchingPairs(matches); internal static MemberBody MakeMethodBody( string bodySource, MethodKind kind = MethodKind.Regular) { // Normalize line endings to \r\n so that character offsets in VerifyEdits // assertions are consistent across Windows and Unix. bodySource = bodySource.NormalizeLineEndings(); var source = WrapMethodBodyWithClass(bodySource, kind); var tree = ParseSource(source); var root = tree.GetRoot(); tree.GetDiagnostics().Verify(); var declaration = (BaseMethodDeclarationSyntax)((ClassDeclarationSyntax)((CompilationUnitSyntax)root).Members[0]).Members[0]; if (kind == MethodKind.ConstructorWithParameters) { var body = SyntaxUtilities.TryGetDeclarationBody(declaration, symbol: null); Contract.ThrowIfNull(body); return body; } // We need to preserve the parent node to allow detection of state machine methods in the analyzer. // If we are not testing a state machine method we only use the body to avoid updating positions in all existing tests. var bodyNode = (kind != MethodKind.Regular) ? ((BaseMethodDeclarationSyntax)SyntaxFactory.SyntaxTree(declaration).GetRoot()).Body : (BlockSyntax)SyntaxFactory.SyntaxTree(declaration.Body!).GetRoot(); return SyntaxUtilities.CreateSimpleBody(bodyNode)!; } internal static string WrapMethodBodyWithClass(string bodySource, MethodKind kind = MethodKind.Regular) => kind switch { MethodKind.Iterator => "class C { IEnumerable<int> F() { " + bodySource + " } }", MethodKind.Async => "class C { async Task<int> F() { " + bodySource + " } }", MethodKind.ConstructorWithParameters => "class C { C" + bodySource + " }", _ => "class C { void F() { " + bodySource + " } }", }; internal static ActiveStatementsDescription GetActiveStatements(string oldSource, string newSource, ActiveStatementFlags[]? flags = null, int documentIndex = 0) => new(oldSource.NormalizeLineEndings(), newSource.NormalizeLineEndings(), source => SyntaxFactory.ParseSyntaxTree(source, path: GetDocumentFilePath(documentIndex)), flags); internal static SyntaxMapDescription GetSyntaxMap(string oldSource, string newSource) => new(oldSource.NormalizeLineEndings(), newSource.NormalizeLineEndings()); internal static void VerifyPreserveLocalVariables(EditScriptDescription edits, bool preserveLocalVariables) { var oldDeclaration = (MethodDeclarationSyntax)((ClassDeclarationSyntax)((CompilationUnitSyntax)edits.Match.OldRoot).Members[0]).Members[0]; var oldBody = SyntaxUtilities.TryGetDeclarationBody(oldDeclaration, symbol: null); Contract.ThrowIfNull(oldBody); var newDeclaration = (MethodDeclarationSyntax)((ClassDeclarationSyntax)((CompilationUnitSyntax)edits.Match.NewRoot).Members[0]).Members[0]; var newBody = SyntaxUtilities.TryGetDeclarationBody(newDeclaration, symbol: null); Contract.ThrowIfNull(newBody); _ = oldBody.ComputeMap(newBody, knownMatches: null); var oldStateMachineInfo = oldBody.GetStateMachineInfo(); var newStateMachineInfo = newBody.GetStateMachineInfo(); var needsSyntaxMap = oldStateMachineInfo.HasSuspensionPoints && newStateMachineInfo.HasSuspensionPoints; // Active methods are detected to preserve local variables for variable mapping and // edited async/iterator methods are considered active. Assert.Equal(preserveLocalVariables, needsSyntaxMap); } }