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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.CompilerServices;
using System.Text.Json;
using XFESystem.Browser;
using XFESystem.Core;
using XFESystem.Desktop;
using Xunit;

namespace XFESystem.Desktop.Tests;

/// <summary>Real desktop, storage, Monaco and JavaScript paths. These are host
/// regression tests, not native screenshots or guest process-isolation claims.</summary>
public sealed class EditorIntegrityRegressionTests
{
    private sealed class Fixture : IDisposable
    {
        public readonly MemoryWorkspace Storage = new(seed: false);
        public readonly RecordingCanvas Canvas = new();
        public readonly DesktopSession Desktop;
        private long now;

        public Fixture() => Desktop = new(Canvas, Storage, new BrowserService(), monotonicMilliseconds: () => now);
        public DesktopWindow Open(string name, string source)
        {
            string path = SystemPaths.Combine(SystemPaths.Documents, name);
            Storage.WriteText(path, source);
            var window = Desktop.Open(DesktopApplication.Editor, path);
            for (int frame = 0; frame < 8; frame++) Desktop.AdvanceFeedback(now += 17);
            return window;
        }
        public void Dispose() => Canvas.Dispose();
    }

    private sealed class RecordingCanvas : IDesktopWebCanvas, IDisposable
    {
        private readonly WindowsTextCanvas raster = new(Path.Combine(AppContext.BaseDirectory, "TestAssets/Font.ttf"), 1280, 800);
        public List<string> DrawnText { get; } = [];
        public int Width => raster.Width;
        public int Height => raster.Height;
        public void Fill(uint rgb, int x, int y, int width, int height) => raster.Fill(rgb, x, y, width, height);
        public void Line(uint rgb, int x1, int y1, int x2, int y2) => raster.Line(rgb, x1, y1, x2, y2);
        public void Text(string text, uint rgb, int x, int y) { DrawnText.Add(text); raster.Text(text, rgb, x, y); }
        public void Present() => raster.Present();
        public void PushClip(DesktopBounds bounds) => raster.PushClip(bounds);
        public void PopClip() => raster.PopClip();
        public double MeasureWebText(string text, double fontSize, string fontFamily, int fontWeight)
            => raster.MeasureWebText(text, fontSize, fontFamily, fontWeight);
        public (int Width, int Height, byte[] Alpha) RasterizeWebText(string text, double fontSize, string fontFamily, int fontWeight)
            => raster.RasterizeWebText(text, fontSize, fontFamily, fontWeight);
        public void WebText(string text, uint color, double x, double y, double fontSize, string fontFamily, int fontWeight, double opacity = 1)
        { DrawnText.Add(text); raster.WebText(text, color, x, y, fontSize, fontFamily, fontWeight, opacity); }
        public void Dispose() => raster.Dispose();
    }

    [Fact]
    public void ControlCharactersAndUtf16SurrogatesSurviveActualModelReadAndSave()
    {
        using var fixture = new Fixture();
        const string source = "// A\0B\u001bC\u0007D\u000bE\u007fF\t中文😀\r\npublic class Integrity { }\r\n";
        var window = fixture.Open("Integrity.cs", source);
        var page = Assert.IsType<BrowserService>(window.BrowserSession);
        Assert.Equal(source, page.ReadEditorText());

        fixture.Desktop.Key(DesktopKey.Save);

        Assert.Equal(source, fixture.Storage.ReadText(window.Location));
        Assert.Equal(source, window.Content);
        Assert.False(window.IsDirty);
        Assert.Contains("Saved to RAM live", window.Status, StringComparison.Ordinal);
    }

    [Theory]
    [InlineData("function(){throw new Error('readback fault')}")]
    [InlineData("function(){return 42}")]
    [InlineData("function(){return null}")]
    [InlineData("function(){return undefined}")]
    [InlineData("function(){return 'x'.repeat(131073)}")]
    public void FailedModelReadCannotOverwriteOriginalAndTheSameEditorCanRecover(string replacement)
    {
        using var fixture = new Fixture();
        const string original = "public class Original { public int Value = 7; }";
        var window = fixture.Open("Protected.cs", original);
        var page = Assert.IsType<BrowserService>(window.BrowserSession);
        page.ExecuteJavaScript("globalThis.originalReadback = xfeMonacoEditor.getValue; xfeMonacoEditor.getValue = " + replacement);
        Assert.Throws<IOException>(() => page.ReadEditorText());

        fixture.Desktop.Key(DesktopKey.Save);

        Assert.Equal(original, fixture.Storage.ReadText(window.Location));
        Assert.Equal(original, window.Content);
        Assert.True(window.IsDirty);
        Assert.StartsWith("Save failed:", window.Status, StringComparison.Ordinal);

        // Exercise the same runtime/gates after the exception, rather than hiding
        // a stranded lock by creating another page session.
        page.ExecuteJavaScript("xfeMonacoEditor.getValue = originalReadback; xfeMonacoEditor.setValue('public class Recovered { }')");
        fixture.Desktop.Key(DesktopKey.Save);
        Assert.Equal("public class Recovered { }", fixture.Storage.ReadText(window.Location));
        Assert.False(window.IsDirty);
    }

    [Fact]
    public void NativeDiagnosticReadersLeaveRevisionModelAndDomUnchanged()
    {
        using var fixture = new Fixture();
        const string source = "using System;\npublic class Diagnostic { public int Value = 42; }";
        var editor = fixture.Open("Diagnostic.cs", source);
        var page = Assert.IsType<BrowserService>(editor.BrowserSession);
        long revision = page.Revision;
        string html = page.Document!.DocumentElement!.OuterHtml;

        for (int read = 0; read < 3; read++)
        {
            using var proof = JsonDocument.Parse(page.ReadMonacoProof());
            Assert.Equal("ActualMonacoWebsiteCSharpDomReadback", proof.RootElement.GetProperty("Kind").GetString());
            Assert.Equal(source, proof.RootElement.GetProperty("ModelValue").GetString());
            Assert.Equal("{}", page.ReadWebsiteDocumentProof());
            Assert.Equal(source, page.ReadEditorText());
            Assert.Equal(revision, page.Revision);
            Assert.Equal(html, page.Document.DocumentElement.OuterHtml);
        }

        var document = new BrowserService();
        document.SetCanvas(fixture.Canvas);
        document.NavigateDocument("# Read-only document\n\nA paragraph.", "Diagnostic.md");
        revision = document.Revision;
        html = document.Document!.DocumentElement!.OuterHtml;
        for (int read = 0; read < 3; read++)
        {
            using var proof = JsonDocument.Parse(document.ReadWebsiteDocumentProof());
            Assert.Equal("ActualWebsiteDocumentDomReadback", proof.RootElement.GetProperty("Kind").GetString());
            Assert.Equal("{}", document.ReadMonacoProof());
            Assert.Equal(revision, document.Revision);
            Assert.Equal(html, document.Document.DocumentElement.OuterHtml);
        }
    }

    [Theory]
    [InlineData(131_073)]
    [InlineData(262_144)]
    public void OversizedCodeFallsBackToEditableTextWithoutLosingSourceOrStoppingDesktop(int length)
    {
        using var fixture = new Fixture();
        string source = new(' ', length);
        var window = fixture.Open("Large.cs", source);
        Assert.Null(window.BrowserSession);
        Assert.Contains("Basic text editor", window.Status, StringComparison.Ordinal);
        Assert.Contains("128 KiB", window.Status, StringComparison.Ordinal);
        Assert.Equal(source, window.Content);
        Assert.Equal(source, fixture.Storage.ReadText(window.Location));
        Assert.Single(fixture.Desktop.Windows);

        fixture.Desktop.Key(DesktopKey.Delete);
        fixture.Desktop.Key(DesktopKey.Save);

        Assert.Equal(source[1..], fixture.Storage.ReadText(window.Location));
        Assert.False(window.IsDirty);
        fixture.Desktop.Open(DesktopApplication.Files);
        Assert.Equal(2, fixture.Desktop.Windows.Count);
    }

    [Fact]
    public void ExpandedMarkdownFailureIsActuallyDrawnAndDesktopRemainsUsable()
    {
        using var fixture = new Fixture();
        string source = new('&', 262_144); // Valid RAM-live file; HTML escaping exceeds the page limit.
        var editor = fixture.Open("Expanded.md", source);

        fixture.Desktop.Key(DesktopKey.Run);

        var failure = fixture.Desktop.ActiveWindow!;
        Assert.NotSame(editor, failure);
        Assert.Equal("Preview failed", failure.Title);
        Assert.StartsWith("Preview failed:", failure.Content, StringComparison.Ordinal);
        Assert.Contains("preserved", failure.Status, StringComparison.Ordinal);
        Assert.Null(failure.BrowserSession);
        Assert.Equal(source, fixture.Storage.ReadText(editor.Location));
        fixture.Canvas.DrawnText.Clear();
        fixture.Desktop.Render();
        Assert.Contains(fixture.Canvas.DrawnText, text => text.StartsWith("Preview failed:", StringComparison.Ordinal));
        Assert.DoesNotContain(fixture.Canvas.DrawnText, text => text.Contains("JavaScript ready", StringComparison.Ordinal));

        fixture.Desktop.Key(DesktopKey.CloseWindow);
        var recovered = fixture.Open("Recovered.txt", "original");
        fixture.Desktop.Key(DesktopKey.Character, '/');
        fixture.Desktop.Key(DesktopKey.Save);
        Assert.Equal("/original", fixture.Storage.ReadText(recovered.Location));
        Assert.Equal(source, fixture.Storage.ReadText(editor.Location));
    }

    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void ClosingHoveredOrPointerCapturedEditorReleasesItsActualPage(bool captured)
    {
        using var fixture = new Fixture();
        var weak = OpenHoverAndClose(fixture, captured);
        // The desktop and its canvas remain alive: this specifically detects a
        // closed window/page retained by hover or pointer-capture fields.
        for (int attempt = 0; attempt < 3; attempt++)
        { GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); }
        Assert.False(weak.TryGetTarget(out _));
        Assert.Empty(fixture.Desktop.Windows);
        fixture.Desktop.Open(DesktopApplication.Files);
        fixture.Desktop.Pointer(500, 400, false);
        Assert.Single(fixture.Desktop.Windows);
        GC.KeepAlive(fixture.Desktop);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    private static WeakReference<BrowserService> OpenHoverAndClose(Fixture fixture, bool captured)
    {
        var window = fixture.Open("Closed.cs", "public class Closed { }");
        var weak = new WeakReference<BrowserService>(Assert.IsType<BrowserService>(window.BrowserSession));
        int x = window.Bounds.X + 70, y = window.Bounds.Y + 110;
        fixture.Desktop.Pointer(x, y, false);
        if (captured) fixture.Desktop.Pointer(x, y, true);
        fixture.Desktop.Key(DesktopKey.CloseWindow);
        Assert.Null(window.BrowserSession);
        Assert.DoesNotContain(window, fixture.Desktop.Windows);
        return weak;
    }
}