/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/EditorFeatures/Test/EditAndContinue/EditorManagedHotReloadLanguageServiceTests.cs
578 строк
29 KB
Tomáš Matoušek
Improves tracking of pending solution updates (#84726)
01 авг 2026, 04:09
Не верифицирован
01 авг 2026, 04:09
518aa60
Код
Авторство
О чём код?
// 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.IO; using System.IO.Pipelines; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.BrokeredServices; using Microsoft.CodeAnalysis.BrokeredServices.UnitTests; using Microsoft.CodeAnalysis.Contracts.EditAndContinue; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.EditAndContinue; using Microsoft.CodeAnalysis.EditAndContinue.UnitTests; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Options; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; using Microsoft.CodeAnalysis.Test.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.UnitTests; using Microsoft.ServiceHub.Framework; using Roslyn.Test.Utilities; using Roslyn.Utilities; using Xunit; using DebuggerContracts = Microsoft.VisualStudio.Debugger.Contracts.HotReload; namespace Microsoft.CodeAnalysis.Editor.UnitTests.EditAndContinue; [UseExportProvider] public sealed class EditorManagedHotReloadLanguageServiceTests : EditAndContinueWorkspaceTestBase { private sealed class TestServiceBroker(Func<ServiceRpcDescriptor, Type, object> createService) : IServiceBroker { public List<ServiceRpcDescriptor> RequestedDescriptors { get; } = []; public event EventHandler<BrokeredServicesChangedEventArgs> AvailabilityChanged { add { } remove { } } public ValueTask<IDuplexPipe> GetPipeAsync(ServiceMoniker serviceMoniker, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) => throw new NotImplementedException(); public ValueTask<T> GetProxyAsync<T>(ServiceRpcDescriptor serviceDescriptor, ServiceActivationOptions options = default, CancellationToken cancellationToken = default) where T : class { RequestedDescriptors.Add(serviceDescriptor); return new ValueTask<T>((T)createService(serviceDescriptor, typeof(T))); } } private sealed class TestManagedHotReloadServiceProxy(IServiceBroker serviceBroker) : BrokeredServiceProxy<DebuggerContracts.IManagedHotReloadService>(serviceBroker, BrokeredServiceDescriptors.DebuggerManagedHotReloadService) { public ValueTask<ImmutableArray<string>> GetCapabilitiesAsync(CancellationToken cancellationToken) => InvokeAsync((service, cancellationToken) => service.GetCapabilitiesAsync(cancellationToken), cancellationToken); } private sealed class TestManagedHotReloadService(ImmutableArray<string> capabilities) : DebuggerContracts.IManagedHotReloadService, IDisposable { public ValueTask<ImmutableArray<Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue.ManagedActiveStatementDebugInfo>> GetActiveStatementsAsync(CancellationToken cancellationToken) => ValueTask.FromResult(ImmutableArray<Microsoft.VisualStudio.Debugger.Contracts.EditAndContinue.ManagedActiveStatementDebugInfo>.Empty); public ValueTask<DebuggerContracts.ManagedHotReloadAvailability> GetAvailabilityAsync(Guid module, CancellationToken cancellationToken) => ValueTask.FromResult(new DebuggerContracts.ManagedHotReloadAvailability(DebuggerContracts.ManagedHotReloadAvailabilityStatus.Available, null)); public ValueTask<ImmutableArray<string>> GetCapabilitiesAsync(CancellationToken cancellationToken) => ValueTask.FromResult(capabilities); public ValueTask PrepareModuleForUpdateAsync(Guid module, CancellationToken cancellationToken) => ValueTask.CompletedTask; public void Dispose() { } } private sealed class TestHotReloadLogger : DebuggerContracts.IHotReloadLogger, IDisposable { public List<DebuggerContracts.HotReloadLogMessage> Messages { get; } = []; public ValueTask LogAsync(DebuggerContracts.HotReloadLogMessage message, CancellationToken cancellationToken) { Messages.Add(message); return ValueTask.CompletedTask; } public void Dispose() { } } private sealed class TestHotReloadLoggerProxy(IServiceBroker serviceBroker) : BrokeredServiceProxy<DebuggerContracts.IHotReloadLogger>(serviceBroker, BrokeredServiceDescriptors.HotReloadLoggerService) { public ValueTask LogAsync(DebuggerContracts.HotReloadLogMessage message, CancellationToken cancellationToken) => InvokeAsync((service, cancellationToken) => service.LogAsync(message, cancellationToken), cancellationToken); } private static string Inspect(DiagnosticData d) => $"{d.Severity} {d.Id}:" + (!string.IsNullOrWhiteSpace(d.DataLocation.UnmappedFileSpan.Path) ? $" {d.DataLocation.UnmappedFileSpan.Path}({d.DataLocation.UnmappedFileSpan.StartLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.StartLinePosition.Character}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Line}, {d.DataLocation.UnmappedFileSpan.EndLinePosition.Character}):" : "") + $" {d.Message}"; private static string Inspect(DebuggerContracts.ManagedHotReloadDiagnostic d) => $"{d.Severity} {d.Id}:" + (!string.IsNullOrWhiteSpace(d.FilePath) ? $" {d.FilePath}({d.Span.StartLine}, {d.Span.StartColumn}, {d.Span.EndLine}, {d.Span.EndColumn}):" : "") + $" {d.Message}"; private TestWorkspace CreateEditorWorkspace(out Solution solution, out EditAndContinueService service, out ManagedHotReloadLanguageService languageService, out PdbMatchingSourceTextProvider sourceTextProvider, Type[] additionalParts = null) { var composition = EditorTestCompositions.EditorFeatures .AddParts( typeof(MockHostWorkspaceProvider), typeof(MockManagedHotReloadService), typeof(MockServiceBrokerProvider)) .AddParts(additionalParts); var workspace = new TestWorkspace(composition: composition, solutionTelemetryId: s_solutionTelemetryId); ((MockServiceBroker)workspace.Services.GetRequiredService<IServiceBrokerProvider>().ServiceBroker).CreateService = t => t switch { _ when t == typeof(DebuggerContracts.IHotReloadLogger) => new MockHotReloadLogger(), _ => throw ExceptionUtilities.UnexpectedValue(t) }; ((MockHostWorkspaceProvider)workspace.GetService<IHostWorkspaceProvider>()).Workspace = workspace; solution = workspace.CurrentSolution; service = GetEditAndContinueService(workspace); sourceTextProvider = new PdbMatchingSourceTextProvider(workspace); var factory = workspace.GetService<ManagedHotReloadLanguageServiceFactory>(); var serviceBroker = workspace.Services.GetRequiredService<IServiceBrokerProvider>().ServiceBroker; var solutionSnapshotProvider = workspace.GetService<ISolutionSnapshotProvider>(); languageService = factory.Create(serviceBroker, solutionSnapshotProvider, workspace.GetService<IHostWorkspaceProvider>(), sourceTextProvider); return workspace; } private class TestContext : IDisposable { public readonly TestWorkspace LocalWorkspace; public readonly PdbMatchingSourceTextProvider PdbMatchingSourceTextProvider; public readonly MockEditAndContinueService MockEncService; public readonly ManagedHotReloadLanguageService LocalService; public TestContext() { var localComposition = EditorTestCompositions.LanguageServerProtocolEditorFeatures .AddExcludedPartTypes( typeof(EditAndContinueService.WorkspaceServiceFactory)) .AddParts( typeof(NoCompilationLanguageService), typeof(MockHostWorkspaceProvider), typeof(MockServiceBrokerProvider), typeof(MockEditAndContinueServiceFactory), typeof(MockManagedHotReloadService)); LocalWorkspace = new TestWorkspace(composition: localComposition); var globalOptions = LocalWorkspace.GetService<IGlobalOptionService>(); ((MockHostWorkspaceProvider)LocalWorkspace.GetService<IHostWorkspaceProvider>()).Workspace = LocalWorkspace; ((MockServiceBroker)LocalWorkspace.Services.GetRequiredService<IServiceBrokerProvider>().ServiceBroker).CreateService = t => t switch { _ when t == typeof(DebuggerContracts.IHotReloadLogger) => new MockHotReloadLogger(), _ => throw ExceptionUtilities.UnexpectedValue(t) }; MockEncService = (MockEditAndContinueService)LocalWorkspace.Services.GetRequiredService<IEditAndContinueWorkspaceService>().Service; var localFactory = LocalWorkspace.GetService<ManagedHotReloadLanguageServiceFactory>(); var localBroker = LocalWorkspace.Services.GetRequiredService<IServiceBrokerProvider>().ServiceBroker; var localSnapshotProvider = LocalWorkspace.GetService<ISolutionSnapshotProvider>(); PdbMatchingSourceTextProvider = new PdbMatchingSourceTextProvider(LocalWorkspace); LocalService = localFactory.Create(localBroker, localSnapshotProvider, LocalWorkspace.GetService<IHostWorkspaceProvider>(), PdbMatchingSourceTextProvider); } public void Dispose() { LocalWorkspace.Dispose(); PdbMatchingSourceTextProvider.Dispose(); } } [Theory, CombinatorialData] public async Task Test(bool commitChanges) { using var context = new TestContext(); var localWorkspace = context.LocalWorkspace; var mockEncService = context.MockEncService; var localService = context.LocalService; await localWorkspace.ChangeSolutionAsync(localWorkspace.CurrentSolution .AddTestProject("proj", out var projectId) .AddTestDocument("class C { }", "test.cs", out var documentId).Project.Solution); var solution = localWorkspace.CurrentSolution; var project = solution.GetRequiredProject(projectId); var document = solution.GetRequiredDocument(documentId); var syntaxTree = await document.GetRequiredSyntaxTreeAsync(CancellationToken.None); var sessionState = localWorkspace.GetService<IEditAndContinueSessionTracker>(); var diagnosticRefresher = localWorkspace.GetService<IDiagnosticsRefresher>(); var observedDiagnosticVersion = diagnosticRefresher.GlobalStateVersion; // StartDebuggingSession var debuggingSession = mockEncService.StartDebuggingSessionImpl = (_, _, _, _) => new DebuggingSessionId(1); Assert.False(sessionState.IsSessionActive); Assert.Empty(sessionState.ApplyChangesDiagnostics); await localService.StartSessionAsync(CancellationToken.None); Assert.True(sessionState.IsSessionActive); Assert.Empty(sessionState.ApplyChangesDiagnostics); // EnterBreakStateAsync mockEncService.BreakStateOrCapabilitiesChangedImpl = inBreakState => { Assert.True(inBreakState); }; await localService.EnterBreakStateAsync(CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); Assert.Empty(sessionState.ApplyChangesDiagnostics); Assert.True(sessionState.IsSessionActive); // EmitSolutionUpdate var errorReadingFileDescriptor = EditAndContinueDiagnosticDescriptors.GetDescriptor(EditAndContinueErrorCode.ErrorReadingFile); var moduleErrorDescriptor = EditAndContinueDiagnosticDescriptors.GetModuleDiagnosticDescriptor(Contracts.EditAndContinue.ManagedHotReloadAvailabilityStatus.Optimized); var syntaxErrorDescriptor = new DiagnosticDescriptor("CS0001", "Syntax error", "Syntax error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true); var compilerHiddenDescriptor = new DiagnosticDescriptor("CS0002", "Hidden", "Emit Hidden", "Compiler", DiagnosticSeverity.Hidden, isEnabledByDefault: true); var compilerInfoDescriptor = new DiagnosticDescriptor("CS0003", "Info", "Emit Info", "Compiler", DiagnosticSeverity.Info, isEnabledByDefault: true); var compilerWarningDescriptor = new DiagnosticDescriptor("CS0004", "Emit Warning", "Emit Warning", "Compiler", DiagnosticSeverity.Warning, isEnabledByDefault: true); var compilerErrorDescriptor = new DiagnosticDescriptor("CS0005", "Emit Error", "Emit Error", "Compiler", DiagnosticSeverity.Error, isEnabledByDefault: true); mockEncService.EmitSolutionUpdateImpl = (solution, _, _) => { var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!; var documentDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2)), ["doc", "error 1"]); var projectDiagnostic = CodeAnalysis.Diagnostic.Create(errorReadingFileDescriptor, Location.None, ["proj", "error 2"]); var moduleError = CodeAnalysis.Diagnostic.Create(moduleErrorDescriptor, Location.None, ["proj", "module error"]); var syntaxError = CodeAnalysis.Diagnostic.Create(syntaxErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); var compilerDocHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); var compilerDocInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); var compilerDocWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); var compilerDocError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.Create(syntaxTree, TextSpan.FromBounds(1, 2))); var compilerProjectHidden = CodeAnalysis.Diagnostic.Create(compilerHiddenDescriptor, Location.None); var compilerProjectInfo = CodeAnalysis.Diagnostic.Create(compilerInfoDescriptor, Location.None); var compilerProjectWarning = CodeAnalysis.Diagnostic.Create(compilerWarningDescriptor, Location.None); var compilerProjectError = CodeAnalysis.Diagnostic.Create(compilerErrorDescriptor, Location.None); var rudeEditDiagnostic = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["x"]).ToDiagnostic(syntaxTree); var deletedDocumentRudeEdit = new RudeEditDiagnostic(RudeEditKind.Delete, TextSpan.FromBounds(2, 3), arguments: ["<deleted>"]).ToDiagnostic(tree: null); return new() { SolutionAction = SolutionAction.PendingUpdate, Solution = solution, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []), Diagnostics = [ new ProjectDiagnostics( project.Id, [ documentDiagnostic, projectDiagnostic, moduleError, rudeEditDiagnostic, deletedDocumentRudeEdit, compilerDocError, compilerDocWarning, compilerDocHidden, compilerDocInfo, compilerProjectError, compilerProjectWarning, compilerProjectHidden, compilerProjectInfo, ]) ], SyntaxError = syntaxError, ProjectsToRebuild = [projectId], ProjectsToRestart = ImmutableDictionary<ProjectId, ImmutableArray<ProjectId>>.Empty.Add(projectId, []), ProjectsToRedeploy = [projectId], }; }; var runningProjectInfo = new DebuggerContracts.RunningProjectInfo( new DebuggerContracts.ProjectInstanceId(project.FilePath, "net10.0"), restartAutomatically: false); var updates = await localService.GetUpdatesAsync([runningProjectInfo], CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); AssertEx.Equal( [ $"Error ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", $"Error ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", $"Error ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}", $"Error ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}", $"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error", $"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning", $"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error", $"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning", ], sessionState.ApplyChangesDiagnostics.Select(Inspect)); AssertEx.Equal( [ $"RestartRequired ENC1001: {document.FilePath}(0, 1, 0, 2): {string.Format(FeaturesResources.ErrorReadingFile, "doc", "error 1")}", $"RestartRequired ENC1001: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.ErrorReadingFile, "proj", "error 2")}", $"RestartRequired ENC2012: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, "proj", "module error")}", $"RestartRequired ENC0033: {document.FilePath}(0, 2, 0, 3): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "x")}", $"RestartRequired ENC0033: {project.FilePath}(0, 0, 0, 0): {string.Format(FeaturesResources.Deleting_0_requires_restarting_the_application, "<deleted>")}", $"Error CS0005: {document.FilePath}(0, 1, 0, 2): Emit Error", $"Warning CS0004: {document.FilePath}(0, 1, 0, 2): Emit Warning", $"Error CS0005: {project.FilePath}(0, 0, 0, 0): Emit Error", $"Warning CS0004: {project.FilePath}(0, 0, 0, 0): Emit Warning", $"Error CS0001: {document.FilePath}(0, 1, 0, 2): Syntax error", ], updates.Diagnostics.Select(Inspect)); var moduleId = Guid.NewGuid(); var methodId = new ManagedModuleMethodId(token: 0x06000001, version: 2); mockEncService.EmitSolutionUpdateImpl = (solution, _, _) => { var syntaxTree = solution.GetRequiredDocument(documentId).GetSyntaxTreeSynchronously(CancellationToken.None)!; return new() { SolutionAction = SolutionAction.PendingUpdate, Solution = solution, ModuleUpdates = new ModuleUpdates( ModuleUpdateStatus.Ready, [ new ManagedHotReloadUpdate( moduleId, "module.dll", project.Id, ilDelta: [1], metadataDelta: [2], pdbDelta: [3], updatedTypes: [0x02000001], requiredCapabilities: ["Baseline"], updatedMethods: [0x06000002], sequencePoints: [new SequencePointUpdates("file.cs", [new SourceLineUpdate(1, 2)])], activeStatements: [new ManagedActiveStatementUpdate(methodId, ilOffset: 1, new(1, 2, 3, 4))], exceptionRegions: [new ManagedExceptionRegionUpdate(methodId, delta: 1, new(10, 20, 30, 40))]) ]), Diagnostics = [], SyntaxError = null, ProjectsToRebuild = [], ProjectsToRestart = ImmutableDictionary<ProjectId, ImmutableArray<ProjectId>>.Empty, ProjectsToRedeploy = [], }; }; updates = await localService.GetUpdatesAsync(runningProjects: [runningProjectInfo], CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); var update = updates.Updates.Single(); Assert.Equal(moduleId, update.Module); Assert.Equal("module.dll", update.ModuleName); AssertEx.SequenceEqual([(byte)1], update.ILDelta); AssertEx.SequenceEqual([(byte)2], update.MetadataDelta); AssertEx.SequenceEqual([(byte)3], update.PdbDelta); AssertEx.SequenceEqual([0x02000001], update.UpdatedTypes); AssertEx.SequenceEqual(["Baseline"], update.RequiredCapabilities); AssertEx.SequenceEqual([0x06000002], update.UpdatedMethods); var sequencePoint = update.SequencePoints.Single(); Assert.Equal("file.cs", sequencePoint.FileName); AssertEx.SequenceEqual(["1->2"], sequencePoint.LineUpdates.Select(u => $"{u.OldLine}->{u.NewLine}")); var activeStatement = update.ActiveStatements.Single(); Assert.Equal(0x06000001, activeStatement.Method.Token); Assert.Equal(2, activeStatement.Method.Version); Assert.Equal(1, activeStatement.ILOffset); Assert.Equal(new(1, 2, 3, 4), activeStatement.NewSpan); var exceptionRegion = update.ExceptionRegions.Single(); Assert.Equal(0x06000001, exceptionRegion.Method.Token); Assert.Equal(2, exceptionRegion.Method.Version); Assert.Equal(1, exceptionRegion.Delta); Assert.Equal(new(10, 20, 30, 40), exceptionRegion.NewSpan); Assert.True(sessionState.IsSessionActive); #pragma warning disable CS0612 // Type or member is obsolete // validate that obsolete overload does not throw for empty array: _ = await localService.GetUpdatesAsync(runningProjects: ImmutableArray<string>.Empty, CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); #pragma warning restore if (commitChanges) { // CommitUpdatesAsync var called = false; mockEncService.CommitSolutionUpdateImpl = () => called = true; await localService.CommitUpdatesAsync(CancellationToken.None); Assert.True(called); } else { // DiscardUpdatesAsync var called = false; mockEncService.DiscardSolutionUpdateImpl = () => called = true; await localService.DiscardUpdatesAsync(CancellationToken.None); Assert.True(called); } Assert.True(sessionState.IsSessionActive); // EndSessionAsync await localService.EndSessionAsync(CancellationToken.None); Assert.Equal(++observedDiagnosticVersion, diagnosticRefresher.GlobalStateVersion); Assert.Empty(sessionState.ApplyChangesDiagnostics); Assert.False(sessionState.IsSessionActive); } [Theory] [InlineData(SolutionAction.None, null)] [InlineData(SolutionAction.Committed, null)] [InlineData(SolutionAction.PendingUpdate, true)] [InlineData(SolutionAction.PendingUpdate, false)] internal async Task SolutionActions(SolutionAction solutionAction, bool? commit) { using var context = new TestContext(); var localService = context.LocalService; var localWorkspace = context.LocalWorkspace; var serviceImpl = localService.Impl.GetTestAccessor(); context.MockEncService.StartDebuggingSessionImpl = (_, _, _, _) => new DebuggingSessionId(1); context.MockEncService.EmitSolutionUpdateImpl = (solution, _, _) => new() { SolutionAction = solutionAction, Solution = solution, ModuleUpdates = new ModuleUpdates(ModuleUpdateStatus.Ready, []), Diagnostics = [], SyntaxError = null, ProjectsToRebuild = [], ProjectsToRestart = [], ProjectsToRedeploy = [], }; await localService.StartSessionAsync(CancellationToken.None); var initialSolution = localWorkspace.CurrentSolution; Assert.Same(initialSolution, serviceImpl.CommittedSolution); await localWorkspace.ChangeSolutionAsync(initialSolution.AddTestProject("proj", out var projectId).Solution); var updatedSolution = localWorkspace.CurrentSolution; await localService.GetUpdatesAsync(runningProjects: ImmutableArray<DebuggerContracts.RunningProjectInfo>.Empty, CancellationToken.None); switch (solutionAction) { case SolutionAction.None: Assert.Null(serviceImpl.PendingUpdatedSolution); Assert.Same(initialSolution, serviceImpl.CommittedSolution); break; case SolutionAction.Committed: Assert.Null(serviceImpl.PendingUpdatedSolution); Assert.Same(updatedSolution, serviceImpl.CommittedSolution); break; case SolutionAction.PendingUpdate: Assert.Same(updatedSolution, serviceImpl.PendingUpdatedSolution); Assert.Same(initialSolution, serviceImpl.CommittedSolution); if (commit.Value) { await localService.CommitUpdatesAsync(CancellationToken.None); Assert.Null(serviceImpl.PendingUpdatedSolution); Assert.Same(updatedSolution, serviceImpl.CommittedSolution); } else { await localService.DiscardUpdatesAsync(CancellationToken.None); Assert.Null(serviceImpl.PendingUpdatedSolution); Assert.Same(initialSolution, serviceImpl.CommittedSolution); } break; default: throw ExceptionUtilities.UnexpectedValue(solutionAction); } await localService.EndSessionAsync(CancellationToken.None); } [Fact] public async Task DefaultPdbMatchingSourceTextProvider() { var source1 = "class C1 { void M() { System.Console.WriteLine(\"a\"); } }"; var dir = Temp.CreateDirectory(); var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1, Encoding.UTF8); using var workspace = CreateEditorWorkspace(out var solution, out var service, out var languageService, out var sourceTextProvider); using var _1 = sourceTextProvider; var projectId = ProjectId.CreateNewId(); var documentId = DocumentId.CreateNewId(projectId); solution = solution. AddProject(projectId, "test", "test", LanguageNames.CSharp). WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithms.Default). AddMetadataReferences(projectId, TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)). AddDocument(DocumentInfo.Create( documentId, name: "test.cs", loader: new WorkspaceFileTextLoader(workspace.Services.SolutionServices, sourceFile.Path, Encoding.UTF8), filePath: sourceFile.Path)); Assert.True(workspace.SetCurrentSolution(_ => solution, WorkspaceChangeKind.SolutionAdded)); solution = workspace.CurrentSolution; var moduleId = EmitAndLoadLibraryToDebuggee(projectId, source1, sourceFilePath: sourceFile.Path); // hydrate document text and overwrite file content: var document1 = solution.GetRequiredDocument(documentId); _ = await document1.GetTextAsync(CancellationToken.None); File.WriteAllText(sourceFile.Path, "class C1 { void M() { System.Console.WriteLine(\"b\"); } }", Encoding.UTF8); await languageService.StartSessionAsync(CancellationToken.None); await languageService.EnterBreakStateAsync(CancellationToken.None); workspace.OnDocumentOpened(documentId, new TestSourceTextContainer() { Text = SourceText.From("class C1 { void M() { System.Console.WriteLine(\"c\"); } }", Encoding.UTF8, SourceHashAlgorithm.Sha1) }); await workspace.GetService<AsynchronousOperationListenerProvider>().GetWaiter(FeatureAttribute.Workspace).ExpeditedWaitAsync(); var (key, (documentState, version)) = sourceTextProvider.GetTestAccessor().GetDocumentsWithChangedLoaderByPath().Single(); Assert.Equal(sourceFile.Path, key); Assert.Equal(solution.SolutionStateContentVersion, version); Assert.Equal(source1, documentState.GetTextSynchronously(CancellationToken.None).ToString()); // check committed document status: var debuggingSession = service.GetTestAccessor().GetActiveDebuggingSessions().Single(); var (document, state) = await debuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(document1, CancellationToken.None); var text = await document.GetTextAsync(); Assert.Equal(CommittedSolution.DocumentState.MatchesBuildOutput, state); Assert.Equal(source1, document.GetTextSynchronously(CancellationToken.None).ToString()); await languageService.EndSessionAsync(CancellationToken.None); } }