/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Tools/IdeBenchmarks/Lsp/LspSourceGeneratorBenchmarks.cs
188 строк
8 KB
David Barbet
Refactor LSP server logging to remove singleton lsp instance (#84001)
16 июн 2026, 05:04
Не верифицирован
16 июн 2026, 05:04
82e81e5
Код
Авторство
О чём код?
// 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.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.LanguageServer; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Roslyn.Test.Utilities; using Xunit; using LSP = Roslyn.LanguageServer.Protocol; using SumType = Roslyn.LanguageServer.Protocol.SumType<Roslyn.LanguageServer.Protocol.FullDocumentDiagnosticReport, Roslyn.LanguageServer.Protocol.UnchangedDocumentDiagnosticReport>; namespace IdeBenchmarks.Lsp { [MemoryDiagnoser] [GcServer(true)] public class LspSourceGeneratorBenchmarks : AbstractLanguageServerProtocolTests { private readonly UseExportProviderAttribute _useExportProviderAttribute = new UseExportProviderAttribute(); private TestLspServer? _testServer; /// <summary> /// Number of typing edits to perform per benchmark iteration. /// </summary> [Params(50, 1000)] public int TypingIterations { get; set; } [Params("Automatic", "Balanced")] public string ExecutionPreference { get; set; } = "Automatic"; public LspSourceGeneratorBenchmarks() : base(NoOpTestOutputHelper.Instance) { } private SourceGeneratorExecutionPreference GetExecutionPreference() => ExecutionPreference == "Balanced" ? SourceGeneratorExecutionPreference.Balanced : SourceGeneratorExecutionPreference.Automatic; [GlobalSetup] public void GlobalSetup() { } [IterationSetup] public void IterationSetup() => LoadSolutionAsync().Wait(); private async Task LoadSolutionAsync() { _useExportProviderAttribute.Before(null); // Typing extends the partial method name: partial void M() → Ma() → Maa() → ... // Each keystroke changes the method name, forcing the generator to update its output. var markup = """ public partial class C { public int F => _field; partial void M{|typing:|}(); } """; _testServer = await CreateTestLspServerAsync(markup, mutatingLspWorkspace: true); // Set the execution preference. var configService = _testServer.TestWorkspace.ExportProvider.GetExportedValue<TestWorkspaceConfigurationService>(); configService.Options = new WorkspaceConfigurationOptions(SourceGeneratorExecution: GetExecutionPreference()); var document = _testServer.GetCurrentSolution().Projects.Single().Documents.Single(); // Add a source generator that produces a partial class with a backing field // when it detects "public partial class C" in the compilation. var generator = new BenchmarkSourceGenerator(); _testServer.TestWorkspace.OnAnalyzerReferenceAdded( document.Project.Id, new TestGeneratorReference(generator)); await _testServer.WaitForSourceGeneratorsAsync(); // Open the document in LSP so edits flow through the server. await _testServer.OpenDocumentAsync(document.GetURI()); } [Benchmark] public async Task TypeAndWaitForSourceGenerators() { var testServer = _testServer!; var typingLocation = testServer.GetLocations("typing").Single(); var documentUri = typingLocation.DocumentUri; var typingLine = typingLocation.Range.Start.Line; var typingColumn = typingLocation.Range.Start.Character; // Simulate typing one character at a time and waiting for source generators after each keystroke. // After each wait, pull diagnostics for the document (mirrors what VS Code does on every change). for (var i = 0; i < TypingIterations; i++) { await testServer.InsertTextAsync(documentUri, (typingLine, typingColumn + i, "a")); await testServer.WaitForSourceGeneratorsAsync(); await testServer.WaitForDiagnosticsAsync(); await testServer.ExecuteRequestAsync<LSP.DocumentDiagnosticParams, SumType>( LSP.Methods.TextDocumentDiagnosticName, new LSP.DocumentDiagnosticParams { TextDocument = new LSP.TextDocumentIdentifier { DocumentUri = documentUri }, }, CancellationToken.None); } // After all typing, refresh source generators (simulates save / explicit refresh in balanced mode). await testServer.RefreshSourceGeneratorsAsync(forceRegeneration: false); // Request the source generated document text to verify the generator ran. var sourceGeneratedDocuments = await testServer.GetCurrentSolution().Projects.Single().GetSourceGeneratedDocumentsAsync(); Assert.NotEmpty(sourceGeneratedDocuments); var sgIdentity = sourceGeneratedDocuments.Single().Identity; var sgUri = SourceGeneratedDocumentUri.Create(sgIdentity); var sgResult = await testServer.ExecuteRequestAsync<LSP.TextDocumentContentParams, LSP.TextDocumentContentResult>( LSP.Methods.WorkspaceTextDocumentContentName, new LSP.TextDocumentContentParams { Uri = sgUri }, CancellationToken.None); Assert.NotNull(sgResult); Assert.Contains("public partial class C", sgResult.Text); } [IterationCleanup] public void Cleanup() { if (_testServer is not null) { _testServer.DisposeAsync().AsTask().Wait(); } _useExportProviderAttribute.After(null); } /// <summary> /// A source generator that looks for type "C" in the compilation, generates a backing /// field (<c>_field</c>), and produces implementations for any partial methods it finds. /// As the user types and the partial method name grows (M → Ma → Maa …), the generated /// output changes on every keystroke. /// </summary> #pragma warning disable RS1042 // test only generator private sealed class BenchmarkSourceGenerator : ISourceGenerator #pragma warning restore RS1042 // test only generator { public void Initialize(GeneratorInitializationContext context) { } public void Execute(GeneratorExecutionContext context) { var typeC = context.Compilation.GetTypeByMetadataName("C"); if (typeC is null) return; var sb = new StringBuilder(); sb.AppendLine("public partial class C"); sb.AppendLine("{"); sb.AppendLine(" private readonly int _field = 1;"); // Generate implementations for every partial method definition on C. foreach (var member in typeC.GetMembers()) { if (member is IMethodSymbol { IsPartialDefinition: true } method) { sb.AppendLine($" partial void {method.Name}() {{ }}"); } } sb.AppendLine("}"); context.AddSource("GeneratedPartial", SourceText.From(sb.ToString(), Encoding.UTF8)); } } } }