File: EditAndContinue\EditAndContinueWorkspaceServiceTests.cs
Web Access
Project: src\src\Features\Test\Microsoft.CodeAnalysis.Features.UnitTests.csproj (Microsoft.CodeAnalysis.Features.UnitTests)
// 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.Immutable;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Contracts.EditAndContinue;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.Emit;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.UnitTests;
using Roslyn.Test.Utilities;
using Roslyn.Test.Utilities.TestGenerators;
using Roslyn.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.EditAndContinue.UnitTests;
 
using static ActiveStatementTestHelpers;
 
[UseExportProvider]
public sealed class EditAndContinueWorkspaceServiceTests : EditAndContinueWorkspaceTestBase
{
    [Theory, CombinatorialData]
    public async Task StartDebuggingSession_CapturingDocuments(bool captureAllDocuments)
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
 
        var encodingA = Encoding.BigEndianUnicode;
        var encodingB = Encoding.Unicode;
        var encodingC = Encoding.GetEncoding("SJIS");
        var encodingE = Encoding.UTF8;
 
        var sourceA1 = "class A {}";
        var sourceB1 = "class B { int F() => 1; }";
        var sourceB2 = "class B { int F() => 2; }";
        var sourceB3 = "class B { int F() => 3; }";
        var sourceC1 = "class C { const char L = 'ワ'; }";
        var sourceD1 = "dummy code";
        var sourceE1 = "class E { }";
        var sourceBytesA1 = encodingA.GetBytesWithPreamble(sourceA1);
        var sourceBytesB1 = encodingB.GetBytesWithPreamble(sourceB1);
        var sourceBytesC1 = encodingC.GetBytesWithPreamble(sourceC1);
        var sourceBytesD1 = Encoding.UTF8.GetBytesWithPreamble(sourceD1);
        var sourceBytesE1 = encodingE.GetBytesWithPreamble(sourceE1);
 
        var dir = Temp.CreateDirectory();
        var sourceFileA = dir.CreateFile("A.cs").WriteAllBytes(sourceBytesA1);
        var sourceFileB = dir.CreateFile("B.cs").WriteAllBytes(sourceBytesB1);
        var sourceFileC = dir.CreateFile("C.cs").WriteAllBytes(sourceBytesC1);
        var sourceFileD = dir.CreateFile("dummy").WriteAllBytes(sourceBytesD1);
        var sourceFileE = dir.CreateFile("E.cs").WriteAllBytes(sourceBytesE1);
        var sourceTreeA1 = SyntaxFactory.ParseSyntaxTree(SourceText.From(sourceBytesA1, sourceBytesA1.Length, encodingA, SourceHashAlgorithms.Default), TestOptions.Regular, sourceFileA.Path);
        var sourceTreeB1 = SyntaxFactory.ParseSyntaxTree(SourceText.From(sourceBytesB1, sourceBytesB1.Length, encodingB, SourceHashAlgorithms.Default), TestOptions.Regular, sourceFileB.Path);
        var sourceTreeC1 = SyntaxFactory.ParseSyntaxTree(SourceText.From(sourceBytesC1, sourceBytesC1.Length, encodingC, SourceHashAlgorithm.Sha1), TestOptions.Regular, sourceFileC.Path);
 
        // E is not included in the compilation:
        var compilation = CSharpTestBase.CreateCompilation([sourceTreeA1, sourceTreeB1, sourceTreeC1], options: TestOptions.DebugDll, targetFramework: DefaultTargetFramework, assemblyName: "P");
        EmitLibrary(compilation);
 
        // change content of B on disk:
        sourceFileB.WriteAllText(sourceB2, encodingB);
 
        // prepare workspace as if it was loaded from project files:
        using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]);
 
        solution = solution
            .AddTestProject("P", LanguageNames.CSharp, out var projectPId).Solution
            .WithProjectChecksumAlgorithm(projectPId, SourceHashAlgorithm.Sha1);
 
        var documentIdA = DocumentId.CreateNewId(projectPId, debugName: "A");
        solution = solution.AddDocument(DocumentInfo.Create(
            id: documentIdA,
            name: "A",
            loader: new WorkspaceFileTextLoader(solution.Services, sourceFileA.Path, encodingA),
            filePath: sourceFileA.Path));
 
        var documentIdB = DocumentId.CreateNewId(projectPId, debugName: "B");
        solution = solution.AddDocument(DocumentInfo.Create(
            id: documentIdB,
            name: "B",
            loader: new WorkspaceFileTextLoader(solution.Services, sourceFileB.Path, encodingB),
            filePath: sourceFileB.Path));
 
        var documentIdC = DocumentId.CreateNewId(projectPId, debugName: "C");
        solution = solution.AddDocument(DocumentInfo.Create(
            id: documentIdC,
            name: "C",
            loader: new WorkspaceFileTextLoader(solution.Services, sourceFileC.Path, encodingC),
            filePath: sourceFileC.Path));
 
        var documentIdE = DocumentId.CreateNewId(projectPId, debugName: "E");
        solution = solution.AddDocument(DocumentInfo.Create(
            id: documentIdE,
            name: "E",
            loader: new WorkspaceFileTextLoader(solution.Services, sourceFileE.Path, encodingE),
            filePath: sourceFileE.Path));
 
        // check that we are testing documents whose hash algorithm does not match the PDB (but the hash itself does):
        Assert.Equal(SourceHashAlgorithm.Sha1, solution.GetDocument(documentIdA).GetTextSynchronously(default).ChecksumAlgorithm);
        Assert.Equal(SourceHashAlgorithm.Sha1, solution.GetDocument(documentIdB).GetTextSynchronously(default).ChecksumAlgorithm);
        Assert.Equal(SourceHashAlgorithm.Sha1, solution.GetDocument(documentIdC).GetTextSynchronously(default).ChecksumAlgorithm);
        Assert.Equal(SourceHashAlgorithm.Sha1, solution.GetDocument(documentIdE).GetTextSynchronously(default).ChecksumAlgorithm);
 
        // design-time-only document with and without absolute path:
        solution = solution.
            AddDocument(CreateDesignTimeOnlyDocument(projectPId, name: "dt1.cs", path: Path.Combine(dir.Path, "dt1.cs"))).
            AddDocument(CreateDesignTimeOnlyDocument(projectPId, name: "dt2.cs", path: "dt2.cs"));
 
        // project that does not support EnC - the contents of documents in this project shouldn't be loaded:
        var projectQ = solution.AddTestProject("Q", NoCompilationConstants.LanguageName);
        solution = projectQ.Solution;
 
        solution = solution.AddDocument(DocumentInfo.Create(
            id: DocumentId.CreateNewId(projectQ.Id, debugName: "D"),
            name: "D",
            loader: new FailingTextLoader(),
            filePath: sourceFileD.Path));
 
        var captureMatchingDocuments = captureAllDocuments
            ? ImmutableArray<DocumentId>.Empty
            : (from project in solution.Projects from documentId in project.DocumentIds select documentId).ToImmutableArray();
 
        var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, NullPdbMatchingSourceTextProvider.Instance, captureMatchingDocuments, captureAllDocuments, reportDiagnostics: true, CancellationToken.None);
        var debuggingSession = service.GetTestAccessor().GetDebuggingSession(sessionId);
 
        var matchingDocuments = debuggingSession.LastCommittedSolution.Test_GetDocumentStates();
        AssertEx.Equal(
        [
            "(A, MatchesBuildOutput)",
            "(C, MatchesBuildOutput)"
        ], matchingDocuments.Select(e => (solution.GetDocument(e.id).Name, e.state)).OrderBy(e => e.Name).Select(e => e.ToString()));
 
        // change content of B on disk again:
        sourceFileB.WriteAllText(sourceB3, encodingB);
        solution = solution.WithDocumentTextLoader(documentIdB, new WorkspaceFileTextLoader(solution.Services, sourceFileB.Path, encodingB), PreservationMode.PreserveValue);
 
        EnterBreakState(debuggingSession);
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"P.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFileB.Path)}"], InspectDiagnostics(emitDiagnostics));
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ProjectNotBuilt()
    {
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }");
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.Empty);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // no changes:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // change the source:
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
        var document2 = solution.GetDocument(document1.Id);
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // changes in the project are ignored:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task DifferentDocumentWithSameContent()
    {
        var source = "class C1 { void M1() { System.Console.WriteLine(1); } }";
        var moduleFile = Temp.CreateFile().WriteAllBytes(TestResources.Basic.Members);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source);
 
        solution = solution.WithProjectOutputFilePath(document.Project.Id, moduleFile.Path);
        _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // update the document
        var document1 = solution.GetDocument(document.Id);
        solution = solution.WithDocumentText(document.Id, CreateText(source));
        var document2 = solution.GetDocument(document.Id);
 
        Assert.Equal(document1.Id, document2.Id);
        Assert.NotSame(document1, document2);
 
        var diagnostics2 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics2);
 
        // validate solution update status and emit - changes made during run mode are ignored:
        var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1"
        ], _telemetryLog);
    }
 
    [Theory, CombinatorialData]
    public async Task ProjectThatDoesNotSupportEnC(bool breakMode)
    {
        using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]);
        var project = solution.AddProject("dummy_proj", "dummy_proj", NoCompilationConstants.LanguageName);
        var document = project.AddDocument("test", "dummy1");
        solution = document.Project.Solution;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // no changes:
        var document1 = solution.Projects.Single().Documents.Single();
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // change the source:
        solution = solution.WithDocumentText(document1.Id, CreateText("dummy2"));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        var document2 = solution.GetDocument(document1.Id);
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
    }
 
    [Fact]
    public async Task ProjectWithoutEffectiveGeneratedFilesOutputDirectory()
    {
        var sourceV1 = """
            /* GENERATE: class G { int F(int x) => 1; } */
            
            class C { int Y => 1; }
            """;
 
        var sourceV2 = """
            /* GENERATE: class G { int F() => 1; } */
 
            class C { int Y => 2; }
            """;
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, sourceV1, generator: generator);
        solution = solution.WithProjectCompilationOutputInfo(document.Project.Id, new CompilationOutputInfo(assemblyPath: null, generatedFilesOutputDirectory: null));
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        // change the source:
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        var generatedDocument = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single();
 
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics1);
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task DesignTimeOnlyDocument()
    {
        var moduleFile = Temp.CreateFile().WriteAllBytes(TestResources.Basic.Members);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }");
 
        var documentInfo = CreateDesignTimeOnlyDocument(document1.Project.Id);
        solution = solution.WithProjectOutputFilePath(document1.Project.Id, moduleFile.Path).AddDocument(documentInfo);
 
        _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // update a design-time-only source file:
        solution = solution.WithDocumentText(documentInfo.Id, CreateText("class UpdatedC2 {}"));
        var document2 = solution.GetDocument(documentInfo.Id);
 
        // no updates:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // validate solution update status and emit - changes made in design-time-only documents are ignored:
        var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1"
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task DesignTimeOnlyDocument_Dynamic()
    {
        using var _ = CreateWorkspace(out var solution, out var service);
 
        (solution, var document) = AddDefaultTestProject(solution, "class C {}");
 
        var sourceText = CreateText("class D {}");
        var documentInfo = DocumentInfo.Create(
            DocumentId.CreateNewId(document.Project.Id),
            name: "design-time-only.cs",
            loader: TextLoader.From(TextAndVersion.Create(sourceText, VersionStamp.Create(), "design-time-only.cs")),
            filePath: "design-time-only.cs",
            isGenerated: false)
            .WithDesignTimeOnly(true);
 
        solution = solution.AddDocument(documentInfo);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        // change the source:
        var document1 = solution.GetDocument(documentInfo.Id);
        solution = solution.WithDocumentText(document1.Id, CreateText("class E {}"));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
    }
 
    [Theory, CombinatorialData]
    public async Task DesignTimeOnlyDocument_Wpf([CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic)] string language, bool delayLoad, bool open, bool designTimeOnlyAddedAfterSessionStarts)
    {
        var source = "class A { }";
        var sourceDesignTimeOnly = (language == LanguageNames.CSharp) ? "class B { }" : "Class C : End Class";
        var sourceDesignTimeOnly2 = (language == LanguageNames.CSharp) ? "class B2 { }" : "Class C2 : End Class";
 
        var dir = Temp.CreateDirectory();
 
        var extension = (language == LanguageNames.CSharp) ? ".cs" : ".vb";
 
        var sourceFileName = "a" + extension;
        var sourceFilePath = dir.CreateFile(sourceFileName).WriteAllText(source, Encoding.UTF8).Path;
 
        var designTimeOnlyFileName = "b.g.i" + extension;
        var designTimeOnlyFilePath = Path.Combine(dir.Path, designTimeOnlyFileName);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // The workspace starts with
        // [added == false] a version of the source that's not updated with the output of single file generator (or design-time build):
        // [added == true] without the output of single file generator (design-time build has not completed)
 
        solution = solution.
            AddTestProject("test", language, out var projectId).
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
            AddTestDocument(source, path: sourceFilePath, out var documentId).Project.Solution;
 
        var designTimeOnlyDocumentId = DocumentId.CreateNewId(projectId);
 
        if (!designTimeOnlyAddedAfterSessionStarts)
        {
            solution = solution.AddDocument(designTimeOnlyDocumentId, designTimeOnlyFileName, SourceText.From(sourceDesignTimeOnly, Encoding.UTF8), filePath: designTimeOnlyFilePath);
        }
 
        // only compile actual source document, not design-time-only document:
        var moduleId = EmitLibrary(source, sourceFilePath: sourceFilePath);
 
        if (!delayLoad)
        {
            LoadLibraryToDebuggee(moduleId);
        }
 
        // make sure renames are not supported:
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline");
 
        var openDocumentIds = open ? ImmutableArray.Create(designTimeOnlyDocumentId) : ImmutableArray<DocumentId>.Empty;
        var sessionId = await service.StartDebuggingSessionAsync(solution, _debuggerService, NullPdbMatchingSourceTextProvider.Instance, captureMatchingDocuments: openDocumentIds, captureAllMatchingDocuments: false, reportDiagnostics: true, CancellationToken.None);
        var debuggingSession = service.GetTestAccessor().GetDebuggingSession(sessionId);
 
        if (designTimeOnlyAddedAfterSessionStarts)
        {
            solution = solution.AddDocument(designTimeOnlyDocumentId, designTimeOnlyFileName, SourceText.From(sourceDesignTimeOnly, Encoding.UTF8), filePath: designTimeOnlyFilePath);
        }
 
        var activeLineSpan = new LinePositionSpan(new(0, 0), new(0, 1));
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1),
                designTimeOnlyFilePath,
                activeLineSpan.ToSourceSpan(),
                ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.MethodUpToDate));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        // change the source (rude edit):
        solution = solution.WithDocumentText(designTimeOnlyDocumentId, CreateText(sourceDesignTimeOnly2));
 
        var designTimeOnlyDocument2 = solution.GetDocument(designTimeOnlyDocumentId);
 
        Assert.False(designTimeOnlyDocument2.State.SupportsEditAndContinue());
        Assert.True(designTimeOnlyDocument2.Project.SupportsEditAndContinue());
 
        var activeStatementMap = await debuggingSession.EditSession.BaseActiveStatements.GetValueAsync(CancellationToken.None);
        Assert.NotEmpty(activeStatementMap.DocumentPathMap);
 
        // Active statements in design-time documents should be left unchanged.
        var asSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(designTimeOnlyDocumentId), CancellationToken.None);
        Assert.Empty(asSpans.Single());
 
        // no Rude Edits reported:
        Assert.Empty(await service.GetDocumentDiagnosticsAsync(designTimeOnlyDocument2, s_noActiveSpans, CancellationToken.None));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(emitDiagnostics);
 
        if (delayLoad)
        {
            LoadLibraryToDebuggee(moduleId);
 
            // validate solution update status and emit:
            (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
            Assert.Equal(ModuleUpdateStatus.None, updates.Status);
            Assert.Empty(emitDiagnostics);
        }
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory, CombinatorialData]
    public async Task ErrorReadingModuleFile(bool breakMode)
    {
        // module file is empty, which will cause a read error:
        var moduleFile = Temp.CreateFile();
 
        string expectedErrorMessage = null;
        try
        {
            using var stream = File.OpenRead(moduleFile.Path);
            using var peReader = new PEReader(stream);
            _ = peReader.GetMetadataReader();
        }
        catch (Exception e)
        {
            expectedErrorMessage = e.Message;
        }
 
        var source1 = "class C { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class C { void M() { System.Console.WriteLine(2); } }";
 
        using var _w = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, source1);
 
        _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // change the source:
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
        var document2 = solution.GetDocument(document1.Id);
 
        // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, moduleFile.Path, expectedErrorMessage)}"], InspectDiagnostics(emitDiagnostics));
 
        // correct the error:
        EmitLibrary(source2);
 
        var (updates2, emitDiagnostics2) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates2.Status);
        Assert.Empty(emitDiagnostics2);
 
        CommitSolutionUpdate(debuggingSession);
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
        }
 
        EndDebuggingSession(debuggingSession);
 
        if (breakMode)
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=3",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges={6A6F7270-0000-4000-8000-000000000000}",
                "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC1001"
            ], _telemetryLog);
        }
        else
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=1",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges={6A6F7270-0000-4000-8000-000000000000}",
                "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC1001"
            ], _telemetryLog);
        }
    }
 
    [Fact]
    public async Task ErrorReadingPdbFile()
    {
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var document1 = AddEmptyTestProject(solution).
            AddDocument("a.cs", CreateText(source1), filePath: sourceFile.Path);
 
        var project = document1.Project;
        solution = project.Solution;
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path);
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId)
        {
            OpenPdbStreamImpl = () =>
            {
                throw new IOException("Error");
            }
        };
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
        EnterBreakState(debuggingSession);
 
        // change the source:
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
        var document2 = solution.GetDocument(document1.Id);
 
        // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // an error occurred so we need to call update to determine whether we have changes to apply or not:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=1|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1"
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task ErrorReadingSourceFile()
    {
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var document1 = solution.
            AddTestProject("test").
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)).
            AddDocument("a.cs", SourceText.From(source1, Encoding.UTF8, SourceHashAlgorithm.Sha1), filePath: sourceFile.Path);
 
        var project = document1.Project;
        solution = project.Solution;
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path, checksumAlgorithm: SourceHashAlgorithms.Default);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
        EnterBreakState(debuggingSession);
 
        // change the source:
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
        var document2 = solution.GetDocument(document1.Id);
 
        using var fileLock = File.Open(sourceFile.Path, FileMode.Open, FileAccess.Read, FileShare.None);
 
        // error not reported here since it might be intermittent and will be reported if the issue persist when applying the update:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // an error occurred so we need to call update to determine whether we have changes to apply or not:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1006: {string.Format(FeaturesResources.UnableToReadSourceFileOrPdb, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
 
        fileLock.Dispose();
 
        // try apply changes again:
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        Assert.NotEmpty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges="
        ], _telemetryLog);
    }
 
    [Theory, CombinatorialData]
    public async Task FileAdded(bool breakMode)
    {
        var sourceA = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var sourceB = "class C2 {}";
 
        var sourceFileA = Temp.CreateFile().WriteAllText(sourceA, Encoding.UTF8);
        var sourceFileB = Temp.CreateFile().WriteAllText(sourceB, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var documentA = solution.
            AddTestProject("test").
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
            AddDocument("test.cs", CreateText(sourceA), filePath: sourceFileA.Path);
 
        solution = documentA.Project.Solution;
 
        // Source B will be added while debugging.
        EmitAndLoadLibraryToDebuggee(sourceA, sourceFilePath: sourceFileA.Path);
 
        var project = documentA.Project;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // add a source file:
        var documentB = project.AddTestDocument(sourceB, path: sourceFileB.Path);
        solution = documentB.Project.Solution;
        documentB = solution.GetRequiredDocument(documentB.Id);
 
        var diagnostics2 = await service.GetDocumentDiagnosticsAsync(documentB, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics2);
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        debuggingSession.DiscardSolutionUpdate();
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
        }
 
        EndDebuggingSession(debuggingSession);
 
        if (breakMode)
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges="
            ], _telemetryLog);
        }
        else
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges="
            ], _telemetryLog);
        }
    }
 
    /// <summary>
    /// <code>
    ///                         F5   build
    ///                              complete
    ///                         │    │
    /// Workspace    ═════0═════╪════╪══════════1═══
    ///                   ▲     │               ▲ src file watcher
    ///                   │     │               │
    /// dll/pdb      ═0═══╪═════╪════1══════════╪═══
    ///                   │     │    ▲          │
    ///               ┌───┘     │    │          │
    ///               │      ┌──┼────┴──────────┘
    /// Source file  ═0══════1══╪═══════════════════
    ///
    /// Committed    ═══════════╪════0══════════1═══
    /// solution
    /// </code>
    /// </summary>
    [Theory, CombinatorialData]
    public async Task ModuleDisallowsEditAndContinue_NoChanges(bool breakMode)
    {
        var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }";
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs");
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var project = solution.
            AddProject("test", "test", LanguageNames.CSharp).
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40));
 
        solution = project.Solution;
 
        // compile with source1:
        var moduleId = EmitLibrary(source1, sourceFilePath: sourceFile.Path);
        LoadLibraryToDebuggee(moduleId, new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.NotAllowedForRuntime, "*message*"));
 
        // update the file with source1 before session starts:
        sourceFile.WriteAllText(source1, Encoding.UTF8);
 
        // source0 is loaded to workspace before session starts:
        var document0 = project.AddTestDocument(source0, path: sourceFile.Path);
        solution = document0.Project.Solution;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // workspace is updated to new version after build completed and the session started:
        solution = solution.WithDocumentText(document0.Id, CreateText(source1));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
        }
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ModuleDisallowsEditAndContinue_SourceGenerator_NoChanges()
    {
        var moduleId = Guid.NewGuid();
 
        var source1 = @"/* GENERATE class C1 { void M() { System.Console.WriteLine(1); } } */";
        var source2 = source1;
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source1, generator);
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId);
 
        LoadLibraryToDebuggee(moduleId, new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.NotAllowedForRuntime, "*message*"));
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // update document with the same content:
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ModuleDisallowsEditAndContinue()
    {
        var moduleId = Guid.NewGuid();
 
        var source1 = @"
class C1
{
  void M()
  {
    System.Console.WriteLine(1);
    System.Console.WriteLine(2);
    System.Console.WriteLine(3);
  }
}";
        var source2 = @"
class C1
{
  void M()
  {
    System.Console.WriteLine(9);
    System.Console.WriteLine();
    System.Console.WriteLine(30);
  }
}";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source1, generator);
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId);
 
        LoadLibraryToDebuggee(moduleId, new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.NotAllowedForRuntime, "*message*"));
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source:
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
        var document2 = solution.GetDocument(document1.Id);
 
        // We do not report module diagnostics until emit.
        // This is to make the analysis deterministic (not dependent on the current state of the debuggee).
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics1);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"{document2.FilePath}: (5,0)-(5,32): Error ENC2016: {string.Format(FeaturesResources.EditAndContinueDisallowedByProject, document2.Project.Name, "*message*")}"], InspectDiagnostics(emitDiagnostics));
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
            "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC2016"
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task Encodings()
    {
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
 
        var source1 = "class C1 { void M() { System.Console.WriteLine(\"ã\"); } }";
 
        var encoding = Encoding.GetEncoding(1252);
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1, encoding);
 
        using var workspace = CreateWorkspace(out var solution, out var service);
 
        var projectId = ProjectId.CreateNewId();
        var documentId = DocumentId.CreateNewId(projectId);
 
        solution = solution.
            AddProject(projectId, "test", "test", LanguageNames.CSharp).
            WithProjectChecksumAlgorithm(projectId, SourceHashAlgorithm.Sha1).
            AddMetadataReferences(projectId, TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
            AddDocument(documentId, "test.cs", SourceText.From(source1, encoding, SourceHashAlgorithm.Sha1), filePath: sourceFile.Path);
 
        // use different checksum alg to trigger PdbMatchingSourceTextProvider call:
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path, encoding: encoding, checksumAlgorithm: SourceHashAlgorithm.Sha256);
 
        var sourceTextProviderCalled = false;
        var sourceTextProvider = new MockPdbMatchingSourceTextProvider()
        {
            TryGetMatchingSourceTextImpl = (filePath, requiredChecksum, checksumAlgorithm) =>
            {
                sourceTextProviderCalled = true;
 
                // fall back to reading the file content:
                return null;
            }
        };
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None, sourceTextProvider);
 
        EnterBreakState(debuggingSession);
 
        var (document, state) = await debuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(documentId, currentDocument: null, CancellationToken.None);
        var text = await document.GetTextAsync();
        Assert.Same(encoding, text.Encoding);
        Assert.Equal(CommittedSolution.DocumentState.MatchesBuildOutput, state);
 
        Assert.True(sourceTextProviderCalled);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory, CombinatorialData]
    public async Task RudeEdits(bool breakMode)
    {
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class C1 { void M<T>() { System.Console.WriteLine(1); } }";
 
        var moduleId = Guid.NewGuid();
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source1);
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // change the source (rude edit):
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
        var document2 = solution.GetDocument(document1.Id);
 
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)],
            diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
            EndDebuggingSession(debuggingSession);
        }
        else
        {
            EndDebuggingSession(debuggingSession);
        }
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        if (breakMode)
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
                "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
            ], _telemetryLog);
        }
        else
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=",
                "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8910|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
            ], _telemetryLog);
        }
    }
 
    [Fact]
    public async Task DeferredApplyChangeWithActiveStatementRudeEdits()
    {
        var source1 = "class C { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class C { void M() { System.Console.WriteLine(2); } }";
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source1);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        var activeLineSpan1 = CreateText(source1).Lines.GetLinePositionSpan(GetSpan(source1, "System.Console.WriteLine(1);"));
        var activeLineSpan2 = CreateText(source2).Lines.GetLinePositionSpan(GetSpan(source2, "System.Console.WriteLine(2);"));
 
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1),
                document.FilePath,
                activeLineSpan1.ToSourceSpan(),
                ActiveStatementFlags.NonLeafFrame | ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.PartiallyExecuted));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        // change the source (rude edit):
        solution = solution.WithDocumentText(document.Id, CreateText(source2));
        var document2 = solution.GetDocument(document.Id);
 
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0001: " + string.Format(FeaturesResources.Updating_an_active_statement_requires_restarting_the_application)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        // exit break state without applying the change:
        ExitBreakState(debuggingSession);
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // enter break state again (with the same active statements)
 
        EnterBreakState(debuggingSession, activeStatements);
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0001: " + string.Format(FeaturesResources.Updating_an_active_statement_requires_restarting_the_application)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        // exit break state without applying the change:
        ExitBreakState(debuggingSession);
 
        // apply the change:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        Assert.NotEmpty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        CommitSolutionUpdate(debuggingSession);
 
        EnterBreakState(debuggingSession, activeStatements);
 
        // no rude edits - changes have been applied
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        ExitBreakState(debuggingSession);
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task RudeEdits_SourceGenerators()
    {
        var sourceV1 = @"
/* GENERATE: class G { int X1() => 1; } */
 
class C { int Y => 1; }
";
        var sourceV2 = @"
/* GENERATE: class G { int X1<T>() => 1; } */
 
class C { int Y => 2; }
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, sourceV1, generator: generator);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        // change the source:
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        var generatedDocument = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync()).Single();
 
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(generatedDocument, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)],
            diagnostics1.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory, CombinatorialData]
    public async Task RudeEdits_DocumentOutOfSync(bool breakMode)
    {
        var source0 = "class C1 { void M() { System.Console.WriteLine(0); } }";
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class C1 { void M<T>() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs");
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var project = AddEmptyTestProject(solution);
        solution = project.Solution;
 
        // compile with source0:
        var moduleId = EmitAndLoadLibraryToDebuggee(source0, sourceFilePath: sourceFile.Path);
 
        // update the file with source1 before session starts:
        sourceFile.WriteAllText(source1, Encoding.UTF8);
 
        // source1 is reflected in workspace before session starts:
        var document1 = project.AddTestDocument(source1, path: sourceFile.Path);
        solution = document1.Project.Solution;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // change the source (rude edit):
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
        var document2 = solution.GetDocument(document1.Id);
 
        // no Rude Edits, since the document is out-of-sync
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // since the document is out-of-sync we need to call update to determine whether we have changes to apply or not:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
 
        // update the file to match the build:
        sourceFile.WriteAllText(source0, Encoding.UTF8);
 
        // we do not reload the content of out-of-sync file for analyzer query:
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // debugger query will trigger reload of out-of-sync file content:
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        // now we see the rude edit:
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(
            [
                "ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)
            ],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
            EndDebuggingSession(debuggingSession);
        }
        else
        {
            EndDebuggingSession(debuggingSession);
        }
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        if (breakMode)
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=2",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
                "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
            ], _telemetryLog);
        }
        else
        {
            AssertEx.Equal(
            [
                "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount=0",
                "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=True|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=1|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges=",
                "Debugging_EncSession_EditSession_RudeEdit: SessionId=1|EditSessionId=2|RudeEditKind=110|RudeEditSyntaxKind=8875|RudeEditBlocking=True|RudeEditProjectId={6A6F7270-0000-4000-8000-000000000000}"
            ], _telemetryLog);
        }
    }
 
    [Fact]
    public async Task RudeEdits_DocumentWithoutSequencePoints()
    {
        var source1 = "abstract class C { public abstract void M(); }";
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build):
        var document1 = solution.
            AddTestProject("test").
            AddDocument("test.cs", CreateText(source1), filePath: sourceFile.Path);
 
        var project = document1.Project;
        solution = project.Solution;
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path);
 
        // do not initialize the document state - we will detect the state based on the PDB content.
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        EnterBreakState(debuggingSession);
 
        // change the source (rude edit since the base document content matches the PDB checksum, so the document is not out-of-sync):
        solution = solution.WithDocumentText(document1.Id, CreateText("abstract class C { public abstract void M(); public abstract void N(); }"));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // Rude Edits reported:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(
            ["ENC0023: " + string.Format(FeaturesResources.Adding_an_abstract_0_or_overriding_an_inherited_0_requires_restarting_the_application, FeaturesResources.method)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task RudeEdits_DelayLoadedModule()
    {
        var source1 = "class C { public void M() { } }";
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("a.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build):
        var document1 = solution.
            AddTestProject("test").
            AddDocument("test.cs", CreateText(source1), filePath: sourceFile.Path);
 
        var project = document1.Project;
        solution = project.Solution;
 
        var moduleId = EmitLibrary(source1, sourceFilePath: sourceFile.Path);
 
        // do not initialize the document state - we will detect the state based on the PDB content.
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        EnterBreakState(debuggingSession);
 
        // change the source (rude edit) before the library is loaded:
        solution = solution.WithDocumentText(document1.Id, CreateText("class C { public void M<T>() { } }"));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // Rude Edits reported:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(
            ["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        // load library to the debuggee:
        LoadLibraryToDebuggee(moduleId);
 
        // Rude Edits still reported:
        diagnostics = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(
            ["ENC0110: " + string.Format(FeaturesResources.Changing_the_signature_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.method)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task SyntaxError()
    {
        var moduleId = Guid.NewGuid();
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }");
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (compilation error):
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { "));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // compilation errors are not reported via EnC diagnostic analyzer:
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics1);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=True|HadRudeEdits=False|HadValidChanges=False|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges="
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task SemanticError()
    {
        var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, sourceV1);
 
        var moduleId = EmitAndLoadLibraryToDebuggee(sourceV1);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (compilation error):
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { int i = 0L; System.Console.WriteLine(i); } }"));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // compilation errors are not reported via EnC diagnostic analyzer:
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics1);
 
        // The EnC analyzer does not check for and block on all semantic errors as they are already reported by diagnostic analyzer.
        // Blocking update on semantic errors would be possible, but the status check is only an optimization to avoid emitting.
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
        Assert.Empty(updates.Updates);
 
        // TODO: https://github.com/dotnet/roslyn/issues/36061
        // Semantic errors should not be reported in emit diagnostics.
 
        AssertEx.Equal([$"{document2.FilePath}: (0,30)-(0,32): Error CS0266: {string.Format(CSharpResources.ERR_NoImplicitConvCast, "long", "int")}"], InspectDiagnostics(emitDiagnostics));
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
            "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=CS0266"
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task HasChanges()
    {
        using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]);
 
        var pathA = Path.Combine(TempRoot.Root, "A.cs");
        var pathB = Path.Combine(TempRoot.Root, "B.cs");
        var pathC = Path.Combine(TempRoot.Root, "C.cs");
        var pathD = Path.Combine(TempRoot.Root, "D.cs");
        var pathX = Path.Combine(TempRoot.Root, "X");
        var pathY = Path.Combine(TempRoot.Root, "Y");
        var pathCommon = Path.Combine(TempRoot.Root, "Common.cs");
 
        solution = solution.
            AddTestProject("A").
            AddDocument("A.cs", "class Program { void Main() { System.Console.WriteLine(1); } }", filePath: pathA).Project.Solution.
            AddTestProject("B").
            AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project.
            AddDocument("B.cs", "class B {}", filePath: pathB).Project.Solution.
            AddTestProject("C").
            AddDocument("Common.cs", "class Common {}", filePath: pathCommon).Project.
            AddDocument("C.cs", "class C {}", filePath: pathC).Project.Solution;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        // change C.cs to have a compilation error:
        var oldSolution = solution;
        var projectC = solution.GetProjectsByName("C").Single();
        var documentC = projectC.Documents.Single(d => d.Name == "C.cs");
        solution = solution.WithDocumentText(documentC.Id, CreateText("class C { void M() { "));
 
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
 
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathCommon, CancellationToken.None));
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathB, CancellationToken.None));
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: pathC, CancellationToken.None));
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, sourceFilePath: "NonexistentFile.cs", CancellationToken.None));
 
        // All projects must have no errors.
        var (updates, _) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
 
        // add a project:
 
        oldSolution = solution;
        var projectD = solution.AddTestProject("D");
        solution = projectD.Solution;
 
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
 
        // remove a project:
        Assert.True(await EditSession.HasChangesAsync(solution, solution.RemoveProject(projectD.Id), CancellationToken.None));
 
        // add a project that doesn't support EnC:
 
        oldSolution = solution;
        var projectE = solution.AddTestProject("E", language: NoCompilationConstants.LanguageName);
        solution = projectE.Solution;
 
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
 
        // remove a project that doesn't support EnC:
        Assert.False(await EditSession.HasChangesAsync(solution, oldSolution, CancellationToken.None));
 
        EndDebuggingSession(debuggingSession);
    }
 
    public enum DocumentKind
    {
        Source,
        Additional,
        AnalyzerConfig,
    }
 
    [Theory, CombinatorialData]
    public async Task HasChanges_Documents(DocumentKind documentKind)
    {
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var pathX = Path.Combine(TempRoot.Root, "X.cs");
        var pathA = Path.Combine(TempRoot.Root, "A.cs");
 
        var generatorExecutionCount = 0;
        var generator = new TestSourceGenerator()
        {
            ExecuteImpl = context =>
            {
                switch (documentKind)
                {
                    case DocumentKind.Source:
                        context.AddSource("Generated.cs", context.Compilation.SyntaxTrees.SingleOrDefault(t => t.FilePath.EndsWith("X.cs"))?.ToString() ?? "none");
                        break;
 
                    case DocumentKind.Additional:
                        context.AddSource("Generated.cs", context.AdditionalFiles.FirstOrDefault()?.GetText().ToString() ?? "none");
                        break;
 
                    case DocumentKind.AnalyzerConfig:
                        var syntaxTree = context.Compilation.SyntaxTrees.Single(t => t.FilePath.EndsWith("A.cs"));
                        var content = context.AnalyzerConfigOptions.GetOptions(syntaxTree).TryGetValue("x", out var optionValue) ? optionValue.ToString() : "none";
 
                        context.AddSource("Generated.cs", content);
                        break;
                }
 
                generatorExecutionCount++;
            }
        };
 
        var project = solution
            .AddTestProject("A")
            .AddTestDocument(source: "", path: pathA)
            .Project;
 
        var projectId = project.Id;
        solution = project.Solution.AddAnalyzerReference(projectId, new TestGeneratorReference(generator));
        project = solution.GetRequiredProject(projectId);
        var generatedDocument = (await project.GetSourceGeneratedDocumentsAsync()).Single();
        var generatedDocumentId = generatedDocument.Id;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        Assert.Equal(1, generatorExecutionCount);
        var changedOrAddedDocuments = new PooledObjects.ArrayBuilder<Document>();
 
        //
        // Add document
        //
 
        generatorExecutionCount = 0;
        var oldSolution = solution;
        var documentId = DocumentId.CreateNewId(projectId);
        solution = documentKind switch
        {
            DocumentKind.Source => solution.AddDocument(documentId, "X", CreateText("xxx"), filePath: pathX),
            DocumentKind.Additional => solution.AddAdditionalDocument(documentId, "X", CreateText("xxx"), filePath: pathX),
            DocumentKind.AnalyzerConfig => solution.AddAnalyzerConfigDocument(documentId, "X", GetAnalyzerConfigText([("x", "1")]), filePath: pathX),
            _ => throw ExceptionUtilities.Unreachable(),
        };
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, pathX, CancellationToken.None));
 
        // always returns false for source generated files:
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, generatedDocument.FilePath, CancellationToken.None));
 
        // generator is not executed since we already know the solution changed without inspecting generated files:
        Assert.Equal(0, generatorExecutionCount);
 
        AssertEx.Equal([generatedDocumentId],
            await EditSession.GetChangedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None));
 
        var diagnostics = new ArrayBuilder<ProjectDiagnostics>();
        await EditSession.PopulateChangedAndAddedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None);
        Assert.Empty(diagnostics);
        AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id));
 
        Assert.Equal(1, generatorExecutionCount);
 
        //
        // Update document to a different document snapshot but the same content
        //
 
        generatorExecutionCount = 0;
        oldSolution = solution;
 
        solution = documentKind switch
        {
            DocumentKind.Source => solution.WithDocumentText(documentId, CreateText("xxx")),
            DocumentKind.Additional => solution.WithAdditionalDocumentText(documentId, CreateText("xxx")),
            DocumentKind.AnalyzerConfig => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "1")])),
            _ => throw ExceptionUtilities.Unreachable(),
        };
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
        Assert.False(await EditSession.HasChangesAsync(oldSolution, solution, pathX, CancellationToken.None));
 
        Assert.Equal(0, generatorExecutionCount);
 
        // source generator infrastructure compares content and reuses state if it matches (SourceGeneratedDocumentState.WithUpdatedGeneratedContent):
        AssertEx.Equal(documentKind == DocumentKind.Source ? new[] { documentId } : [],
            await EditSession.GetChangedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None));
 
        await EditSession.PopulateChangedAndAddedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None);
        Assert.Empty(diagnostics);
        Assert.Empty(changedOrAddedDocuments);
 
        Assert.Equal(1, generatorExecutionCount);
 
        //
        // Update document content
        //
 
        generatorExecutionCount = 0;
        oldSolution = solution;
        solution = documentKind switch
        {
            DocumentKind.Source => solution.WithDocumentText(documentId, CreateText("xxx-changed")),
            DocumentKind.Additional => solution.WithAdditionalDocumentText(documentId, CreateText("xxx-changed")),
            DocumentKind.AnalyzerConfig => solution.WithAnalyzerConfigDocumentText(documentId, GetAnalyzerConfigText([("x", "2")])),
            _ => throw ExceptionUtilities.Unreachable(),
        };
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, pathX, CancellationToken.None));
 
        AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId],
            await EditSession.GetChangedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None));
 
        await EditSession.PopulateChangedAndAddedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None);
        Assert.Empty(diagnostics);
        AssertEx.Equal(documentKind == DocumentKind.Source ? [documentId, generatedDocumentId] : [generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id));
 
        Assert.Equal(1, generatorExecutionCount);
 
        //
        // Remove document
        //
 
        generatorExecutionCount = 0;
        oldSolution = solution;
        solution = documentKind switch
        {
            DocumentKind.Source => solution.RemoveDocument(documentId),
            DocumentKind.Additional => solution.RemoveAdditionalDocument(documentId),
            DocumentKind.AnalyzerConfig => solution.RemoveAnalyzerConfigDocument(documentId),
            _ => throw ExceptionUtilities.Unreachable(),
        };
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, pathX, CancellationToken.None));
 
        Assert.Equal(0, generatorExecutionCount);
 
        AssertEx.Equal([generatedDocumentId],
            await EditSession.GetChangedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None));
 
        await EditSession.PopulateChangedAndAddedDocumentsAsync(oldSolution.GetProject(projectId), solution.GetProject(projectId), changedOrAddedDocuments, diagnostics, CancellationToken.None);
        Assert.Empty(diagnostics);
        AssertEx.Equal([generatedDocumentId], changedOrAddedDocuments.Select(d => d.Id));
 
        Assert.Equal(1, generatorExecutionCount);
    }
 
    [Fact]
    public async Task HasChanges_SourceGeneratorFailure()
    {
        using var _ = CreateWorkspace(out var solution, out var service);
 
        var pathA = Path.Combine(TempRoot.Root, "A.txt");
 
        var generatorExecutionCount = 0;
        var generator = new TestSourceGenerator()
        {
            ExecuteImpl = context =>
            {
                generatorExecutionCount++;
 
                var additionalText = context.AdditionalFiles.Single().GetText().ToString();
                if (additionalText.Contains("updated"))
                {
                    throw new InvalidOperationException("Source generator failed");
                }
 
                context.AddSource("generated.cs", SourceText.From("generated: " + additionalText, Encoding.UTF8, SourceHashAlgorithm.Sha256));
            }
        };
 
        var project = solution
            .AddTestProject("A")
            .AddAdditionalDocument("A.txt", "text", filePath: pathA)
            .Project;
 
        var projectId = project.Id;
        solution = project.Solution.AddAnalyzerReference(projectId, new TestGeneratorReference(generator));
        project = solution.GetRequiredProject(projectId);
        var aId = project.AdditionalDocumentIds.Single();
 
        var generatedDocuments = await project.Solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, CancellationToken.None);
 
        var generatedText = generatedDocuments.States.Single().Value.GetTextSynchronously(CancellationToken.None).ToString();
        AssertEx.AreEqual("generated: text", generatedText);
        Assert.Equal(1, generatorExecutionCount);
 
        var generatorDiagnostics = await solution.CompilationState.GetSourceGeneratorDiagnosticsAsync(project.State, CancellationToken.None);
        Assert.Empty(generatorDiagnostics);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession);
 
        var changedOrAddedDocuments = new ArrayBuilder<Document>();
 
        //
        // Update document content
        //
 
        var oldSolution = solution;
        var oldProject = project;
        solution = solution.WithAdditionalDocumentText(aId, CreateText("updated text"));
        project = solution.GetRequiredProject(projectId);
 
        // Change in additional file is detected:
        Assert.True(await EditSession.HasChangesAsync(oldSolution, solution, CancellationToken.None));
 
        // No changed source documents since the generator failed:
        AssertEx.Empty(await EditSession.GetChangedDocumentsAsync(oldProject, project, CancellationToken.None).ToImmutableArrayAsync(CancellationToken.None));
 
        var diagnostics = new ArrayBuilder<ProjectDiagnostics>();
        await EditSession.PopulateChangedAndAddedDocumentsAsync(oldProject, project, changedOrAddedDocuments, diagnostics, CancellationToken.None);
        Assert.Contains("System.InvalidOperationException: Source generator failed", diagnostics.Single().Diagnostics.Single().GetMessage());
        AssertEx.Empty(changedOrAddedDocuments);
 
        Assert.Equal(2, generatorExecutionCount);
 
        generatedDocuments = await solution.CompilationState.GetSourceGeneratedDocumentStatesAsync(project.State, CancellationToken.None);
        Assert.Empty(generatedDocuments.States);
 
        generatorDiagnostics = await solution.CompilationState.GetSourceGeneratorDiagnosticsAsync(project.State, CancellationToken.None);
        Assert.Contains("System.InvalidOperationException: Source generator failed", generatorDiagnostics.Single().GetMessage());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/1204")]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1371694")]
    public async Task Project_Add()
    {
        // Project A:
        var sourceA1 = "class A { void M() { System.Console.WriteLine(1); } }";
 
        // Project B (baseline, but not loaded into solution):
        var sourceB1 = "class B { int F() => 1; }";
 
        // Additional documents added to B:
        var sourceB2 = "class B { int G() => 1; }";
        var sourceB3 = "class B { int F() => 2; }";
 
        var dir = Temp.CreateDirectory();
        var sourceFileA = dir.CreateFile("a.cs").WriteAllText(sourceA1, Encoding.UTF8);
        var sourceFileB = dir.CreateFile("b.cs").WriteAllText(sourceB1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, [sourceA1]);
        var documentA1 = solution.Projects.Single().Documents.Single();
 
        var mvidA = EmitAndLoadLibraryToDebuggee(sourceA1, sourceFilePath: sourceFileA.Path, assemblyName: "A");
        var mvidB = EmitAndLoadLibraryToDebuggee(sourceB1, sourceFilePath: sourceFileB.Path, assemblyName: "B");
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // An active statement may be present in the added file since the file exists in the PDB:
        var activeLineSpanA1 = CreateText(sourceA1).Lines.GetLinePositionSpan(GetSpan(sourceA1, "System.Console.WriteLine(1);"));
        var activeLineSpanB1 = CreateText(sourceB1).Lines.GetLinePositionSpan(GetSpan(sourceB1, "1"));
 
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                new ManagedInstructionId(new ManagedMethodId(mvidA, token: 0x06000001, version: 1), ilOffset: 1),
                sourceFileA.Path,
                activeLineSpanA1.ToSourceSpan(),
                ActiveStatementFlags.LeafFrame | ActiveStatementFlags.MethodUpToDate),
            new ManagedActiveStatementDebugInfo(
                new ManagedInstructionId(new ManagedMethodId(mvidB, token: 0x06000001, version: 1), ilOffset: 1),
                sourceFileB.Path,
                activeLineSpanB1.ToSourceSpan(),
                ActiveStatementFlags.LeafFrame | ActiveStatementFlags.MethodUpToDate));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        // add project that matches assembly B and update the document:
 
        var documentB2 = solution.
            AddTestProject("B").
            AddTestDocument(sourceB2, path: sourceFileB.Path);
 
        solution = documentB2.Project.Solution;
 
        // TODO: https://github.com/dotnet/roslyn/issues/1204
        // Should return span in document B since the document content matches the PDB.
        var baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentA1.Id, documentB2.Id), CancellationToken.None);
        AssertEx.Equal(
        [
            "<empty>",
            "(0,21)-(0,22)"
        ], baseSpans.Select(spans => spans.IsEmpty ? "<empty>" : string.Join(",", spans.Select(s => s.LineSpan.ToString()))));
 
        var trackedActiveSpans = ImmutableArray.Create(
            new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpanB1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame));
 
        var currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(documentB2, (_, _, _) => new(trackedActiveSpans), CancellationToken.None);
        // TODO: https://github.com/dotnet/roslyn/issues/1204
        // AssertEx.Equal(trackedActiveSpans, currentSpans);
        Assert.Empty(currentSpans);
 
        var diagnostics = await service.GetDocumentDiagnosticsAsync(documentB2, s_noActiveSpans, CancellationToken.None);
 
        // TODO: https://github.com/dotnet/roslyn/issues/1204
        //AssertEx.Equal(
        //    new[] { "ENC0020: " + string.Format(FeaturesResources.Renaming_0_requires_restarting_the_application, FeaturesResources.method) },
        //    diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
        Assert.Empty(diagnostics);
 
        // update document with a valid change:
        solution = solution.WithDocumentText(documentB2.Id, CreateText(sourceB3));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
 
        // TODO: https://github.com/dotnet/roslyn/issues/1204
        // verify valid update
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        ExitBreakState(debuggingSession);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory, CombinatorialData]
    public async Task Capabilities(bool breakState)
    {
        var source1 = "class C { void M() { } }";
        var source2 = "[System.Obsolete]class C { void M() { } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, [source1]);
        var documentId = solution.Projects.Single().Documents.Single().Id;
 
        EmitAndLoadLibraryToDebuggee(source1);
 
        // attached to processes that allow updating custom attributes:
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline", "ChangeCustomAttributes");
 
        // F5
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // update document:
        solution = solution.WithDocumentText(documentId, CreateText(source2));
 
        var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        if (breakState)
        {
            EnterBreakState(debuggingSession);
        }
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // attach to additional processes - at least one process that does not allow updating custom attributes:
        if (breakState)
        {
            ExitBreakState(debuggingSession);
        }
 
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline");
 
        if (breakState)
        {
            EnterBreakState(debuggingSession);
        }
        else
        {
            CapabilitiesChanged(debuggingSession);
        }
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0101: " + string.Format(FeaturesResources.Updating_the_attributes_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.class_)],
           diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        if (breakState)
        {
            ExitBreakState(debuggingSession);
        }
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0101: " + string.Format(FeaturesResources.Updating_the_attributes_of_0_requires_restarting_the_application_because_it_is_not_supported_by_the_runtime, FeaturesResources.class_)],
           diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        // detach from processes that do not allow updating custom attributes:
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline", "ChangeCustomAttributes");
 
        if (breakState)
        {
            EnterBreakState(debuggingSession);
        }
        else
        {
            CapabilitiesChanged(debuggingSession);
        }
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        if (breakState)
        {
            ExitBreakState(debuggingSession);
        }
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            $"Debugging_EncSession: SolutionSessionId={{00000000-AAAA-AAAA-AAAA-000000000000}}|SessionId=1|SessionCount=0|EmptySessionCount={(breakState ? 3 : 0)}|HotReloadSessionCount=0|EmptyHotReloadSessionCount={(breakState ? 4 : 3)}"
        ], _telemetryLog);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/56431")]
    public async Task Capabilities_NoTypesEmitted()
    {
        var sourceV1 = @"
/* GENERATE:
class G
{
    int M()
    {
        return 1;
    }
}
*/
";
        var sourceV2 = @"
/* GENERATE:
class G
{
    // a change that won't cause a type to be emitted
    int M()
    {
        return 1;
    }
}
*/
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator);
 
        var moduleId = EmitLibrary(sourceV1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        // attached to processes that doesn't allow creating new types
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline");
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // change the source
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        // validate solution update status and emit
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check that no types have been updated. this used to throw
        var delta = updates.Updates.Single();
        Assert.Empty(delta.UpdatedTypes);
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task Capabilities_SynthesizedNewType()
    {
        var source1 = "class C { void M() { } }";
        var source2 = "class C { void M() { var x = new { Goo = 1 }; } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, [source1]);
        var project = solution.Projects.Single();
        solution = solution.WithProjectParseOptions(project.Id, new CSharpParseOptions(LanguageVersion.CSharp10));
        var documentId = solution.Projects.Single().Documents.Single().Id;
 
        EmitAndLoadLibraryToDebuggee(source1);
 
        // attached to processes that doesn't allow creating new types
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline");
 
        // F5
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // update document:
        solution = solution.WithDocumentText(documentId, CreateText(source2));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // These errors aren't reported as document diagnostics
        var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // They are reported as emit diagnostics
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics));
 
        // no emitted delta:
        Assert.Empty(updates.Updates);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_EmitError()
    {
        var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        (solution, var document) = AddDefaultTestProject(solution, sourceV1);
        EmitAndLoadLibraryToDebuggee(sourceV1);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit but passing no encoding to emulate emit error):
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, SourceText.From("class C1 { void M() { System.Console.WriteLine(2); } }", encoding: null, SourceHashAlgorithms.Default));
        var document2 = solution.Projects.Single().Documents.Single();
 
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics1);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics));
 
        // no emitted delta:
        Assert.Empty(updates.Updates);
 
        // no pending update:
        Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
        Assert.Throws<InvalidOperationException>(() => debuggingSession.CommitSolutionUpdate());
        Assert.Throws<InvalidOperationException>(() => debuggingSession.DiscardSolutionUpdate());
 
        // no change in non-remappable regions since we didn't have any active statements:
        Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
 
        // solution update status after discarding an update (still has update ready):
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Blocked, updates.Status);
        AssertEx.Equal([$"{document2.FilePath}: (0,0)-(0,54): Error CS8055: {string.Format(CSharpResources.ERR_EncodinglessSyntaxTree)}"], InspectDiagnostics(emitDiagnostics));
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
            "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=CS8055"
        ], _telemetryLog);
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task ValidSignificantChange_ApplyBeforeFileWatcherEvent(bool saveDocument)
    {
        // Scenarios tested:
        //
        // SaveDocument=true
        // workspace:     --V0-------------|--V2--------|------------|
        // file system:   --V0---------V1--|-----V2-----|------------|
        //                   \--build--/   F5    ^      F10  ^       F10
        //                                       save        file watcher: no-op
        // SaveDocument=false
        // workspace:     --V0-------------|--V2--------|----V1------|
        // file system:   --V0---------V1--|------------|------------|
        //                   \--build--/   F5           F10  ^       F10
        //                                                   file watcher: workspace update
 
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build):
        var document1 = solution.
            AddTestProject("test").
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework)).
            AddDocument("test.cs", CreateText("class C1 { void M() { System.Console.WriteLine(0); } }"), filePath: sourceFile.Path);
 
        var documentId = document1.Id;
        solution = document1.Project.Solution;
 
        var sourceTextProvider = new MockPdbMatchingSourceTextProvider()
        {
            TryGetMatchingSourceTextImpl = (filePath, requiredChecksum, checksumAlgorithm) =>
            {
                Assert.Equal(sourceFile.Path, filePath);
                AssertEx.Equal(requiredChecksum, CreateText(source1).GetChecksum());
                Assert.Equal(SourceHashAlgorithms.Default, checksumAlgorithm);
 
                return source1;
            }
        };
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None, sourceTextProvider);
 
        EnterBreakState(debuggingSession);
 
        // The user opens the source file and changes the source before Roslyn receives file watcher event.
        var source2 = "class C1 { void M() { System.Console.WriteLine(2); } }";
        solution = solution.WithDocumentText(documentId, CreateText(source2));
        var document2 = solution.GetDocument(documentId);
 
        // Save the document:
        if (saveDocument)
        {
            sourceFile.WriteAllText(source2, Encoding.UTF8);
        }
 
        // EnC service queries for a document, which triggers read of the source file from disk.
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
 
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        CommitSolutionUpdate(debuggingSession);
 
        ExitBreakState(debuggingSession);
 
        EnterBreakState(debuggingSession);
 
        // file watcher updates the workspace:
        solution = solution.WithDocumentText(documentId, CreateTextFromFile(sourceFile.Path));
        var document3 = solution.Projects.Single().Documents.Single();
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
 
        if (saveDocument)
        {
            Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        }
        else
        {
            Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
            debuggingSession.DiscardSolutionUpdate();
        }
 
        ExitBreakState(debuggingSession);
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_FileUpdateNotObservedBeforeDebuggingSessionStart()
    {
        // workspace:     --V0--------------V2-------|--------V3------------------V1--------------|
        // file system:   --V0---------V1-----V2-----|------------------------------V1------------|
        //                   \--build--/      ^save  F5   ^      ^F10 (no change)   ^save         F10 (ok)
        //                                                file watcher: no-op
        // build updates file from V0 -> V1
 
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class C1 { void M() { System.Console.WriteLine(2); } }";
        var source3 = "class C1 { void M() { System.Console.WriteLine(3); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("test.cs").WriteAllText(source2, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build):
        var document2 = solution.
            AddTestProject("test").
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
            AddDocument("test.cs", CreateText(source2), filePath: sourceFile.Path);
 
        var documentId = document2.Id;
 
        var project = document2.Project;
        solution = project.Solution;
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        EnterBreakState(debuggingSession);
 
        // user edits the file:
        solution = solution.WithDocumentText(documentId, CreateText(source3));
        var document3 = solution.Projects.Single().Documents.Single();
 
        // EnC service queries for a document, but the source file on disk doesn't match the PDB
 
        // We don't report rude edits for out-of-sync documents:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // since the document is out-of-sync we need to call update to determine whether we have changes to apply or not:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        AssertEx.Equal([$"test.csproj: (0,0)-(0,0): Warning ENC1005: {string.Format(FeaturesResources.DocumentIsOutOfSyncWithDebuggee, sourceFile.Path)}"], InspectDiagnostics(emitDiagnostics));
 
        // undo:
        solution = solution.WithDocumentText(documentId, CreateText(source1));
 
        var currentDocument = solution.GetDocument(documentId);
 
        // save (note that this call will fail to match the content with the PDB since it uses the content prior to the actual file write)
        // TODO: await debuggingSession.OnSourceFileUpdatedAsync(currentDocument);
        var (doc, state) = await debuggingSession.LastCommittedSolution.GetDocumentAndStateAsync(documentId, currentDocument, CancellationToken.None);
        Assert.Null(doc);
        Assert.Equal(CommittedSolution.DocumentState.OutOfSync, state);
        sourceFile.WriteAllText(source1, Encoding.UTF8);
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
 
        // the content actually hasn't changed:
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_AddedFileNotObservedBeforeDebuggingSessionStart()
    {
        // workspace:     ------|----V0---------------|----------
        // file system:   --V0--|---------------------|----------
        //                      F5   ^          ^F10 (no change)
        //                           file watcher observes the file
 
        var source1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("test.cs").WriteAllText(source1, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with no file
        var project = solution.
            AddProject("test", "test", LanguageNames.CSharp).
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40));
 
        solution = project.Solution;
 
        var moduleId = EmitAndLoadLibraryToDebuggee(source1, sourceFilePath: sourceFile.Path);
 
        _debuggerService.IsEditAndContinueAvailable = _ => new ManagedHotReloadAvailability(ManagedHotReloadAvailabilityStatus.Attach, localizedMessage: "*attached*");
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        // An active statement may be present in the added file since the file exists in the PDB:
        var activeInstruction1 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1);
        var activeSpan1 = GetSpan(source1, "System.Console.WriteLine(1);");
        var sourceText1 = CreateText(source1);
        var activeLineSpan1 = sourceText1.Lines.GetLinePositionSpan(activeSpan1);
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                activeInstruction1,
                "test.cs",
                activeLineSpan1.ToSourceSpan(),
                ActiveStatementFlags.LeafFrame));
 
        // disallow any edits (attach scenario)
        EnterBreakState(debuggingSession, activeStatements);
 
        // File watcher observes the document and adds it to the workspace:
 
        var document1 = project.AddDocument("test.cs", sourceText1, filePath: sourceFile.Path);
        solution = document1.Project.Solution;
 
        // We don't report rude edits for the added document:
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // TODO: https://github.com/dotnet/roslyn/issues/49938
        // We currently create the AS map against the committed solution, which may not contain all documents.
        // var spans = await service.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None);
        // AssertEx.Equal(new[] { $"({activeLineSpan1}, LeafFrame)" }, spans.Single().Select(s => s.ToString()));
 
        // No changes.
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        AssertEx.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory, CombinatorialData]
    public async Task ValidSignificantChange_DocumentOutOfSync(bool delayLoad)
    {
        var sourceOnDisk = "class C1 { void M() { System.Console.WriteLine(1); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFile = dir.CreateFile("test.cs").WriteAllText(sourceOnDisk, Encoding.UTF8);
 
        using var _ = CreateWorkspace(out var solution, out var service);
 
        // the workspace starts with a version of the source that's not updated with the output of single file generator (or design-time build):
        var document1 = solution.
            AddProject("test", "test", LanguageNames.CSharp).
            AddMetadataReferences(TargetFrameworkUtil.GetReferences(TargetFramework.Mscorlib40)).
            AddDocument("test.cs", CreateText("class C1 { void M() { System.Console.WriteLine(0); } }"), filePath: sourceFile.Path);
 
        var project = document1.Project;
        solution = project.Solution;
 
        var moduleId = EmitLibrary(sourceOnDisk, sourceFilePath: sourceFile.Path);
 
        if (!delayLoad)
        {
            LoadLibraryToDebuggee(moduleId);
        }
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.None);
 
        EnterBreakState(debuggingSession);
 
        // no changes have been made to the project
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(updates.Updates);
        Assert.Empty(emitDiagnostics);
 
        // a file watcher observed a change and updated the document, so it now reflects the content on disk (the code that we compiled):
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceOnDisk));
        var document3 = solution.Projects.Single().Documents.Single();
 
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document3, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // the content of the file is now exactly the same as the compiled document, so there is no change to be applied:
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        Assert.Empty(emitDiagnostics);
 
        EndDebuggingSession(debuggingSession);
 
        Assert.Empty(debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
    }
 
    [Theory, CombinatorialData]
    public async Task ValidSignificantChange_EmitSuccessful(bool breakMode, bool commitUpdate)
    {
        var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var sourceV2 = "class C1 { void M() { System.Console.WriteLine(2); } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1);
 
        var moduleId = EmitAndLoadLibraryToDebuggee(sourceV1);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        if (breakMode)
        {
            EnterBreakState(debuggingSession);
        }
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
        var document2 = solution.GetDocument(document1.Id);
 
        var diagnostics1 = await service.GetDocumentDiagnosticsAsync(document2, s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics1);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        ValidateDelta(updates.Updates.Single());
 
        void ValidateDelta(ManagedHotReloadUpdate delta)
        {
            // check emitted delta:
            Assert.Empty(delta.ActiveStatements);
            Assert.NotEmpty(delta.ILDelta);
            Assert.NotEmpty(delta.MetadataDelta);
            Assert.NotEmpty(delta.PdbDelta);
            Assert.Equal(0x06000001, delta.UpdatedMethods.Single());
            Assert.Equal(0x02000002, delta.UpdatedTypes.Single());
            Assert.Equal(moduleId, delta.Module);
            Assert.Empty(delta.ExceptionRegions);
            Assert.Empty(delta.SequencePoints);
        }
 
        // the update should be stored on the service:
        var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
        var newBaseline = pendingUpdate.ProjectBaselines.Single();
        AssertEx.Equal(updates.Updates, pendingUpdate.Deltas);
        Assert.Equal(document2.Project.Id, newBaseline.ProjectId);
        Assert.Equal(moduleId, newBaseline.EmitBaseline.OriginalMetadata.GetModuleVersionId());
 
        var readers = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
        Assert.Equal(2, readers.Length);
        Assert.NotNull(readers[0]);
        Assert.NotNull(readers[1]);
 
        if (commitUpdate)
        {
            // all update providers either provided updates or had no change to apply:
            CommitSolutionUpdate(debuggingSession);
 
            Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
            // no change in non-remappable regions since we didn't have any active statements:
            Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
 
            var baselineReaders = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
            Assert.Equal(2, baselineReaders.Length);
            Assert.Same(readers[0], baselineReaders[0]);
            Assert.Same(readers[1], baselineReaders[1]);
 
            // verify that baseline is added:
            Assert.Same(newBaseline.EmitBaseline, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(document2.Project.Id));
 
            // solution update status after committing an update:
            (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
            Assert.Empty(emitDiagnostics);
            Assert.Equal(ModuleUpdateStatus.None, updates.Status);
        }
        else
        {
            // another update provider blocked the update:
            debuggingSession.DiscardSolutionUpdate();
 
            Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
            // solution update status after committing an update:
            (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
            Assert.Empty(emitDiagnostics);
            Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
            ValidateDelta(updates.Updates.Single());
            debuggingSession.DiscardSolutionUpdate();
        }
 
        if (breakMode)
        {
            ExitBreakState(debuggingSession);
        }
 
        EndDebuggingSession(debuggingSession);
 
        // open module readers should be disposed when the debugging session ends:
        VerifyReadersDisposed(readers);
 
        AssertEx.SetEqual([moduleId], debuggingSession.GetTestAccessor().GetModulesPreparedForUpdate());
 
        if (breakMode)
        {
            AssertEx.Equal(
            [
                $"Debugging_EncSession: SolutionSessionId={{00000000-AAAA-AAAA-AAAA-000000000000}}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount={(commitUpdate ? 3 : 2)}",
                $"Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges={(commitUpdate ? "{6A6F7270-0000-4000-8000-000000000000}" : "")}",
            ], _telemetryLog);
        }
        else
        {
            AssertEx.Equal(
            [
                $"Debugging_EncSession: SolutionSessionId={{00000000-AAAA-AAAA-AAAA-000000000000}}|SessionId=1|SessionCount=0|EmptySessionCount=0|HotReloadSessionCount=1|EmptyHotReloadSessionCount={(commitUpdate ? 1 : 0)}",
                $"Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=0|InBreakState=False|Capabilities=31|ProjectIdsWithAppliedChanges={(commitUpdate ? "{6A6F7270-0000-4000-8000-000000000000}" : "")}"
            ], _telemetryLog);
        }
    }
 
    [Theory]
    [InlineData(true)]
    [InlineData(false)]
    public async Task ValidSignificantChange_EmitSuccessful_UpdateDeferred(bool commitUpdate)
    {
        var dir = Temp.CreateDirectory();
 
        var sourceV1 = "class C1 { void M1() { int a = 1; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(1); } }";
        var compilationV1 = CSharpTestBase.CreateCompilation(sourceV1, parseOptions: TestOptions.Regular.WithNoRefSafetyRulesAttribute(), options: TestOptions.DebugDll, targetFramework: DefaultTargetFramework, assemblyName: "lib");
 
        var (peImage, pdbImage) = compilationV1.EmitToArrays(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb));
        var moduleMetadata = ModuleMetadata.CreateFromImage(peImage);
        var moduleFile = dir.CreateFile("lib.dll").WriteAllBytes(peImage);
        var pdbFile = dir.CreateFile("lib.pdb").WriteAllBytes(pdbImage);
        var moduleId = moduleMetadata.GetModuleVersionId();
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1);
 
        _mockCompilationOutputsProvider = _ => new CompilationOutputFiles(moduleFile.Path, pdbFile.Path);
 
        // set up an active statement in the first method, so that we can test preservation of local signature.
        var activeStatements = ImmutableArray.Create(new ManagedActiveStatementDebugInfo(
            new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 0),
            documentName: document1.Name,
            sourceSpan: new SourceSpan(0, 15, 0, 16),
            ActiveStatementFlags.LeafFrame));
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // module is not loaded:
        EnterBreakState(debuggingSession, activeStatements);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M1() { int a = 1; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }"));
        var document2 = solution.GetDocument(document1.Id);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        Assert.Empty(emitDiagnostics);
 
        // delta to apply:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(0x06000002, delta.UpdatedMethods.Single());
        Assert.Equal(0x02000002, delta.UpdatedTypes.Single());
        Assert.Equal(moduleId, delta.Module);
        Assert.Empty(delta.ExceptionRegions);
        Assert.Empty(delta.SequencePoints);
 
        // the update should be stored on the service:
        var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
        var newBaseline = pendingUpdate.ProjectBaselines.Single();
 
        var readers = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
        Assert.Equal(2, readers.Length);
        Assert.NotNull(readers[0]);
        Assert.NotNull(readers[1]);
 
        Assert.Equal(document2.Project.Id, newBaseline.ProjectId);
        Assert.Equal(moduleId, newBaseline.EmitBaseline.OriginalMetadata.GetModuleVersionId());
 
        if (commitUpdate)
        {
            CommitSolutionUpdate(debuggingSession);
            Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
            // no change in non-remappable regions since we didn't have any active statements:
            Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
 
            // verify that baseline is added:
            Assert.Same(newBaseline.EmitBaseline, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(document2.Project.Id));
 
            // solution update status after committing an update:
            ExitBreakState(debuggingSession);
 
            // make another update:
            EnterBreakState(debuggingSession);
 
            // Update M1 - this method has an active statement, so we will attempt to preserve the local signature.
            // Since the method hasn't been edited before we'll read the baseline PDB to get the signature token.
            // This validates that the Portable PDB reader can be used (and is not disposed) for a second generation edit.
            var document3 = solution.GetDocument(document1.Id);
            solution = solution.WithDocumentText(document3.Id, CreateText("class C1 { void M1() { int a = 3; System.Console.WriteLine(a); } void M2() { System.Console.WriteLine(2); } }"));
 
            (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
            Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
            Assert.Empty(emitDiagnostics);
            debuggingSession.DiscardSolutionUpdate();
        }
        else
        {
            debuggingSession.DiscardSolutionUpdate();
            Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
        }
 
        ExitBreakState(debuggingSession);
        EndDebuggingSession(debuggingSession);
 
        // open module readers should be disposed when the debugging session ends:
        VerifyReadersDisposed(readers);
    }
 
    [Fact]
    public async Task ValidSignificantChange_PartialTypes()
    {
        var sourceA1 = @"
partial class C { int X = 1; void F() { X = 1; } }
 
partial class D { int U = 1; public D() { } }
partial class D { int W = 1; }
 
partial class E { int A; public E(int a) { A = a; } }
";
        var sourceB1 = @"
partial class C { int Y = 1; }
partial class E { int B; public E(int a, int b) { A = a; B = new System.Func<int>(() => b)(); } }
";
 
        var sourceA2 = @"
partial class C { int X = 2; void F() { X = 2; } }
 
partial class D { int U = 2; }
partial class D { int W = 2; public D() { } }
 
partial class E { int A = 1; public E(int a) { A = a; } }
";
        var sourceB2 = @"
partial class C { int Y = 2; }
partial class E { int B = 2; public E(int a, int b) { A = a; B = new System.Func<int>(() => b)(); } }
";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, [sourceA1, sourceB1]);
        var project = solution.Projects.Single();
 
        LoadLibraryToDebuggee(EmitLibrary([(sourceA1, "test1.cs"), (sourceB1, "test2.cs")]));
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit):
        var documentA = project.Documents.First();
        var documentB = project.Documents.Skip(1).First();
        solution = solution.WithDocumentText(documentA.Id, CreateText(sourceA2));
        solution = solution.WithDocumentText(documentB.Id, CreateText(sourceB2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(6, delta.UpdatedMethods.Length);  // F, C.C(), D.D(), E.E(int), E.E(int, int), lambda
        AssertEx.SetEqual([0x02000002, 0x02000003, 0x02000004, 0x02000005], delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X"));
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory]
    [CombinatorialData]
    [WorkItem("https://github.com/dotnet/roslyn/issues/72331")]
    internal async Task ValidSignificantChange_SourceGenerators_DocumentUpdate_GeneratedDocumentUpdate(SourceGeneratorExecutionPreference executionPreference)
    {
        var sourceV1 = @"
/* GENERATE: file class G { int X => 1; } */
 
class C { int Y => 1; }
";
        var sourceV2 = @"
/* GENERATE: file class G { int X => 2; } */
 
class C { int Y => 2; }
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var workspace = CreateWorkspace(out var solution, out var service);
        var workspaceConfig = Assert.IsType<TestWorkspaceConfigurationService>(workspace.Services.GetRequiredService<IWorkspaceConfigurationService>());
        workspaceConfig.Options = new WorkspaceConfigurationOptions(executionPreference);
 
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator);
 
        var moduleId = EmitLibrary(sourceV1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        // Trigger initial source generation before debugging session starts.
        // Causes source generator to run on the solution for the first time.
        // Futher compilation access won't automatically trigger source generators,
        // the EnC service has to do so.
        _ = await solution.Projects.Single().GetCompilationAsync(CancellationToken.None);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit)
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(2, delta.UpdatedMethods.Length);
        AssertEx.Equal([0x02000002, 0x02000003], delta.UpdatedTypes, itemInspector: t => "0x" + t.ToString("X"));
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Theory]
    [CombinatorialData]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/2169491")]
    internal async Task RudeEdit_SourceGenerators_DocumentUpdate_GeneratedDocumentAdd(SourceGeneratorExecutionPreference executionPreference)
    {
        var sourceV1 = """
            class C { int Y => 1; }
            """;
 
        var sourceV2 = """
            /* GENERATE: [assembly: System.Reflection.AssemblyMetadata("X", "Y")] */
 
            class C { int Y => 2; }
            """;
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var workspace = CreateWorkspace(out var solution, out var service);
        var workspaceConfig = Assert.IsType<TestWorkspaceConfigurationService>(workspace.Services.GetRequiredService<IWorkspaceConfigurationService>());
        workspaceConfig.Options = new WorkspaceConfigurationOptions(executionPreference);
 
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator);
 
        var moduleId = EmitLibrary(sourceV1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        // Trigger initial source generation before debugging session starts.
        // Causes source generator to run on the solution for the first time.
        // Futher compilation access won't automatically trigger source generators,
        // the EnC service has to do so.
        _ = await solution.Projects.Single().GetCompilationAsync(CancellationToken.None);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit)
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        // validate solution update status and emit:
        var results = (await debuggingSession.EmitSolutionUpdateAsync(solution, s_noActiveSpans, CancellationToken.None).ConfigureAwait(false)).Dehydrate();
        var diagnostics = results.GetAllDiagnostics();
 
        var generatedFilePath = Path.Combine(
            TempRoot.Root,
            "Microsoft.CodeAnalysis.Test.Utilities",
            "Roslyn.Test.Utilities.TestGenerators.TestSourceGenerator",
            "Generated_test1.cs");
 
        AssertEx.Equal(
        [
            $@"ENC0021: '{generatedFilePath}' (0,0)-(0,56): " +
            string.Format(FeaturesResources.Adding_0_requires_restarting_the_application, FeaturesResources.attribute),
        ], diagnostics.Select(d => $"{d.Id}: '{d.FilePath}' {d.Span.GetDebuggerDisplay()}: {d.Message}"));
 
        Assert.Equal(ModuleUpdateStatus.RestartRequired, results.ModuleUpdates.Status);
        Assert.Empty(results.ModuleUpdates.Updates);
 
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_SourceGenerators_DocumentUpdate_GeneratedDocumentUpdate_LineChanges()
    {
        var sourceV1 = @"
/* GENERATE:
class G
{
    int M()
    {
        return 1;
    }
}
*/
";
        var sourceV2 = @"
/* GENERATE:
class G
{
 
    int M()
    {
        return 1;
    }
}
*/
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator);
 
        var moduleId = EmitLibrary(sourceV1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
 
        var lineUpdate = delta.SequencePoints.Single();
        AssertEx.Equal(["3 -> 4"], lineUpdate.LineUpdates.Select(edit => $"{edit.OldLine} -> {edit.NewLine}"));
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Empty(delta.UpdatedMethods);
        Assert.Empty(delta.UpdatedTypes);
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_SourceGenerators_DocumentUpdate_GeneratedDocumentInsert()
    {
        var sourceV1 = @"
partial class C { int X = 1; }
";
        var sourceV2 = @"
/* GENERATE: partial class C { int Y = 2; } */
 
partial class C { int X = 1; }
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1, generator);
 
        var moduleId = EmitLibrary(sourceV1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(document1.Id, CreateText(sourceV2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(1, delta.UpdatedMethods.Length); // constructor update
        Assert.Equal(0x02000002, delta.UpdatedTypes.Single());
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_SourceGenerators_AdditionalDocumentUpdate()
    {
        var source = @"
class C { int Y => 1; }
";
 
        var additionalSourceV1 = @"
/* GENERATE: class G { int X => 1; } */
";
        var additionalSourceV2 = @"
/* GENERATE: class G { int X => 2; } */
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source, generator, additionalFileText: additionalSourceV1);
 
        var moduleId = EmitLibrary(source, generatorProject: document.Project, additionalFileText: additionalSourceV1);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the additional source (valid edit):
        var additionalDocument1 = solution.Projects.Single().AdditionalDocuments.Single();
        solution = solution.WithAdditionalDocumentText(additionalDocument1.Id, CreateText(additionalSourceV2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(1, delta.UpdatedMethods.Length);
        Assert.Equal(0x02000003, delta.UpdatedTypes.Single());
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_SourceGenerators_AnalyzerConfigUpdate()
    {
        var source = @"
class C { int Y => 1; }
";
 
        var configV1 = new[] { ("enc_generator_output", "1") };
        var configV2 = new[] { ("enc_generator_output", "2") };
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source, generator, analyzerConfig: configV1);
 
        var moduleId = EmitLibrary(source, generatorProject: document.Project, analyzerOptions: configV1);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // change the additional source (valid edit):
        var configDocument1 = solution.Projects.Single().AnalyzerConfigDocuments.Single();
        solution = solution.WithAnalyzerConfigDocumentText(configDocument1.Id, GetAnalyzerConfigText(configV2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(1, delta.UpdatedMethods.Length);
        Assert.Equal(0x02000003, delta.UpdatedTypes.Single());
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task ValidSignificantChange_SourceGenerators_DocumentRemove()
    {
        var source1 = "";
 
        var generator = new TestSourceGenerator()
        {
            ExecuteImpl = context => context.AddSource("generated", $"class G {{ int X => {context.Compilation.SyntaxTrees.Count()}; }}")
        };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, source1, generator);
 
        var moduleId = EmitLibrary(source1, generatorProject: document1.Project);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        // remove the source document (valid edit):
        solution = document1.Project.Solution.RemoveDocument(document1.Id);
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Equal(1, delta.UpdatedMethods.Length);
        Assert.Equal(0x02000002, delta.UpdatedTypes.Single());
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact]
    public async Task RudeEdit()
    {
        var source1 = "class C { void M() { } }";
        var source2 = "class C { void M() { var x = new { Goo = 1 }; } }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, [source1]);
        var project = solution.Projects.Single();
        solution = solution.WithProjectParseOptions(project.Id, new CSharpParseOptions(LanguageVersion.CSharp10));
        var documentId = solution.Projects.Single().Documents.Single().Id;
 
        EmitAndLoadLibraryToDebuggee(source1);
 
        // attached to processes that doesn't allow creating new types
        _debuggerService.GetCapabilitiesImpl = () => ImmutableArray.Create("Baseline");
 
        // F5
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // update document:
        solution = solution.WithDocumentText(documentId, CreateText(source2));
        var document2 = solution.Projects.Single().Documents.Single();
 
        // These errors aren't reported as document diagnostics
        var diagnostics = await service.GetDocumentDiagnosticsAsync(solution.GetDocument(documentId), s_noActiveSpans, CancellationToken.None);
        AssertEx.Empty(diagnostics);
 
        // They are reported as emit diagnostics
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1007: {FeaturesResources.ChangesRequiredSynthesizedType}"], InspectDiagnostics(emitDiagnostics));
 
        // no emitted delta:
        Assert.Empty(updates.Updates);
 
        EndDebuggingSession(debuggingSession);
    }
 
    /// <summary>
    /// Emulates two updates to Multi-TFM project.
    /// </summary>
    [Fact]
    public async Task TwoUpdatesWithLoadedAndUnloadedModule()
    {
        var dir = Temp.CreateDirectory();
 
        var source1 = "class A { void M() { System.Console.WriteLine(1); } }";
        var source2 = "class A { void M() { System.Console.WriteLine(2); } }";
        var source3 = "class A { void M() { System.Console.WriteLine(3); } }";
 
        var compilationA = CSharpTestBase.CreateCompilation(source1, options: TestOptions.DebugDll, targetFramework: DefaultTargetFramework, assemblyName: "A");
        var compilationB = CSharpTestBase.CreateCompilation(source1, options: TestOptions.DebugDll, targetFramework: DefaultTargetFramework, assemblyName: "B");
 
        var (peImageA, pdbImageA) = compilationA.EmitToArrays(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb));
        var moduleMetadataA = ModuleMetadata.CreateFromImage(peImageA);
        var moduleFileA = Temp.CreateFile("A.dll").WriteAllBytes(peImageA);
        var pdbFileA = dir.CreateFile("A.pdb").WriteAllBytes(pdbImageA);
        var moduleIdA = moduleMetadataA.GetModuleVersionId();
 
        var (peImageB, pdbImageB) = compilationB.EmitToArrays(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb));
        var moduleMetadataB = ModuleMetadata.CreateFromImage(peImageB);
        var moduleFileB = dir.CreateFile("B.dll").WriteAllBytes(peImageB);
        var pdbFileB = dir.CreateFile("B.pdb").WriteAllBytes(pdbImageB);
        var moduleIdB = moduleMetadataB.GetModuleVersionId();
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var documentA) = AddDefaultTestProject(solution, source1);
        var projectA = documentA.Project;
 
        var projectB = solution.AddTestProject("B").WithAssemblyName("A").
            AddMetadataReferences(projectA.MetadataReferences).
            AddDocument("DocB", source1, filePath: Path.Combine(TempRoot.Root, "DocB.cs")).Project;
 
        solution = projectB.Solution;
 
        _mockCompilationOutputsProvider = project =>
            (project.Id == projectA.Id) ? new CompilationOutputFiles(moduleFileA.Path, pdbFileA.Path) :
            (project.Id == projectB.Id) ? new CompilationOutputFiles(moduleFileB.Path, pdbFileB.Path) :
            throw ExceptionUtilities.UnexpectedValue(project);
 
        // only module A is loaded
        LoadLibraryToDebuggee(moduleIdA);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession);
 
        //
        // First update.
        //
 
        solution = solution.WithDocumentText(projectA.Documents.Single().Id, CreateText(source2));
        solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        Assert.Empty(emitDiagnostics);
 
        var deltaA = updates.Updates.Single(d => d.Module == moduleIdA);
        var deltaB = updates.Updates.Single(d => d.Module == moduleIdB);
        Assert.Equal(2, updates.Updates.Length);
 
        // the update should be stored on the service:
        var pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
        var newBaselineA1 = pendingUpdate.ProjectBaselines.Single(b => b.ProjectId == projectA.Id).EmitBaseline;
        var newBaselineB1 = pendingUpdate.ProjectBaselines.Single(b => b.ProjectId == projectB.Id).EmitBaseline;
 
        var baselineA0 = newBaselineA1.GetInitialEmitBaseline();
        var baselineB0 = newBaselineB1.GetInitialEmitBaseline();
 
        var readers = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
        Assert.Equal(4, readers.Length);
        Assert.False(readers.Any(r => r is null));
 
        Assert.Equal(moduleIdA, newBaselineA1.OriginalMetadata.GetModuleVersionId());
        Assert.Equal(moduleIdB, newBaselineB1.OriginalMetadata.GetModuleVersionId());
 
        CommitSolutionUpdate(debuggingSession);
        Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
        // no change in non-remappable regions since we didn't have any active statements:
        Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
 
        // verify that baseline is added for both modules:
        Assert.Same(newBaselineA1, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectA.Id));
        Assert.Same(newBaselineB1, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectB.Id));
 
        // solution update status after committing an update:(updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        ExitBreakState(debuggingSession);
        EnterBreakState(debuggingSession);
 
        //
        // Second update.
        //
 
        solution = solution.WithDocumentText(projectA.Documents.Single().Id, CreateText(source3));
        solution = solution.WithDocumentText(projectB.Documents.Single().Id, CreateText(source3));
 
        // validate solution update status and emit:
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
        Assert.Empty(emitDiagnostics);
 
        deltaA = updates.Updates.Single(d => d.Module == moduleIdA);
        deltaB = updates.Updates.Single(d => d.Module == moduleIdB);
        Assert.Equal(2, updates.Updates.Length);
 
        // the update should be stored on the service:
        pendingUpdate = debuggingSession.GetTestAccessor().GetPendingSolutionUpdate();
        var newBaselineA2 = pendingUpdate.ProjectBaselines.Single(b => b.ProjectId == projectA.Id).EmitBaseline;
        var newBaselineB2 = pendingUpdate.ProjectBaselines.Single(b => b.ProjectId == projectB.Id).EmitBaseline;
 
        Assert.NotSame(newBaselineA1, newBaselineA2);
        Assert.NotSame(newBaselineB1, newBaselineB2);
        Assert.Same(baselineA0, newBaselineA2.GetInitialEmitBaseline());
        Assert.Same(baselineB0, newBaselineB2.GetInitialEmitBaseline());
        Assert.Same(baselineA0.OriginalMetadata, newBaselineA2.OriginalMetadata);
        Assert.Same(baselineB0.OriginalMetadata, newBaselineB2.OriginalMetadata);
 
        // no new module readers:
        var baselineReaders = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
        AssertEx.Equal(readers, baselineReaders);
 
        CommitSolutionUpdate(debuggingSession);
        Assert.Null(debuggingSession.GetTestAccessor().GetPendingSolutionUpdate());
 
        // no change in non-remappable regions since we didn't have any active statements:
        Assert.Empty(debuggingSession.EditSession.NonRemappableRegions);
 
        // module readers tracked:
        baselineReaders = debuggingSession.GetTestAccessor().GetBaselineModuleReaders();
        AssertEx.Equal(readers, baselineReaders);
 
        // verify that baseline is updated for both modules:
        Assert.Same(newBaselineA2, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectA.Id));
        Assert.Same(newBaselineB2, debuggingSession.GetTestAccessor().GetProjectEmitBaseline(projectB.Id));
 
        // solution update status after committing an update:
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.None, updates.Status);
 
        ExitBreakState(debuggingSession);
        EndDebuggingSession(debuggingSession);
 
        // open deferred module readers should be dispose when the debugging session ends:
        VerifyReadersDisposed(readers);
    }
 
    [Fact]
    public async Task ValidSignificantChange_BaselineCreationFailed_NoStream()
    {
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, "class C1 { void M() { System.Console.WriteLine(1); } }");
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid())
        {
            OpenPdbStreamImpl = () => null,
            OpenAssemblyStreamImpl = () => null,
        };
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // module not loaded
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-pdb", new FileNotFoundException().Message)}"], InspectDiagnostics(emitDiagnostics));
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
    }
 
    [Fact]
    public async Task ValidSignificantChange_BaselineCreationFailed_AssemblyReadError()
    {
        var sourceV1 = "class C1 { void M() { System.Console.WriteLine(1); } }";
        var compilationV1 = CSharpTestBase.CreateCompilationWithMscorlib40(sourceV1, options: TestOptions.DebugDll, assemblyName: "lib");
 
        var pdbStream = new MemoryStream();
        var peImage = compilationV1.EmitToArray(new EmitOptions(debugInformationFormat: DebugInformationFormat.PortablePdb), pdbStream: pdbStream);
        pdbStream.Position = 0;
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, sourceV1);
 
        _mockCompilationOutputsProvider = _ => new MockCompilationOutputs(Guid.NewGuid())
        {
            OpenPdbStreamImpl = () => pdbStream,
            OpenAssemblyStreamImpl = () => throw new IOException("*message*"),
        };
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // module not loaded
        EnterBreakState(debuggingSession);
 
        // change the source (valid edit):
        var document1 = solution.Projects.Single().Documents.Single();
        solution = solution.WithDocumentText(document1.Id, CreateText("class C1 { void M() { System.Console.WriteLine(2); } }"));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        AssertEx.Equal([$"proj.csproj: (0,0)-(0,0): Error ENC1001: {string.Format(FeaturesResources.ErrorReadingFile, "test-assembly", "*message*")}"], InspectDiagnostics(emitDiagnostics));
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
 
        EndDebuggingSession(debuggingSession);
 
        AssertEx.Equal(
        [
            "Debugging_EncSession: SolutionSessionId={00000000-AAAA-AAAA-AAAA-000000000000}|SessionId=1|SessionCount=1|EmptySessionCount=0|HotReloadSessionCount=0|EmptyHotReloadSessionCount=1",
            "Debugging_EncSession_EditSession: SessionId=1|EditSessionId=2|HadCompilationErrors=False|HadRudeEdits=False|HadValidChanges=True|HadValidInsignificantChanges=False|RudeEditsCount=0|EmitDeltaErrorIdCount=1|InBreakState=True|Capabilities=31|ProjectIdsWithAppliedChanges=",
            "Debugging_EncSession_EditSession_EmitDeltaErrorId: SessionId=1|EditSessionId=2|ErrorId=ENC1001"
        ], _telemetryLog);
    }
 
    [Fact]
    public async Task ActiveStatements()
    {
        var sourceV1 = "class C { void F() { G(1); } void G(int a) => System.Console.WriteLine(1); }";
        var sourceV2 = "class C { int x; void F() { G(2); G(1); } void G(int a) => System.Console.WriteLine(2); }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1);
 
        var activeSpan11 = GetSpan(sourceV1, "G(1);");
        var activeSpan12 = GetSpan(sourceV1, "System.Console.WriteLine(1)");
        var activeSpan21 = GetSpan(sourceV2, "G(2); G(1);");
        var activeSpan22 = GetSpan(sourceV2, "System.Console.WriteLine(2)");
        var adjustedActiveSpan1 = GetSpan(sourceV2, "G(2);");
        var adjustedActiveSpan2 = GetSpan(sourceV2, "System.Console.WriteLine(2)");
 
        var documentId = document1.Id;
        var documentPath = document1.FilePath;
 
        var sourceTextV1 = document1.GetTextSynchronously(CancellationToken.None);
        var sourceTextV2 = CreateText(sourceV2);
 
        var activeLineSpan11 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan11);
        var activeLineSpan12 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan12);
        var activeLineSpan21 = sourceTextV2.Lines.GetLinePositionSpan(activeSpan21);
        var activeLineSpan22 = sourceTextV2.Lines.GetLinePositionSpan(activeSpan22);
        var adjustedActiveLineSpan1 = sourceTextV2.Lines.GetLinePositionSpan(adjustedActiveSpan1);
        var adjustedActiveLineSpan2 = sourceTextV2.Lines.GetLinePositionSpan(adjustedActiveSpan2);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // default if not called in a break state
        Assert.True((await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None)).IsDefault);
 
        var moduleId = Guid.NewGuid();
        var activeInstruction1 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1);
        var activeInstruction2 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000002, version: 1), ilOffset: 1);
 
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                activeInstruction1,
                documentPath,
                activeLineSpan11.ToSourceSpan(),
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame),
            new ManagedActiveStatementDebugInfo(
                activeInstruction2,
                documentPath,
                activeLineSpan12.ToSourceSpan(),
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        var activeStatementSpan11 = new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame);
        var activeStatementSpan12 = new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame);
 
        var baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document1.Id), CancellationToken.None);
        AssertEx.Equal(
        [
            activeStatementSpan11,
            activeStatementSpan12
        ], baseSpans.Single());
 
        var trackedActiveSpans1 = ImmutableArray.Create(activeStatementSpan11, activeStatementSpan12);
 
        var currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(document1, (_, _, _) => new(trackedActiveSpans1), CancellationToken.None);
        AssertEx.Equal(trackedActiveSpans1, currentSpans);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(documentId, sourceTextV2);
        var document2 = solution.GetDocument(documentId);
 
        // tracking span update triggered by the edit:
        var activeStatementSpan21 = new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan21, ActiveStatementFlags.NonLeafFrame);
        var activeStatementSpan22 = new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan22, ActiveStatementFlags.LeafFrame);
        var trackedActiveSpans2 = ImmutableArray.Create(activeStatementSpan21, activeStatementSpan22);
 
        currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(document2, (_, _, _) => new(trackedActiveSpans2), CancellationToken.None);
        AssertEx.Equal([adjustedActiveLineSpan1, adjustedActiveLineSpan2], currentSpans.Select(s => s.LineSpan));
    }
 
    [Theory, CombinatorialData]
    public async Task ActiveStatements_SyntaxErrorOrOutOfSyncDocument(bool isOutOfSync)
    {
        var sourceV1 = "class C { void F() => G(1); void G(int a) => System.Console.WriteLine(1); }";
 
        // syntax error (missing ';') unless testing out-of-sync document
        var sourceV2 = isOutOfSync
            ? "class C { int x; void F() => G(1); void G(int a) => System.Console.WriteLine(2); }"
            : "class C { int x void F() => G(1); void G(int a) => System.Console.WriteLine(2); }";
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, sourceV1);
 
        var activeSpan11 = GetSpan(sourceV1, "G(1)");
        var activeSpan12 = GetSpan(sourceV1, "System.Console.WriteLine(1)");
 
        var documentId = document1.Id;
        var documentFilePath = document1.FilePath;
 
        var sourceTextV1 = await document1.GetTextAsync(CancellationToken.None);
        var sourceTextV2 = CreateText(sourceV2);
 
        var activeLineSpan11 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan11);
        var activeLineSpan12 = sourceTextV1.Lines.GetLinePositionSpan(activeSpan12);
 
        var debuggingSession = await StartDebuggingSessionAsync(
            service,
            solution,
            isOutOfSync ? CommittedSolution.DocumentState.OutOfSync : CommittedSolution.DocumentState.MatchesBuildOutput);
 
        var moduleId = Guid.NewGuid();
        var activeInstruction1 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000001, version: 1), ilOffset: 1);
        var activeInstruction2 = new ManagedInstructionId(new ManagedMethodId(moduleId, token: 0x06000002, version: 1), ilOffset: 1);
 
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                activeInstruction1,
                documentFilePath,
                activeLineSpan11.ToSourceSpan(),
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame),
            new ManagedActiveStatementDebugInfo(
                activeInstruction2,
                documentFilePath,
                activeLineSpan12.ToSourceSpan(),
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        var baseSpans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single();
        AssertEx.Equal(
        [
            new ActiveStatementSpan(new ActiveStatementId(0), activeLineSpan11, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame),
            new ActiveStatementSpan(new ActiveStatementId(1), activeLineSpan12, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame)
        ], baseSpans);
 
        // change the source (valid edit):
        solution = solution.WithDocumentText(documentId, sourceTextV2);
        var document2 = solution.GetDocument(documentId);
 
        // no adjustments made due to syntax error or out-of-sync document:
        var currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(document2, (_, _, _) => ValueTaskFactory.FromResult(baseSpans), CancellationToken.None);
        AssertEx.Equal([activeLineSpan11, activeLineSpan12], currentSpans.Select(s => s.LineSpan));
    }
 
    [Theory, CombinatorialData]
    public async Task ActiveStatements_ForeignDocument(bool withPath, bool designTimeOnly)
    {
        using var _ = CreateWorkspace(out var solution, out var service, [typeof(NoCompilationLanguageService)]);
 
        var project = solution.AddProject("dummy_proj", "dummy_proj", designTimeOnly ? LanguageNames.CSharp : NoCompilationConstants.LanguageName);
        var filePath = withPath ? Path.Combine(TempRoot.Root, "test.cs") : null;
        var sourceText = CreateText("dummy1");
 
        var documentInfo = DocumentInfo.Create(
            DocumentId.CreateNewId(project.Id, "test"),
            name: "test",
            loader: TextLoader.From(TextAndVersion.Create(sourceText, VersionStamp.Create(), filePath)),
            filePath: filePath)
            .WithDesignTimeOnly(designTimeOnly);
 
        var document = project.Solution.AddDocument(documentInfo).GetDocument(documentInfo.Id);
 
        solution = document.Project.Solution;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        var activeStatements = ImmutableArray.Create(
            new ManagedActiveStatementDebugInfo(
                new ManagedInstructionId(new ManagedMethodId(Guid.Empty, token: 0x06000001, version: 1), ilOffset: 0),
                documentName: document.Name,
                sourceSpan: new SourceSpan(0, 1, 0, 2),
                ActiveStatementFlags.NonLeafFrame));
 
        EnterBreakState(debuggingSession, activeStatements);
 
        // active statements are not tracked in non-Roslyn projects:
        var currentSpans = await debuggingSession.GetAdjustedActiveStatementSpansAsync(document, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(currentSpans);
 
        var baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None);
        Assert.Empty(baseSpans.Single());
 
        // update solution:
        solution = solution.WithDocumentText(document.Id, CreateText("dummy2"));
 
        baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None);
        Assert.Empty(baseSpans.Single());
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/24320")]
    public async Task ActiveStatements_LinkedDocuments()
    {
        var markedSources = new[]
        {
@"class Test1
{
    static void Main() => <AS:2>Project2::Test1.F();</AS:2>
    static void F() => <AS:1>Project4::Test2.M();</AS:1>
}",
@"class Test2 { static void M() => <AS:0>Console.WriteLine();</AS:0> }"
        };
 
        var module1 = Guid.NewGuid();
        var module2 = Guid.NewGuid();
        var module4 = Guid.NewGuid();
 
        var debugInfos = GetActiveStatementDebugInfosCSharp(
            markedSources,
            methodRowIds: [1, 2, 1],
            modules: [module4, module2, module1]);
 
        // Project1: Test1.cs, Test2.cs
        // Project2: Test1.cs (link from P1)
        // Project3: Test1.cs (link from P1)
        // Project4: Test2.cs (link from P1)
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, SourceMarkers.Clear(markedSources));
 
        var documents = solution.Projects.Single().Documents;
        var doc1 = documents.First();
        var doc2 = documents.Skip(1).First();
        var text1 = await doc1.GetTextAsync();
        var text2 = await doc2.GetTextAsync();
 
        DocumentId AddProjectAndLinkDocument(string projectName, Document doc, SourceText text)
        {
            var p = solution.AddTestProject(projectName);
            var linkedDocId = DocumentId.CreateNewId(p.Id, projectName + "->" + doc.Name);
            solution = p.Solution.AddDocument(linkedDocId, doc.Name, text, filePath: doc.FilePath);
            return linkedDocId;
        }
 
        var docId3 = AddProjectAndLinkDocument("Project2", doc1, text1);
        var docId4 = AddProjectAndLinkDocument("Project3", doc1, text1);
        var docId5 = AddProjectAndLinkDocument("Project4", doc2, text2);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
        EnterBreakState(debuggingSession, debugInfos);
 
        // Base Active Statements
 
        var baseActiveStatementsMap = await debuggingSession.EditSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
        var documentMap = baseActiveStatementsMap.DocumentPathMap;
 
        Assert.Equal(2, documentMap.Count);
 
        AssertEx.Equal(
        [
            $"2: {doc1.FilePath}: (2,32)-(2,52) flags=[MethodUpToDate, NonLeafFrame]",
            $"1: {doc1.FilePath}: (3,29)-(3,49) flags=[MethodUpToDate, NonLeafFrame]"
        ], documentMap[doc1.FilePath].Select(InspectActiveStatement));
 
        AssertEx.Equal(
        [
            $"0: {doc2.FilePath}: (0,39)-(0,59) flags=[LeafFrame, MethodUpToDate]",
        ], documentMap[doc2.FilePath].Select(InspectActiveStatement));
 
        Assert.Equal(3, baseActiveStatementsMap.InstructionMap.Count);
 
        var statements = baseActiveStatementsMap.InstructionMap.Values.OrderBy(v => v.Id.Ordinal).ToArray();
        var s = statements[0];
        Assert.Equal(0x06000001, s.InstructionId.Method.Token);
        Assert.Equal(module4, s.InstructionId.Method.Module);
 
        s = statements[1];
        Assert.Equal(0x06000002, s.InstructionId.Method.Token);
        Assert.Equal(module2, s.InstructionId.Method.Module);
 
        s = statements[2];
        Assert.Equal(0x06000001, s.InstructionId.Method.Token);
        Assert.Equal(module1, s.InstructionId.Method.Module);
 
        var spans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, [doc1.Id, doc2.Id, docId3, docId4, docId5], CancellationToken.None);
 
        AssertEx.Equal(
        [
            "(2,32)-(2,52), (3,29)-(3,49)", // test1.cs
            "(0,39)-(0,59)",                // test2.cs
            "(2,32)-(2,52), (3,29)-(3,49)", // link test1.cs
            "(2,32)-(2,52), (3,29)-(3,49)", // link test1.cs
            "(0,39)-(0,59)"                 // link test2.cs
        ], spans.Select(docSpans => string.Join(", ", docSpans.Select(span => span.LineSpan))));
    }
 
    [Fact]
    public async Task ActiveStatements_OutOfSyncDocuments()
    {
        var markedSource1 =
@"class C
{
    static void M()
    {
        try
        {
        }
        catch (Exception e)
        {
            <AS:0>M();</AS:0>
        }
    }
}";
        var source2 =
@"class C
{
    static void M()
    {
        try
        {
        }
        catch (Exception e)
        {
 
            M();
        }
    }
}";
 
        var markedSources = new[] { markedSource1 };
 
        var thread1 = Guid.NewGuid();
 
        // Thread1 stack trace: F (AS:0 leaf)
 
        var debugInfos = GetActiveStatementDebugInfosCSharp(
            markedSources,
            methodRowIds: [1],
            ilOffsets: [1],
            flags:
            [
                ActiveStatementFlags.LeafFrame | ActiveStatementFlags.MethodUpToDate
            ]);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        solution = AddDefaultTestProject(solution, SourceMarkers.Clear(markedSources));
        var project = solution.Projects.Single();
        var document = project.Documents.Single();
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution, initialState: CommittedSolution.DocumentState.OutOfSync);
        EnterBreakState(debuggingSession, debugInfos);
 
        // update document to test a changed solution
        solution = solution.WithDocumentText(document.Id, CreateText(source2));
        document = solution.GetDocument(document.Id);
 
        var baseActiveStatementMap = await debuggingSession.EditSession.BaseActiveStatements.GetValueAsync(CancellationToken.None).ConfigureAwait(false);
 
        // Active Statements - available in out-of-sync documents, as they reflect the state of the debuggee and not the base document content
 
        Assert.Single(baseActiveStatementMap.DocumentPathMap);
 
        AssertEx.Equal(
        [
            $"0: {document.FilePath}: (9,18)-(9,22) flags=[LeafFrame, MethodUpToDate]",
        ], baseActiveStatementMap.DocumentPathMap[document.FilePath].Select(InspectActiveStatement));
 
        Assert.Equal(1, baseActiveStatementMap.InstructionMap.Count);
 
        var activeStatement1 = baseActiveStatementMap.InstructionMap.Values.OrderBy(v => v.InstructionId.Method.Token).Single();
        Assert.Equal(0x06000001, activeStatement1.InstructionId.Method.Token);
        Assert.Equal(document.FilePath, activeStatement1.FilePath);
        Assert.True(activeStatement1.IsLeaf);
 
        // Active statement reported as unchanged as the containing document is out-of-sync:
        var baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None);
        AssertEx.Equal([$"(9,18)-(9,22)"], baseSpans.Single().Select(s => s.LineSpan.ToString()));
 
        // Document got synchronized:
        debuggingSession.LastCommittedSolution.Test_SetDocumentState(document.Id, CommittedSolution.DocumentState.MatchesBuildOutput);
 
        // New location of the active statement reported:
        baseSpans = await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(document.Id), CancellationToken.None);
        AssertEx.Equal([$"(10,12)-(10,16)"], baseSpans.Single().Select(s => s.LineSpan.ToString()));
    }
 
    [Fact]
    public async Task ActiveStatements_SourceGeneratedDocuments_LineDirectives()
    {
        var markedSource1 = @"
/* GENERATE:
class C
{
    void F()
    {
#line 1 ""a.razor""
       <AS:0>F();</AS:0>
#line default
    }
}
*/
";
        var markedSource2 = @"
/* GENERATE:
class C
{
    void F()
    {
#line 2 ""a.razor""
       <AS:0>F();</AS:0>
#line default
    }
}
*/
";
        var source1 = SourceMarkers.Clear(markedSource1);
        var source2 = SourceMarkers.Clear(markedSource2);
 
        var additionalFileSourceV1 = @"
       xxxxxxxxxxxxxxxxx
";
 
        var generator = new TestSourceGenerator() { ExecuteImpl = GenerateSource };
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document1) = AddDefaultTestProject(solution, source1, generator, additionalFileText: additionalFileSourceV1);
 
        var generatedDocument1 = (await solution.Projects.Single().GetSourceGeneratedDocumentsAsync().ConfigureAwait(false)).Single();
 
        var moduleId = EmitLibrary(source1, generatorProject: document1.Project, additionalFileText: additionalFileSourceV1);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [GetGeneratedCodeFromMarkedSource(markedSource1)],
            filePaths: [generatedDocument1.FilePath],
            modules: [moduleId],
            methodRowIds: [1],
            methodVersions: [1],
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame
            ]));
 
        // change the source (valid edit)
        solution = solution.WithDocumentText(document1.Id, CreateText(source2));
 
        // validate solution update status and emit:
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        // check emitted delta:
        var delta = updates.Updates.Single();
        Assert.Empty(delta.ActiveStatements);
        Assert.NotEmpty(delta.ILDelta);
        Assert.NotEmpty(delta.MetadataDelta);
        Assert.NotEmpty(delta.PdbDelta);
        Assert.Empty(delta.UpdatedMethods);
        Assert.Empty(delta.UpdatedTypes);
 
        AssertEx.Equal(
        [
            "a.razor: [0 -> 1]"
        ], delta.SequencePoints.Inspect());
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/54347")]
    public async Task ActiveStatements_EncSessionFollowedByHotReload()
    {
        var markedSource1 = @"
class C
{
    int F()
    {
        try
        {
            return 0;
        }
        catch
        {
            <AS:0>return 1;</AS:0>
        }
    }
}
";
        var markedSource2 = @"
class C
{
    int F()
    {
        try
        {
            return 0;
        }
        catch
        {
            <AS:0>return 2;</AS:0>
        }
    }
}
";
        var source1 = SourceMarkers.Clear(markedSource1);
        var source2 = SourceMarkers.Clear(markedSource2);
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, source1);
 
        var moduleId = EmitLibrary(source1);
        LoadLibraryToDebuggee(moduleId);
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [markedSource1],
            modules: [moduleId],
            methodRowIds: [1],
            methodVersions: [1],
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame
            ]));
 
        // change the source (rude edit)
        solution = solution.WithDocumentText(document.Id, CreateText(source2));
        document = solution.GetDocument(document.Id);
 
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None);
        AssertEx.Equal(["ENC0063: " + string.Format(FeaturesResources.Updating_a_0_around_an_active_statement_requires_restarting_the_application, CSharpFeaturesResources.catch_clause)],
            diagnostics.Select(d => $"{d.Id}: {d.GetMessage()}"));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.RestartRequired, updates.Status);
 
        // undo the change
        solution = solution.WithDocumentText(document.Id, CreateText(source1));
        document = solution.GetDocument(document.Id);
 
        ExitBreakState(debuggingSession);
 
        // change the source (now a valid edit since there is no active statement)
        solution = solution.WithDocumentText(document.Id, CreateText(source2));
 
        diagnostics = await service.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        // validate solution update status and emit (Hot Reload change):
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        debuggingSession.DiscardSolutionUpdate();
        EndDebuggingSession(debuggingSession);
    }
 
    /// <summary>
    /// Scenario:
    /// F5 a program that has function F that calls G. G has a long-running loop, which starts executing.
    /// The user makes following operations:
    /// 1) Break, edit F from version 1 to version 2, continue (change is applied), G is still running in its loop
    ///    Function remapping is produced for F v1 -> F v2.
    /// 2) Hot-reload edit F (without breaking) to version 3.
    ///    Function remapping is not produced for F v2 -> F v3. If G ever returned to F it will be remapped from F v1 -> F v2,
    ///    where F v2 is considered stale code. This is consistent with the semantic of Hot Reload: Hot Reloaded changes do not have
    ///    an effect until the method is called again. In this case the method is not called, it it returned into hence the stale
    ///    version executes.
    /// 3) Break and apply EnC edit. This edit is to F v3 (Hot Reload) of the method. We will produce remapping F v3 -> v4.
    /// </summary>
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52100")]
    public async Task BreakStateRemappingFollowedUpByRunStateUpdate()
    {
        var markedSourceV1 =
@"class Test
{
    static bool B() => true;
 
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
        /*insert1[1]*/B();/*insert2[5]*/B();/*insert3[10]*/B();
        <AS:1>G();</AS:1>
    }
}";
        var markedSourceV2 = Update(markedSourceV1, marker: "1");
        var markedSourceV3 = Update(markedSourceV2, marker: "2");
        var markedSourceV4 = Update(markedSourceV3, marker: "3");
 
        var moduleId = EmitAndLoadLibraryToDebuggee(SourceMarkers.Clear(markedSourceV1));
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, SourceMarkers.Clear(markedSourceV1));
        var documentId = document.Id;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // EnC update F v1 -> v2
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [markedSourceV1],
            modules: [moduleId, moduleId],
            methodRowIds: [2, 3],
            methodVersions: [1, 1],
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame,    // G
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, // F
            ]));
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV2)));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
        Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        CommitSolutionUpdate(debuggingSession);
 
        AssertEx.Equal(
        [
            $"0x06000002 v1 | AS {document.FilePath}: (4,41)-(4,42) => (4,41)-(4,42)",
            $"0x06000003 v1 | AS {document.FilePath}: (9,14)-(9,18) => (10,14)-(10,18)",
        ], InspectNonRemappableRegions(debuggingSession.EditSession.NonRemappableRegions));
 
        ExitBreakState(debuggingSession);
 
        // Hot Reload update F v2 -> v3
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV3)));
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
        Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        CommitSolutionUpdate(debuggingSession);
 
        // the regions remain unchanged
        AssertEx.Equal(
        [
            $"0x06000002 v1 | AS {document.FilePath}: (4,41)-(4,42) => (4,41)-(4,42)",
            $"0x06000003 v1 | AS {document.FilePath}: (9,14)-(9,18) => (10,14)-(10,18)",
        ], InspectNonRemappableRegions(debuggingSession.EditSession.NonRemappableRegions));
 
        // EnC update F v3 -> v4
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [markedSourceV1],       // matches F v1
            modules: [moduleId, moduleId],
            methodRowIds: [2, 3],
            methodVersions: [1, 1], // frame F v1 is still executing (G has not returned)
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame,    // G
                ActiveStatementFlags.Stale | ActiveStatementFlags.NonLeafFrame,        // F - not up-to-date anymore and since F v1 is followed by F v3 (hot-reload) it is now stale
            ]));
 
        var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single();
        AssertEx.Equal(
        [
            new ActiveStatementSpan(new ActiveStatementId(0), new LinePositionSpan(new(4, 41), new(4, 42)), ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame),
        ], spans);
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSourceV4)));
 
        (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
        Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        CommitSolutionUpdate(debuggingSession);
 
        // Stale active statement region is gone.
        AssertEx.Equal(
        [
            $"0x06000002 v1 | AS {document.FilePath}: (4,41)-(4,42) => (4,41)-(4,42)",
        ], InspectNonRemappableRegions(debuggingSession.EditSession.NonRemappableRegions));
 
        ExitBreakState(debuggingSession);
    }
 
    /// <summary>
    /// Scenario:
    /// - F5
    /// - edit, but not apply the edits
    /// - break
    /// </summary>
    [Fact]
    public async Task BreakInPresenceOfUnappliedChanges()
    {
        var markedSource1 =
@"class Test
{
    static bool B() => true;
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
        <AS:1>G();</AS:1>
    }
}";
 
        var markedSource2 =
@"class Test
{
    static bool B() => true;
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
        B();
        <AS:1>G();</AS:1>
    }
}";
 
        var markedSource3 =
@"class Test
{
    static bool B() => true;
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
        B();
        B();
        <AS:1>G();</AS:1>
    }
}";
 
        var moduleId = EmitAndLoadLibraryToDebuggee(SourceMarkers.Clear(markedSource1));
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, SourceMarkers.Clear(markedSource1));
        var documentId = document.Id;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // Update to snapshot 2, but don't apply
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSource2)));
 
        // EnC update F v2 -> v3
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [markedSource1],
            modules: [moduleId, moduleId],
            methodRowIds: [2, 3],
            methodVersions: [1, 1],
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame,    // G
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, // F
            ]));
 
        // check that the active statement is mapped correctly to snapshot v2:
        var expectedSpanG1 = new LinePositionSpan(new LinePosition(3, 41), new LinePosition(3, 42));
        var expectedSpanF1 = new LinePositionSpan(new LinePosition(8, 14), new LinePosition(8, 18));
 
        var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single();
        AssertEx.Equal(
        [
            new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId),
            new ActiveStatementSpan(new ActiveStatementId(1), expectedSpanF1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId)
        ], spans);
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSource3)));
 
        // check that the active statement is mapped correctly to snapshot v3:
        var expectedSpanG2 = new LinePositionSpan(new LinePosition(3, 41), new LinePosition(3, 42));
        var expectedSpanF2 = new LinePositionSpan(new LinePosition(9, 14), new LinePosition(9, 18));
 
        spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single();
        AssertEx.Equal(
        [
            new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame, documentId),
            new ActiveStatementSpan(new ActiveStatementId(1), expectedSpanF2, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.NonLeafFrame, documentId)
        ], spans);
 
        // no rude edits:
        var document1 = solution.GetDocument(documentId);
        var diagnostics = await service.GetDocumentDiagnosticsAsync(document1, s_noActiveSpans, CancellationToken.None);
        Assert.Empty(diagnostics);
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
        Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        CommitSolutionUpdate(debuggingSession);
 
        AssertEx.Equal(
        [
            $"0x06000002 v1 | AS {document.FilePath}: (3,41)-(3,42) => (3,41)-(3,42)",
            $"0x06000003 v1 | AS {document.FilePath}: (7,14)-(7,18) => (9,14)-(9,18)",
        ], InspectNonRemappableRegions(debuggingSession.EditSession.NonRemappableRegions));
 
        ExitBreakState(debuggingSession);
    }
 
    /// <summary>
    /// Scenario:
    /// - F5
    /// - edit and apply edit that deletes non-leaf active statement
    /// - break
    /// </summary>
    [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/52100")]
    public async Task BreakAfterRunModeChangeDeletesNonLeafActiveStatement()
    {
        var markedSource1 =
@"class Test
{
    static bool B() => true;
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
        <AS:1>G();</AS:1>
    }
}";
 
        var markedSource2 =
@"class Test
{
    static bool B() => true;
    static void G() { while (B()); <AS:0>}</AS:0>
 
    static void F()
    {
    }
}";
        var moduleId = EmitAndLoadLibraryToDebuggee(SourceMarkers.Clear(markedSource1));
 
        using var _ = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, SourceMarkers.Clear(markedSource1));
        var documentId = document.Id;
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        // Apply update: F v1 -> v2.
 
        solution = solution.WithDocumentText(documentId, CreateText(SourceMarkers.Clear(markedSource2)));
 
        var (updates, emitDiagnostics) = await EmitSolutionUpdateAsync(debuggingSession, solution);
        Assert.Empty(emitDiagnostics);
        Assert.Equal(0x06000003, updates.Updates.Single().UpdatedMethods.Single());
        Assert.Equal(0x02000002, updates.Updates.Single().UpdatedTypes.Single());
        Assert.Equal(ModuleUpdateStatus.Ready, updates.Status);
 
        CommitSolutionUpdate(debuggingSession);
 
        // Break
 
        EnterBreakState(debuggingSession, GetActiveStatementDebugInfosCSharp(
            [markedSource1],
            modules: [moduleId, moduleId],
            methodRowIds: [2, 3],
            methodVersions: [1, 1],  // frame F v1 is still executing (G has not returned)
            flags:
            [
                ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame,    // G
                ActiveStatementFlags.NonLeafFrame, // F
            ]));
 
        // check that the active statement is mapped correctly to snapshot v2:
        var expectedSpanG1 = new LinePositionSpan(new LinePosition(3, 41), new LinePosition(3, 42));
 
        var spans = (await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray.Create(documentId), CancellationToken.None)).Single();
        AssertEx.Equal(
        [
            new ActiveStatementSpan(new ActiveStatementId(0), expectedSpanG1, ActiveStatementFlags.MethodUpToDate | ActiveStatementFlags.LeafFrame)
            // active statement in F has been deleted
        ], spans);
 
        ExitBreakState(debuggingSession);
    }
 
    [Fact]
    public async Task MultiSession()
    {
        var source1 = "class C { void M() { System.Console.WriteLine(); } }";
        var source3 = "class C { void M() { WriteLine(2); } }";
 
        var dir = Temp.CreateDirectory();
        var sourceFileA = dir.CreateFile("A.cs").WriteAllText(source1, Encoding.UTF8);
        var moduleId = EmitLibrary(source1, sourceFileA.Path, assemblyName: "Proj");
 
        using var _ = CreateWorkspace(out var solution, out var encService);
 
        var projectP = solution.
            AddTestProject("P").
            WithMetadataReferences(TargetFrameworkUtil.GetReferences(DefaultTargetFramework));
 
        solution = projectP.Solution;
 
        var documentIdA = DocumentId.CreateNewId(projectP.Id, debugName: "A");
        solution = solution.AddDocument(DocumentInfo.Create(
            id: documentIdA,
            name: "A",
            loader: new WorkspaceFileTextLoader(solution.Services, sourceFileA.Path, Encoding.UTF8),
            filePath: sourceFileA.Path));
 
        var tasks = Enumerable.Range(0, 10).Select(async i =>
        {
            var sessionId = await encService.StartDebuggingSessionAsync(
                solution,
                _debuggerService,
                NullPdbMatchingSourceTextProvider.Instance,
                captureMatchingDocuments: ImmutableArray<DocumentId>.Empty,
                captureAllMatchingDocuments: true,
                reportDiagnostics: true,
                CancellationToken.None);
 
            var solution1 = solution.WithDocumentText(documentIdA, CreateText("class C { void M() { System.Console.WriteLine(" + i + "); } }"));
 
            var result1 = await encService.EmitSolutionUpdateAsync(sessionId, solution1, s_noActiveSpans, CancellationToken.None);
            Assert.Empty(result1.Diagnostics);
            Assert.Equal(1, result1.ModuleUpdates.Updates.Length);
            encService.DiscardSolutionUpdate(sessionId);
 
            var solution2 = solution1.WithDocumentText(documentIdA, CreateText(source3));
 
            var result2 = await encService.EmitSolutionUpdateAsync(sessionId, solution2, s_noActiveSpans, CancellationToken.None);
            Assert.Equal("CS0103", result2.Diagnostics.Single().Diagnostics.Single().Id);
            Assert.Empty(result2.ModuleUpdates.Updates);
 
            encService.EndDebuggingSession(sessionId);
        });
 
        await Task.WhenAll(tasks);
 
        Assert.Empty(encService.GetTestAccessor().GetActiveDebuggingSessions());
    }
 
    [Fact]
    public async Task Disposal()
    {
        using var _1 = CreateWorkspace(out var solution, out var service);
        (solution, var document) = AddDefaultTestProject(solution, "class C { }");
 
        var debuggingSession = await StartDebuggingSessionAsync(service, solution);
 
        EndDebuggingSession(debuggingSession);
 
        // The folling methods shall not be called after the debugging session ended.
        await Assert.ThrowsAsync<ObjectDisposedException>(async () => await debuggingSession.EmitSolutionUpdateAsync(solution, s_noActiveSpans, CancellationToken.None));
        Assert.Throws<ObjectDisposedException>(() => debuggingSession.BreakStateOrCapabilitiesChanged(inBreakState: true));
        Assert.Throws<ObjectDisposedException>(() => debuggingSession.DiscardSolutionUpdate());
        Assert.Throws<ObjectDisposedException>(() => debuggingSession.CommitSolutionUpdate());
        Assert.Throws<ObjectDisposedException>(() => debuggingSession.EndSession(out _));
 
        // The following methods can be called at any point in time, so we must handle race with dispose gracefully.
        Assert.Empty(await debuggingSession.GetDocumentDiagnosticsAsync(document, s_noActiveSpans, CancellationToken.None));
        Assert.Empty(await debuggingSession.GetAdjustedActiveStatementSpansAsync(document, s_noActiveSpans, CancellationToken.None));
        Assert.True((await debuggingSession.GetBaseActiveStatementSpansAsync(solution, ImmutableArray<DocumentId>.Empty, CancellationToken.None)).IsDefault);
    }
}