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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using XFESystem.Core;
using XFESystem.Desktop;
using XFESystem.NativeKernel;
using XFESystem.NativeRuntime.ConsoleApplication;

namespace XFESystem.ConsoleEdition.Tests;

public sealed class ConsoleEditionTests
{
    [Fact]
    public unsafe void IncompleteFramesCannotReleaseBootAndCompleteFramesReleaseItOnce()
    {
        uint[] scanout = new uint[320*240]; int handoffs = 0;
        fixed(uint* pointer=scanout)
        {
            var framebuffer = new ConsoleMemoryFramebuffer(new((nuint)pointer,(nuint)(scanout.Length*4),320,240,1280,32,0xFF0000,0xFF00,0xFF),
                () => { Assert.All(scanout,p=>Assert.Equal(0u,p)); handoffs++; });
            Assert.Throws<ArgumentException>(()=>framebuffer.Present(ReadOnlySpan<int>.Empty));
            Assert.Equal(0,handoffs);
            int[] complete = Enumerable.Repeat(0x123456,320*240).ToArray();
            framebuffer.Present(complete); Assert.Equal(1,handoffs); Assert.Equal(0x123456u,scanout[0]);
            framebuffer.Present(complete); Assert.Equal(1,handoffs);
        }
    }

    private sealed class Scanout(int width, int height) : IConsoleFramebuffer
    {
        public int Width => width;
        public int Height => height;
        public int[] Pixels = new int[checked(width * height)];
        public int Presentations;
        public void Present(ReadOnlySpan<int> pixels) { pixels.CopyTo(Pixels); Presentations++; }
    }

    [Theory]
    [InlineData(1024, 768, 100, 12, 20, 81, 31)]
    [InlineData(3840, 2160, 200, 24, 40, 156, 46)]
    [InlineData(320, 240, 100, 12, 20, 24, 4)]
    public void ClassicLayoutPreservesRustCellAndHeaderGeometry(int width, int height, int scale, int cellWidth, int cellHeight, int columns, int rows)
    {
        var scanout = new Scanout(width, height); var renderer = new ClassicConsoleRenderer(scanout);
        Assert.Equal(scale, renderer.ScalePercent); Assert.Equal(cellWidth, renderer.CellWidth); Assert.Equal(cellHeight, renderer.CellHeight);
        Assert.Equal(columns, renderer.Columns); Assert.Equal(rows, renderer.Rows);
        var shell = new TerminalShell(new MemoryWorkspace(false), "0.3.0-dev.4", columns, 128);
        renderer.Render(shell);
        Assert.Equal(1, scanout.Presentations); Assert.Equal(1, renderer.FramesPresented);
        Assert.Equal((int)ClassicConsoleRenderer.Accent, scanout.Pixels[0]);
        Assert.Equal((int)ClassicConsoleRenderer.Panel, scanout.Pixels[(height - 1) * width]);
        Assert.Equal((int)ClassicConsoleRenderer.Background, scanout.Pixels[(renderer.BodyTop + 2) * width]);
    }

    [Fact]
    public void ConsoleAssemblyHasNoGraphicalOrNetworkDependencies()
    {
        var names = typeof(ClassicConsoleRenderer).Assembly.GetReferencedAssemblies().Select(name => name.Name).ToArray();
        foreach (string excluded in new[] { "XFESystem.Desktop", "XFESystem.Browser", "XFESystem.Platform", "XFESystem.Documents", "XFESystem.Tls", "XFESystem.Network" })
            Assert.DoesNotContain(excluded, names);
        Assert.Contains("XFESystem.Console", names); Assert.Contains("XFESystem.ManagedRuntime", names);
    }

    [Fact]
    public void ShellTypingAndCaretAreRenderedWithoutMutatingCompletedOutput()
    {
        var scanout = new Scanout(1024, 768); var renderer = new ClassicConsoleRenderer(scanout);
        var shell = new TerminalShell(new MemoryWorkspace(false), columns: renderer.Columns, rows: 128);
        renderer.Render(shell); int[] initial = (int[])scanout.Pixels.Clone();
        string output = Text(shell);
        shell.InsertText("echo typed"); shell.HandleKey(TerminalShellKey.Left);
        renderer.Render(shell); Assert.Equal(output, Text(shell)); Assert.False(initial.SequenceEqual(scanout.Pixels));
        int[] edited = (int[])scanout.Pixels.Clone(); renderer.Render(shell, cursorVisible: false);
        Assert.False(edited.SequenceEqual(scanout.Pixels));
        shell.HandleKey(TerminalShellKey.Enter); renderer.Render(shell);
        Assert.Contains("typed", Text(shell)); Assert.Equal("", shell.Input);
    }

    [Fact]
    public void UnicodeAndAnsiProduceActualClassicFramebufferPixels()
    {
        var scanout = new Scanout(1024, 768); var renderer = new ClassicConsoleRenderer(scanout);
        var shell = new TerminalShell(new MemoryWorkspace(false), columns: renderer.Columns, rows: 128);
        shell.ProgramOutput.Write("\u001bc\u001b[38;2;255;80;40m中文\u001b[0m\r\n");
        renderer.Render(shell);
        Assert.Contains(0xFF5028, scanout.Pixels.Skip(renderer.BodyTop * scanout.Width).Take(renderer.CellHeight * scanout.Width));
        Assert.Contains((int)ClassicConsoleRenderer.Foreground, scanout.Pixels.Skip((renderer.BodyTop + renderer.CellHeight) * scanout.Width));
    }

    [Fact]
    public void LongInputWrapsOnSmallScreenAndKeepsCaretVisible()
    {
        var scanout = new Scanout(320, 240); var renderer = new ClassicConsoleRenderer(scanout);
        var shell = new TerminalShell(new MemoryWorkspace(false), columns: renderer.Columns, rows: 128);
        shell.InsertText(new string('x', 1024)); renderer.Render(shell);
        shell.MoveCaret(0); renderer.Render(shell);
        Assert.Equal(2, scanout.Presentations);
        int bottom = renderer.BodyTop + renderer.Rows * renderer.CellHeight;
        Assert.Contains((int)ClassicConsoleRenderer.Accent, scanout.Pixels.Skip(renderer.BodyTop * 320).Take((bottom - renderer.BodyTop) * 320));
    }

    [Fact]
    public void ScrollbackShowsOlderOutputAndLeavesPromptUsable()
    {
        var scanout = new Scanout(640, 480); var renderer = new ClassicConsoleRenderer(scanout);
        var shell = new TerminalShell(new MemoryWorkspace(false), columns: renderer.Columns, rows: 128);
        for (int index = 0; index < 80; index++) shell.ProgramOutput.WriteLine("line " + index);
        renderer.Render(shell); int[] latest = (int[])scanout.Pixels.Clone();
        renderer.Render(shell, scrollRows: 30); Assert.False(latest.SequenceEqual(scanout.Pixels));
        shell.InsertText("echo back"); shell.HandleKey(TerminalShellKey.Enter); renderer.Render(shell);
        Assert.Contains("back", Text(shell)); Assert.Equal(3, scanout.Presentations);
    }

    [Theory]
    [InlineData(0x1C, TerminalShellKey.Enter)]
    [InlineData(0x14B, TerminalShellKey.Left)]
    [InlineData(0x147, TerminalShellKey.Home)]
    [InlineData(0x153, TerminalShellKey.Delete)]
    [InlineData(0x152, TerminalShellKey.Insert)]
    public void ConsoleKeyboardUsesRealShellNavigation(uint code, TerminalShellKey expected)
    {
        Assert.Equal(expected, ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyDown, KeyCode = code })!.Value.Key);
        Assert.Null(ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyUp, KeyCode = code }));
    }

    [Fact]
    public void ModifierKeysCannotOpenGuiOrInjectControlCharacters()
    {
        var typed = ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyDown, KeyCode = 0x1E, Unicode = 'A' });
        Assert.Equal("A", typed!.Value.Text);
        Assert.Null(ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyDown, KeyCode = 0x12, Unicode = 'e', Modifiers = Ps2Decoder.Super }));
        Assert.Null(ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyDown, KeyCode = 0x3E, Modifiers = Ps2Decoder.Alt }));
        Assert.Equal(TerminalShellKey.Cancel, ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.KeyDown, KeyCode = 0x2E, Modifiers = Ps2Decoder.Control })!.Value.Key);
        Assert.Null(ConsoleKeyboard.Translate(new() { Kind = Ps2Decoder.Pointer, Unicode = 'x' }));
    }

    [Theory]
    [InlineData(16, 0xF800u, 0x7E0u, 0x1Fu)]
    [InlineData(24, 0xFFu, 0xFF00u, 0xFF0000u)]
    [InlineData(32, 0xFF0000u, 0xFF00u, 0xFFu)]
    public unsafe void RawScanoutHonorsPitchAndChannelMasksWithoutTouchingGuards(int bits, uint red, uint green, uint blue)
    {
        const int width = 320, height = 240, guard = 64;
        int bytes = bits / 8, pitch = width * bytes + 16;
        byte[] memory = Enumerable.Repeat((byte)0xA5, guard + pitch * height + guard).ToArray();
        fixed (byte* pointer = memory)
        {
            var presenter = new ConsoleMemoryFramebuffer(new((nuint)(pointer + guard), (nuint)(pitch * height), width, height, pitch, bits, red, green, blue));
            int[] pixels = new int[width * height]; pixels.AsSpan().Fill(0xFF0000); presenter.Present(pixels);
        }
        Assert.All(memory.Take(guard), value => Assert.Equal(0xA5, value));
        Assert.All(memory.Skip(guard + pitch * height), value => Assert.Equal(0xA5, value));
        for (int row = 0; row < height; row++)
            Assert.All(memory.Skip(guard + row * pitch + width * bytes).Take(16), value => Assert.Equal(0xA5, value));
        uint packed = 0; for (int index = 0; index < bytes; index++) packed |= (uint)memory[guard + index] << (index * 8);
        Assert.Equal(red, packed);
    }

    [Fact]
    public unsafe void FramebufferRejectsAliasedMasksShortMappingAndIncompleteFrames()
    {
        byte[] memory = new byte[320 * 240 * 4];
        fixed (byte* pointer = memory)
        {
            nuint address = (nuint)pointer;
            Assert.Throws<ArgumentException>(() => new ConsoleMemoryFramebuffer(new(address, (nuint)memory.Length, 320, 240, 1280, 32, 0xFF00, 0xFF00, 0xFF)));
            Assert.Throws<ArgumentException>(() => new ConsoleMemoryFramebuffer(new(address, 10, 320, 240, 1280, 32, 0xFF0000, 0xFF00, 0xFF)));
            var presenter = new ConsoleMemoryFramebuffer(new(address, (nuint)memory.Length, 320, 240, 1280, 32, 0xFF0000, 0xFF00, 0xFF));
            Assert.Throws<ArgumentException>(() => presenter.Present(new int[4]));
        }
    }

    private static string Text(TerminalShell shell) => string.Concat(shell.ReadOutput().Cells.Select(cell => cell.Text));
}