XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFESystem

【Rust】XFE系统,基于Rust和汇编

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Text.Json;
using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;
using Xunit.Abstractions;

namespace XFESystem.Desktop.Tests;

/// <summary>Actual packaged Monaco/Jint on a dedicated host thread. The script
/// gate proves ownership isolation, not native preemption or guest timings.</summary>
public sealed class BackgroundEditorTests(ITestOutputHelper output)
{
    private const string Source = "using System;\n\npublic class BackgroundWidget\n{\n    public int Value = 42;\n}\n";

    [Fact]
    public void ActualCoreInitializationDoesNotOwnTheDesktopAndTransfersARealCSharpModel()
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("Worker.cs", Source);
        fixture.WaitForCore();
        Assert.True(editor.IsEditorPreparing);
        Assert.Null(editor.BrowserSession);
        Assert.Equal(Source, editor.Content);
        Assert.Equal(0, fixture.Canvas.Presents);
        Assert.DoesNotContain(fixture.Trace, s => s.Contains("|SourcePresented|", StringComparison.Ordinal));

        fixture.Desktop.Render();
        Assert.True(fixture.Desktop.LastFrameCompleted);
        string sourceFrame = Assert.Single(fixture.Trace, s => s.Contains("|SourcePresented|", StringComparison.Ordinal));
        Assert.Contains("|SourceCharacters|" + Source.Length + "|", sourceFrame, StringComparison.Ordinal);
        Assert.Contains("|VisibleNonEmptyLines|5|", sourceFrame, StringComparison.Ordinal);
        Assert.Contains("|SourceFingerprint|" + Fingerprint(Source) + "|", sourceFrame, StringComparison.Ordinal);
        Assert.DoesNotContain(fixture.Trace, s => s.StartsWith("XFE_EDITOR_BACKGROUND|Ready|", StringComparison.Ordinal));
        fixture.Desktop.ShowDesktop();
        fixture.Desktop.AdvanceFeedback(17);
        fixture.Desktop.Render();
        Assert.Null(fixture.Desktop.ActiveWindow);
        fixture.Desktop.ShowDesktop();
        fixture.Desktop.Render();
        Assert.Same(editor, fixture.Desktop.ActiveWindow);
        var bounds = editor.Bounds;
        int x = bounds.X + bounds.Width / 2, y = bounds.Y + 14;
        fixture.Desktop.Pointer(x, y, true, timestampMs: 1000);
        fixture.Desktop.Pointer(x + 27, y + 13, true, timestampMs: 1020);
        fixture.Desktop.Pointer(x + 27, y + 13, false, timestampMs: 1040);
        fixture.Desktop.Render();
        Assert.NotEqual(bounds, editor.Bounds);
        Assert.Equal(Source, editor.Content);
        Assert.Single(fixture.Trace, s => s.Contains("|SourcePresented|", StringComparison.Ordinal));
        Assert.False(fixture.ReleaseCore.IsSet); // All desktop operations happened with Jint still held.

        fixture.ReleaseCore.Set();
        fixture.WaitReady(editor);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
        Assert.NotEqual(fixture.UiThread, fixture.WorkerThread);
        Assert.Equal(Source, page.ReadEditorText());
        using var proof = JsonDocument.Parse(page.Diagnostics.MonacoProof);
        Assert.Equal("csharp", proof.RootElement.GetProperty("Language").GetString());
        Assert.Equal(Source, proof.RootElement.GetProperty("ModelValue").GetString());
        Assert.Contains(proof.RootElement.GetProperty("ViewLines").EnumerateArray(), line => line.GetProperty("Text").GetString()!.Contains("BackgroundWidget", StringComparison.Ordinal));
        Assert.True(proof.RootElement.GetProperty("ViewLines").EnumerateArray().SelectMany(line => line.GetProperty("Spans").EnumerateArray()).Select(span => span.GetProperty("Color").GetString()).Distinct().Count() > 1);
        Assert.Contains(fixture.Trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_END|", StringComparison.Ordinal) && line.Contains("EditorCore.js|Success|True", StringComparison.Ordinal));
        string frameReady = Assert.Single(fixture.Trace, line => line.StartsWith("XFE_EDITOR_BACKGROUND|FrameReady|", StringComparison.Ordinal));
        int readyPumps = int.Parse(frameReady.Split("|InitializationPumps|", StringSplitOptions.None)[1].Trim(), System.Globalization.CultureInfo.InvariantCulture);
        Assert.InRange(readyPumps, 0, 7);
        output.WriteLine(frameReady);
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.BrowserKey(new("keyup", "/", "Slash"));
        fixture.Desktop.Key(DesktopKey.Save);
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/" + Source && !editor.IsDirty);
        Assert.Equal("/" + Source, fixture.Storage.ReadText(editor.Location));
        Assert.All(fixture.PreparationCanvases, canvas => Assert.True(canvas.MeasureCalls > 0));
        Assert.Single(fixture.PreparationCanvases, canvas => canvas.HandedOff);
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void TwoPendingWindowsCancelAndEditedSourceIsRequeuedWithoutLoss()
    {
        using var fixture = new Fixture();
        var first = fixture.Open("First.cs", Source);
        fixture.WaitForCore();
        long originalGeneration = first.EditorPreparationGeneration;
        var second = fixture.Open("Second.cs", "public class Second { }");
        Assert.True(second.IsEditorPreparing);
        fixture.Desktop.Close(second.Id);
        Assert.DoesNotContain(second, fixture.Desktop.Windows);
        fixture.Desktop.Activate(first.Id);
        fixture.Desktop.Key(DesktopKey.Character, '/');
        fixture.Desktop.Key(DesktopKey.Save);
        string latest = "/" + Source;
        Assert.Equal(latest, fixture.Storage.ReadText(first.Location));
        fixture.ReleaseCore.Set();
        fixture.WaitReady(first);

        Assert.True(first.EditorPreparationGeneration > originalGeneration);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(first.BrowserSession);
        Assert.Equal(latest, first.Content);
        Assert.Equal(latest, page.ReadEditorText());
        Assert.Equal(latest, fixture.Storage.ReadText(first.Location));
        Assert.DoesNotContain(second, fixture.Desktop.Windows);
        Assert.Contains(fixture.Trace, s => s.Contains("|Superseded|Window|" + first.Id + "|", StringComparison.Ordinal));
        Assert.Contains(fixture.Trace, s => s.Contains("|Cancel|Window|" + second.Id + "|", StringComparison.Ordinal));
        Assert.DoesNotContain(fixture.Trace, s => s.Contains("|Ready|Window|" + second.Id + "|", StringComparison.Ordinal));
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void RejectedWorkerNeverConstructsAFontCanvasOrRunsJintOnTheCaller()
    {
        using var canvas = new WindowsTextCanvas(FontPath, 1280, 800);
        var resources = new NeverCanvasFactory();
        var browser = new BrowserService();
        var service = new BrowserEditorPreparationService(browser, new RejectScheduler(), resources);
        var storage = new MemoryWorkspace(seed: false);
        string path = SystemPaths.Combine(SystemPaths.Documents, "Rejected.cs");
        storage.WriteText(path, Source);
        var desktop = new DesktopSession(canvas, storage, browser, editorPreparation: service);
        var editor = desktop.Open(DesktopApplication.Editor, path);
        desktop.AdvanceFeedback(17);
        desktop.Render();
        Assert.False(editor.IsEditorPreparing);
        Assert.Null(editor.BrowserSession);
        Assert.Contains("verified background editor worker is unavailable", editor.Status, StringComparison.Ordinal);
        Assert.Equal(Source, editor.Content);
        desktop.Key(DesktopKey.Character, '/');
        desktop.Key(DesktopKey.Save);
        Assert.Equal("/" + Source, storage.ReadText(path));
        Assert.Equal(0, resources.Calls);
    }

    [Fact]
    public void WorkerFontFailureKeepsTheSourceAndTheSameWorkerCanPrepareTheNextEditor()
    {
        using var fixture = new Fixture(failFirstCanvas: true);
        var failed = fixture.Open("Unavailable.cs", Source);
        fixture.WaitCompleted(failed);
        Assert.Null(failed.BrowserSession);
        Assert.Equal(Source, failed.Content);
        Assert.Contains("private font setup failed", failed.Status, StringComparison.Ordinal);
        fixture.Desktop.Key(DesktopKey.Character, '/');
        fixture.Desktop.Key(DesktopKey.Save);
        Assert.Equal("/" + Source, fixture.Storage.ReadText(failed.Location));
        fixture.Desktop.Close(failed.Id);
        var next = fixture.Open("Recovered.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(next);
        Assert.Equal(Source, Assert.IsAssignableFrom<IBackgroundEditorSession>(next.BrowserSession).ReadEditorText());
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void AbortedBasicSourceFrameIsNotReportedAndCompletedProofIsEmittedOnce()
    {
        var canvas = new AbortingCanvas();
        var storage = new MemoryWorkspace(seed: false);
        string path = SystemPaths.Combine(SystemPaths.Documents, "Yield.cs");
        storage.WriteText(path, Source);
        var trace = new List<string>();
        var desktop = new DesktopSession(canvas, storage, new BrowserService(), editorPreparation: new PendingService(), threadIdentity: () => 71) { Trace = trace.Add };
        desktop.Open(DesktopApplication.Editor, path);
        desktop.Render();
        Assert.False(desktop.LastFrameCompleted);
        Assert.Empty(trace);
        Assert.Equal(0, canvas.Presents);
        canvas.Abort = false;
        desktop.Render();
        Assert.True(desktop.LastFrameCompleted);
        string proof = Assert.Single(trace);
        Assert.Contains("|SourcePresented|", proof, StringComparison.Ordinal);
        Assert.Contains("|UiThread|71|", proof, StringComparison.Ordinal);
        Assert.Contains("|SourceFingerprint|" + Fingerprint(Source) + "|", proof, StringComparison.Ordinal);
        desktop.Pointer(1000, 650, false);
        desktop.Render();
        Assert.Single(trace);
    }

    [Fact]
    public void ActualMonacoDeferredInitializationUsesTheDesktopClockOnTheWorker()
    {
        using var fixture = new Fixture(sharedClock: true, initialMilliseconds: 27_457);
        var editor = fixture.Open("LateClock.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
        Assert.NotEmpty(fixture.WorkerPumpTimes);
        Assert.All(fixture.WorkerPumpTimes, time => Assert.True(time >= 27_457));
        Assert.Equal(Source, page.ReadEditorText());
        var delivered = Assert.Single(fixture.PreparationCanvases, canvas => canvas.HandedOff);
        Assert.True(delivered.PreparedTextCalls > 0);
        long layout = page.Diagnostics.LayoutPassCount;
        fixture.Desktop.Render();
        Assert.Equal(layout, page.Diagnostics.LayoutPassCount);
    }

    [Fact]
    public void UnverifiedCanvasChangeStillInvalidatesThePreparedLayout()
    {
        using var canvas = new WindowsTextCanvas(FontPath, 1280, 800);
        var page = new BrowserService(); page.SetCanvas(canvas); page.ResizeViewport(796, 404);
        page.NavigateEditor(Source, "DifferentMetrics.cs"); page.Layout(new(796, 404));
        long before = page.LayoutPassCount;
        using var other = new WindowsTextCanvas(FontPath, 1280, 800);
        page.CompleteEditorPreparationHandoff(other, equivalentTextMetrics: false);
        page.Layout(new(796, 404));
        Assert.Equal(before + 1, page.LayoutPassCount);
    }

    [Fact]
    public void OngoingWorkerPumpCannotBlockSnapshotPaintingAndSaveFlushesQueuedInputInOrder()
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("Ongoing.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
        fixture.GateNextPump(); fixture.Tick();
        fixture.WaitUntil(() => fixture.PumpEntered.IsSet);
        fixture.Desktop.Render();
        fixture.Desktop.ShowDesktop(); fixture.Desktop.Render(); Assert.Null(fixture.Desktop.ActiveWindow);
        fixture.Desktop.ShowDesktop(); fixture.Desktop.Render(); Assert.Same(editor, fixture.Desktop.ActiveWindow);
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.BrowserKey(new("keyup", "/", "Slash"));
        fixture.Desktop.Key(DesktopKey.Save);
        Assert.Equal(Source, fixture.Storage.ReadText(editor.Location));
        Assert.True(editor.IsDirty);
        fixture.Desktop.Close(editor.Id); Assert.Contains(editor, fixture.Desktop.Windows);
        Assert.Equal(Source, page.ReadEditorText());
        Assert.False(fixture.ReleasePump.IsSet);
        fixture.ReleasePump.Set();
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/" + Source && !editor.IsDirty);
        Assert.Equal("/" + Source, page.ReadEditorText());
        fixture.Desktop.Close(editor.Id); Assert.DoesNotContain(editor, fixture.Desktop.Windows);
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void PointerFloodCoalescesAndSavingNeverClearsEditsQueuedAfterItsFlush()
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("Ordered.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
        fixture.GateNextPump(); fixture.WaitUntil(() => fixture.PumpEntered.IsSet);
        for (int index = 0; index < 10_000; index++) Assert.True(page.Pointer(new("mousemove", index % 5 + 10, 10)));
        Assert.Null(page.Diagnostics.Error);
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.Key(DesktopKey.Save);
        fixture.Desktop.BrowserKey(new("keydown", "x", "KeyX", "x"));
        fixture.ReleasePump.Set();
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/" + Source && page.ReadEditorText() == "/x" + Source);
        Assert.True(editor.IsDirty);
        fixture.Desktop.Key(DesktopKey.Save);
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/x" + Source && !editor.IsDirty);
        Assert.Contains(fixture.Trace, line => line == "XFE_DESKTOP_SAVED|" + editor.Location + "|" + (Source.Length + 1) + "\n");
        Assert.Contains(fixture.Trace, line => line == "XFE_DESKTOP_SAVED|" + editor.Location + "|" + (Source.Length + 2) + "\n");
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void PreviewFlushesQueuedSourceAndCanOpenItsWindowDuringTheUiPoll()
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("PreviewQueued.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        fixture.GateNextPump(); fixture.WaitUntil(() => fixture.PumpEntered.IsSet);
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.Key(DesktopKey.Run);
        Assert.Same(editor, fixture.Desktop.ActiveWindow);
        fixture.ReleasePump.Set();
        fixture.WaitUntil(() => fixture.Desktop.ActiveWindow?.Application == DesktopApplication.Browser);
        var previewWindow = fixture.Desktop.ActiveWindow!;
        fixture.WaitReady(previewWindow);
        var preview = Assert.IsAssignableFrom<IBackgroundEditorSession>(previewWindow.BrowserSession);
        Assert.Contains("/" + Source.TrimEnd(), preview.CurrentPage.Text, StringComparison.Ordinal);
        Assert.Null(preview.ReadEditorText());
        Assert.Equal(Source, fixture.Storage.ReadText(editor.Location));
        Assert.True(editor.IsDirty);
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void CancellationAtTheActualCoreScriptBoundaryStopsFurtherScriptsAndDeliveryFonts()
    {
        using var fixture = new Fixture();
        var cancelled = fixture.Open("CancelledBoundary.cs", Source);
        fixture.WaitForCore();
        fixture.Desktop.Close(cancelled.Id); fixture.ReleaseCore.Set();
        fixture.WaitUntil(() => fixture.Trace.Any(line => line.StartsWith("XFE_EDITOR_BACKGROUND|Cancelled|", StringComparison.Ordinal)));
        Assert.DoesNotContain(cancelled, fixture.Desktop.Windows);
        Assert.Single(fixture.PreparationCanvases);
        Assert.DoesNotContain(fixture.Trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_END|", StringComparison.Ordinal) && line.Contains("EditorCore.js", StringComparison.Ordinal));
        Assert.DoesNotContain(fixture.Trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN|", StringComparison.Ordinal) && line.Contains("WebsiteMonaco.js", StringComparison.Ordinal));
        Assert.DoesNotContain(fixture.Trace, line => line.StartsWith("XFE_EDITOR_BACKGROUND|Failed|", StringComparison.Ordinal));
        Assert.Equal(Source, fixture.Storage.ReadText(cancelled.Location));
        var recovered = fixture.Open("AfterCancellation.cs", Source); fixture.WaitReady(recovered);
        Assert.Equal(Source, recovered.BrowserSession!.ReadEditorText());
        Assert.Empty(fixture.WorkerErrors);
    }

    [Theory]
    [InlineData("Empty.cs", "")]
    [InlineData("Spacing.cs", " \t\n\t\n")]
    [InlineData("TabUnicode.cs", "// 中文😀\r\npublic sealed class Tabbed\r\n{\r\n\tpublic int Value = 42;\r\n}\r\n")]
    [InlineData("Actual.js", "const value = 42;\nconsole.log(value);\n")]
    public void RealViewReadinessPreservesEmptySpacingUnicodeAndJavaScript(string name, string source)
    {
        using var fixture = new Fixture();
        var editor = fixture.Open(name, source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        Assert.Equal(source, Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession).ReadEditorText());
        Assert.Contains(fixture.Trace, line => line.StartsWith("XFE_EDITOR_BACKGROUND|FrameReady|", StringComparison.Ordinal));
        Assert.DoesNotContain(fixture.Trace, line => line.StartsWith("XFE_EDITOR_BACKGROUND|Failed|", StringComparison.Ordinal));
        Assert.Empty(fixture.WorkerErrors);
    }

    [Fact]
    public void MarkdownEditUsesARealPlainTextModelAndKeepsPreviewOnItsOwnWorkerPage()
    {
        using var fixture = new Fixture();
        const string source = "# Markdown heading\n\n**Original** source with `code` and 中文.\n";
        var editor = fixture.Open("Actual.md", source);
        fixture.Desktop.SetMarkdownView(editor.Id, MarkdownViewMode.Edit);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
        // C# palette diagnostics must not mislabel a plain-text model. The
        // production readiness gate separately requires language=plaintext and
        // the exact real model/view before it can emit FrameReady.
        Assert.Equal("{}", page.Diagnostics.MonacoProof);
        Assert.Contains(fixture.Trace, line => line.StartsWith("XFE_EDITOR_BACKGROUND|FrameReady|", StringComparison.Ordinal));
        Assert.Equal(source, page.ReadEditorText());
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.Key(DesktopKey.Save);
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/" + source && !editor.IsDirty);
        fixture.Desktop.SetMarkdownView(editor.Id, MarkdownViewMode.Split);
        fixture.WaitUntil(() => editor.MarkdownView == MarkdownViewMode.Split && editor.PreviewSession is IBackgroundEditorSession preview && preview.CurrentPage.Text.Contains("/# Markdown heading", StringComparison.Ordinal));
        Assert.NotSame(editor.BrowserSession, editor.PreviewSession);
        Assert.Null(editor.PreviewSession!.ReadEditorText());
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.WaitUntil(() => editor.PreviewSession is IBackgroundEditorSession preview && preview.CurrentPage.Text.Contains("//# Markdown heading", StringComparison.Ordinal));
        Assert.Equal("/" + source, fixture.Storage.ReadText(editor.Location));
        Assert.True(editor.IsDirty);
        fixture.Desktop.Render(); Assert.True(fixture.Desktop.LastFrameCompleted);
        Assert.Empty(fixture.WorkerErrors);
    }

    [Theory]
    [InlineData("z", "KeyZ")]
    [InlineData("y", "KeyY")]
    [InlineData("x", "KeyX")]
    [InlineData("v", "KeyV")]
    public void EmptyTextControlKeysCannotBeClearedByAnEarlierSaveAcknowledgement(string key, string code)
    {
        using var fixture = new Fixture();
        using var barrierEntered = new ManualResetEventSlim();
        using var releaseBarrier = new ManualResetEventSlim();
        try
        {
            var editor = fixture.Open("ControlRace.cs", Source);
            fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
            var page = Assert.IsAssignableFrom<IBackgroundEditorSession>(editor.BrowserSession);
            fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
            fixture.Desktop.BrowserKey(new("keyup", "/", "Slash"));
            fixture.WaitUntil(() => page.ReadEditorText() == "/" + Source);
            fixture.WaitForWorkerIdle();
            fixture.Desktop.Key(DesktopKey.Save);
            Assert.True(fixture.TryQueue(() => { barrierEntered.Set(); if (!releaseBarrier.Wait(TimeSpan.FromSeconds(60))) throw new TimeoutException("Control-key test barrier timed out."); }));
            Assert.True(barrierEntered.Wait(TimeSpan.FromSeconds(30)));
            // The real FIFO save readback has finished, but its callback has
            // not run on the UI. This control event remains behind the barrier.
            Assert.True(fixture.Desktop.BrowserKey(new("keydown", key, code, Control: true)));
            fixture.Tick();
            Assert.Equal("/" + Source, fixture.Storage.ReadText(editor.Location));
            Assert.Equal("/" + Source, page.ReadEditorText());
            Assert.True(editor.IsDirty);
            fixture.Desktop.Close(editor.Id); Assert.Contains(editor, fixture.Desktop.Windows);
            releaseBarrier.Set();
            if (key == "z") fixture.WaitUntil(() => page.ReadEditorText() == Source); // Actual Monaco undo, not a simulated model.
            else fixture.WaitForWorkerIdle();
            Assert.True(editor.IsDirty);
            Assert.Empty(fixture.WorkerErrors);
        }
        finally { releaseBarrier.Set(); }
    }

    [Fact]
    public void ClosingAReadySessionCancelsTheActualNextScriptBoundaryAndQueuedResource()
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("ReadyCancellation.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        fixture.QueueCancellationProbe();
        Assert.True(fixture.ProbeEntered.Wait(TimeSpan.FromSeconds(30)));
        fixture.Desktop.Close(editor.Id); Assert.DoesNotContain(editor, fixture.Desktop.Windows);
        fixture.ReleaseProbe.Set();
        Assert.True(fixture.ProbeDone.Wait(TimeSpan.FromSeconds(30)));
        Assert.True(fixture.ProbeCancelled);
        Assert.False(fixture.ProbeExecuted);
        Assert.Equal(0, fixture.ProbeSecondResourceReads);
        Assert.DoesNotContain(fixture.Trace, line => line.StartsWith("XFE_BROWSER_SCRIPT_END|", StringComparison.Ordinal) && line.Contains("AfterReadyCancelA.js", StringComparison.Ordinal));
        Assert.Empty(fixture.WorkerErrors);
    }

    [Theory]
    [InlineData("Alt", "AltLeft", false, true)]
    [InlineData("Control", "ControlLeft", true, false)]
    [InlineData("Shift", "ShiftLeft", true, true)]
    [InlineData("Meta", "MetaLeft", true, true)]
    [InlineData("CapsLock", "CapsLock", true, true)]
    [InlineData("NumLock", "NumLock", true, true)]
    [InlineData("ScrollLock", "ScrollLock", true, true)]
    public void PureModifierAfterAnActualSaveCannotDirtyTheDocumentOrBlockSystemClose(string key, string code, bool control, bool alt)
    {
        using var fixture = new Fixture();
        var editor = fixture.Open("ModifierClose.cs", Source);
        fixture.WaitForCore(); fixture.ReleaseCore.Set(); fixture.WaitReady(editor);
        fixture.Desktop.BrowserKey(new("keydown", "/", "Slash", "/"));
        fixture.Desktop.Key(DesktopKey.Save);
        fixture.WaitUntil(() => fixture.Storage.ReadText(editor.Location) == "/" + Source && !editor.IsDirty);
        Assert.True(fixture.Desktop.BrowserKey(new("keydown", key, code, Shift: key == "Shift", Control: control, Alt: alt)));
        Assert.False(editor.IsDirty);
        fixture.Desktop.Key(DesktopKey.CloseWindow);
        Assert.DoesNotContain(editor, fixture.Desktop.Windows);
        Assert.Equal("/" + Source, fixture.Storage.ReadText(editor.Location));
        Assert.Empty(fixture.WorkerErrors);
    }

    private static string FontPath => Path.Combine(AppContext.BaseDirectory, "TestAssets/Font.ttf");
    private static string Fingerprint(string source)
    {
        ulong hash = 14695981039346656037;
        foreach (char c in source) { hash = unchecked((hash ^ (byte)c) * 1099511628211); hash = unchecked((hash ^ (byte)(c >> 8)) * 1099511628211); }
        return hash.ToString("x16");
    }

    private sealed class Fixture : IDisposable, IEditorBackgroundWorkScheduler, IEditorPreparationCanvasFactory
    {
        public readonly MemoryWorkspace Storage = new(seed: false);
        public readonly WindowsTextCanvas Canvas = new(FontPath, 1280, 800);
        public readonly ConcurrentQueue<string> Trace = new();
        public readonly ConcurrentQueue<Exception> WorkerErrors = new();
        public readonly ConcurrentQueue<PreparationCanvas> PreparationCanvases = new();
        public readonly ConcurrentQueue<long> WorkerPumpTimes = new();
        public readonly ManualResetEventSlim CoreEntered = new(), ReleaseCore = new();
        public readonly ManualResetEventSlim PumpEntered = new(), ReleasePump = new(true);
        public readonly ManualResetEventSlim ProbeEntered = new(), ReleaseProbe = new(), ProbeDone = new();
        public bool ProbeCancelled, ProbeExecuted;
        public int ProbeSecondResourceReads => resources.SecondReads;
        public readonly DesktopSession Desktop;
        public readonly int UiThread = Environment.CurrentManagedThreadId;
        public int WorkerThread;
        private readonly BlockingCollection<Action> queue = new(12);
        private readonly Thread worker;
        private int gated;
        private int failFirstCanvas;
        private int gatePump;
        private int outstanding;
        private long now;
        private BrowserService? backend;
        private readonly CancellationProbeResources resources = new();
        public Fixture(bool failFirstCanvas = false, bool sharedClock = false, long initialMilliseconds = 0)
        {
            this.failFirstCanvas = failFirstCanvas ? 1 : 0;
            now = initialMilliseconds;
            var browser = new BrowserService(resources: resources) { Trace = line =>
            {
                Trace.Enqueue(line);
                if (line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN|", StringComparison.Ordinal) && line.Contains("AfterReadyCancelA.js", StringComparison.Ordinal))
                { ProbeEntered.Set(); if (!ReleaseProbe.Wait(TimeSpan.FromSeconds(60))) throw new TimeoutException("Ready-cancel script boundary timed out."); }
                if (line.StartsWith("XFE_BROWSER_PUMP_BEGIN|", StringComparison.Ordinal) && Environment.CurrentManagedThreadId != UiThread)
                {
                    WorkerPumpTimes.Enqueue(long.Parse(line.Split('|')[1], System.Globalization.CultureInfo.InvariantCulture));
                    if (Interlocked.Exchange(ref gatePump, 0) != 0)
                    { PumpEntered.Set(); if (!ReleasePump.Wait(TimeSpan.FromSeconds(60))) throw new TimeoutException("Post-ready worker gate timed out."); }
                }
                if (line.StartsWith("XFE_BROWSER_SCRIPT_BEGIN|", StringComparison.Ordinal) && line.Contains("EditorCore.js", StringComparison.Ordinal) && Interlocked.CompareExchange(ref gated, 1, 0) == 0)
                {
                    WorkerThread = Environment.CurrentManagedThreadId;
                    if (WorkerThread == UiThread) throw new InvalidOperationException("Core evaluation ran on the desktop thread.");
                    CoreEntered.Set();
                    if (!ReleaseCore.Wait(TimeSpan.FromSeconds(60))) throw new TimeoutException("Test core gate was not released.");
                }
            } };
            var service = new BrowserEditorPreparationService(new CaptureFactory(browser, page => backend = page), this, this, Trace.Enqueue,
                monotonicMilliseconds: sharedClock ? () => Volatile.Read(ref now) : null);
            Desktop = new(Canvas, Storage, browser, monotonicMilliseconds: () => now, editorPreparation: service) { Trace = Trace.Enqueue };
            worker = new Thread(() =>
            {
                foreach (var action in queue.GetConsumingEnumerable())
                { try { action(); } catch (Exception error) { WorkerErrors.Enqueue(error); } Interlocked.Decrement(ref outstanding); }
            }, 8 * 1024 * 1024) { IsBackground = true, Name = "RealMonacoHostWorker" };
            worker.Start();
        }
        public bool TryQueue(Action work)
        {
            Interlocked.Increment(ref outstanding);
            if (queue.TryAdd(work)) return true;
            Interlocked.Decrement(ref outstanding); return false;
        }
        public void WaitForWorkerIdle()
        {
            var timer = Stopwatch.StartNew();
            while (Volatile.Read(ref outstanding) != 0 && timer.Elapsed < TimeSpan.FromSeconds(60)) Thread.Sleep(5);
            Assert.Equal(0, Volatile.Read(ref outstanding));
        }
        public void QueueCancellationProbe() => Assert.True(TryQueue(() =>
        {
            try { backend!.ExecuteJavaScript("for(const name of ['AfterReadyCancelA.js','AfterReadyCancelB.js']){const s=document.createElement('script');s.src='/Browser/'+name;document.head.appendChild(s)}"); }
            catch (OperationCanceledException) { ProbeCancelled = true; }
            ProbeExecuted = backend!.Document!.Body!.HasAttribute("data-after-close");
            ProbeDone.Set();
        }));
        public IDesktopCanvas CreateEditorPreparationCanvas()
        {
            Assert.NotEqual(UiThread, Environment.CurrentManagedThreadId);
            if (Interlocked.Exchange(ref failFirstCanvas, 0) != 0) throw new IOException("private font setup failed");
            var canvas = new PreparationCanvas(); PreparationCanvases.Enqueue(canvas); return canvas;
        }
        public DesktopWindow Open(string name, string source)
        {
            string path = SystemPaths.Combine(SystemPaths.Documents, name); Storage.WriteText(path, source);
            return Desktop.Open(DesktopApplication.Editor, path);
        }
        public void WaitForCore() => Assert.True(CoreEntered.Wait(TimeSpan.FromSeconds(30)), string.Join("", Trace));
        public void GateNextPump() { ReleasePump.Reset(); Interlocked.Exchange(ref gatePump, 1); }
        public void Tick() { now += 67; Desktop.AdvanceFeedback(now); }
        public void WaitUntil(Func<bool> condition)
        {
            var timer = Stopwatch.StartNew();
            while (!condition() && timer.Elapsed < TimeSpan.FromSeconds(60)) { Tick(); Thread.Sleep(5); }
            Assert.True(condition(), string.Join("", Trace));
        }
        public void WaitReady(DesktopWindow window)
        {
            WaitCompleted(window);
            Assert.NotNull(window.BrowserSession);
            Desktop.Render();
        }
        public void WaitCompleted(DesktopWindow window)
        {
            var timer = Stopwatch.StartNew();
            while (window.IsEditorPreparing && timer.Elapsed < TimeSpan.FromSeconds(60))
            { now += 17; Desktop.AdvanceFeedback(now); Thread.Sleep(5); }
            Assert.False(window.IsEditorPreparing, string.Join("", Trace));
        }
        public void Dispose()
        {
            ReleaseCore.Set(); ReleasePump.Set(); ReleaseProbe.Set();
            foreach (var window in Desktop.Windows.ToArray())
            {
                Desktop.Activate(window.Id);
                Desktop.Key(DesktopKey.Escape); // Discard test edits and retire both panes and pending preparations.
            }
            queue.CompleteAdding();
            Assert.True(worker.Join(TimeSpan.FromSeconds(60)), "Monaco host worker did not exit.");
            foreach (var canvas in PreparationCanvases) canvas.Dispose();
            Canvas.Dispose(); queue.Dispose(); CoreEntered.Dispose(); ReleaseCore.Dispose(); PumpEntered.Dispose(); ReleasePump.Dispose();
            ProbeEntered.Dispose(); ReleaseProbe.Dispose(); ProbeDone.Dispose();
        }
    }

    private sealed class CaptureFactory(BrowserService factory, Action<BrowserService> capture) : IInteractiveBrowserFactory
    {
        public IInteractiveBrowserSession CreatePageSession()
        { var page = (BrowserService)factory.CreatePageSession(); capture(page); return page; }
    }
    private sealed class CancellationProbeResources : IBrowserResourceSource
    {
        private readonly PackagedBrowserResources packaged = new();
        public int SecondReads;
        public bool TryGet(Uri address, out WebResponse response)
        {
            if (address.AbsolutePath is "/Browser/AfterReadyCancelA.js" or "/Browser/AfterReadyCancelB.js")
            {
                if (address.AbsolutePath.EndsWith("B.js", StringComparison.Ordinal)) Interlocked.Increment(ref SecondReads);
                response = new(address, 200, "application/javascript", "document.body.setAttribute('data-after-close','executed');"); return true;
            }
            return packaged.TryGet(address, out response);
        }
    }

    private sealed class PreparationCanvas : IDesktopWebCanvas, IEditorPreparationCanvasHandoff, IDisposable
    {
        private readonly WindowsTextCanvas metrics = new(FontPath, 2, 2);
        private int owner = Environment.CurrentManagedThreadId;
        public int Width => 2; public int Height => 2;
        public long MeasureCalls => metrics.MeasureCalls;
        public int PreparedTextCalls;
        public bool HandedOff;
        public void PrepareEditorText(string text, double fontSize, string fontFamily, int fontWeight)
        { Own(); PreparedTextCalls++; metrics.RasterizeWebText(text, fontSize, fontFamily, fontWeight); }
        public bool TryCompleteEditorPreparationHandoff(IDesktopCanvas target)
        {
            if (target is not WindowsTextCanvas actual || actual.FontSha256 != metrics.FontSha256 || target.ScalePercent != ((IDesktopCanvas)this).ScalePercent) return false;
            owner = Environment.CurrentManagedThreadId; HandedOff = true; return true;
        }
        private void Own() => Assert.Equal(owner, Environment.CurrentManagedThreadId);
        public double MeasureWebText(string text, double size, string family, int weight) { Own(); return metrics.MeasureWebText(text, size, family, weight); }
        public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double size, string family, int weight) { Own(); return metrics.RasterizeWebText(text, size, family, weight); }
        public void Fill(uint rgb, int x, int y, int width, int height) => throw new InvalidOperationException("Worker drew a display fill.");
        public void Line(uint rgb, int x1, int y1, int x2, int y2) => throw new InvalidOperationException("Worker drew a display line.");
        public void Text(string text, uint rgb, int x, int y) => throw new InvalidOperationException("Worker drew display text.");
        public void WebText(string text, uint color, double x, double y, double size, string family, int weight, double opacity = 1) => throw new InvalidOperationException("Worker painted the display.");
        public void PushClip(DesktopBounds bounds) => throw new InvalidOperationException("Worker clipped the display.");
        public void PopClip() => throw new InvalidOperationException("Worker clipped the display.");
        public void Present() => throw new InvalidOperationException("Worker presented a display.");
        public void Dispose() => metrics.Dispose();
    }
    private sealed class RejectScheduler : IEditorBackgroundWorkScheduler { public bool TryQueue(Action work) => false; }
    private sealed class NeverCanvasFactory : IEditorPreparationCanvasFactory
    { public int Calls; public IDesktopCanvas CreateEditorPreparationCanvas() { Calls++; throw new InvalidOperationException(); } }
    private sealed class PendingService : IEditorPreparationService
    {
        public IEditorPreparationOperation Begin(EditorPreparationRequest request) => new PendingOperation(request);
        private sealed class PendingOperation(EditorPreparationRequest request) : IEditorPreparationOperation
        { public EditorPreparationRequest Request => request; public void Cancel() { } public bool TryTakeResult(out EditorPreparationResult result) { result = null!; return false; } }
    }
    private sealed class AbortingCanvas : IDesktopCanvas
    {
        public bool Abort = true; public int Presents;
        public int Width => 1280; public int Height => 800;
        public void Fill(uint rgb, int x, int y, int width, int height) { }
        public void Line(uint rgb, int x1, int y1, int x2, int y2) { }
        public void Text(string text, uint rgb, int x, int y) { if (Abort && text.Contains("BackgroundWidget", StringComparison.Ordinal)) throw new DesktopRenderYieldException(); }
        public void Present() { Presents++; }
    }
}