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

XFESystem

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

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Win32.SafeHandles;
using XFESystem.Kernel;
using Xunit;

namespace XFESystem.NativeInterop.Tests;

/// <summary>Real host temporary-file I/O through the production vector transfer implementation.</summary>
public sealed unsafe class VectorFileIoTests
{
    private const int InvalidArgument = 0x1001C;
    private const int Overflow = 0x10040;

    [Fact]
    public void ScatterWriteUsesConsecutivePositionsStartingAtTheRequestedOffset()
    {
        using var file = new TemporaryFile("...........");
        byte[] first = "AB"u8.ToArray(), second = "cde"u8.ToArray(), third = "FG"u8.ToArray();
        fixed (byte* a = first, b = second, c = third)
        {
            VectorFileIo.IoVector* vectors = stackalloc VectorFileIo.IoVector[3]
            {
                new() { Base = a, Count = 2 }, new() { Base = b, Count = 3 }, new() { Base = c, Count = 2 }
            };
            Assert.Equal(7, VectorFileIo.Transfer(file.Descriptor, vectors, 3, 2, write: true));
        }
        Assert.Equal("..ABcdeFG..", Encoding.UTF8.GetString(file.ReadAll()));
    }

    [Fact]
    public void GatherReadFillsVectorsInOrderWithoutChangingTheFile()
    {
        using var file = new TemporaryFile("0123456789ABC");
        byte[] first = new byte[2], second = new byte[3], third = new byte[1];
        fixed (byte* a = first, b = second, c = third)
        {
            VectorFileIo.IoVector* vectors = stackalloc VectorFileIo.IoVector[3]
            {
                new() { Base = a, Count = 2 }, new() { Base = b, Count = 3 }, new() { Base = c, Count = 1 }
            };
            Assert.Equal(6, VectorFileIo.Transfer(file.Descriptor, vectors, 3, 3, write: false));
        }
        Assert.Equal("34", Encoding.UTF8.GetString(first));
        Assert.Equal("567", Encoding.UTF8.GetString(second));
        Assert.Equal("8", Encoding.UTF8.GetString(third));
        Assert.Equal("0123456789ABC", Encoding.UTF8.GetString(file.ReadAll()));
    }

    [Fact]
    public void EndOfFileReturnsPartialCountAndLeavesRemainingBuffersUntouched()
    {
        using var file = new TemporaryFile("ABC");
        byte[] first = Filled(2), second = Filled(4), third = Filled(2);
        fixed (byte* a = first, b = second, c = third)
        {
            VectorFileIo.IoVector* vectors = stackalloc VectorFileIo.IoVector[3]
            {
                new() { Base = a, Count = 2 }, new() { Base = b, Count = 4 }, new() { Base = c, Count = 2 }
            };
            Assert.Equal(3, VectorFileIo.Transfer(file.Descriptor, vectors, 3, 0, write: false));
        }
        Assert.Equal("AB", Encoding.UTF8.GetString(first));
        Assert.Equal(new byte[] { (byte)'C', 0xCC, 0xCC, 0xCC }, second);
        Assert.Equal(Filled(2), third);
    }

    [Fact]
    public void ReadAtEndOfFileDoesNotModifyAnyDestination()
    {
        using var file = new TemporaryFile("ABC");
        byte[] destination = Filled(3);
        fixed (byte* bytes = destination)
        {
            VectorFileIo.IoVector vector = new() { Base = bytes, Count = 3 };
            Assert.Equal(0, VectorFileIo.Transfer(file.Descriptor, &vector, 1, 3, write: false));
        }
        Assert.Equal(Filled(3), destination);
    }

    [Fact]
    public void EmptyVectorsAreSkippedWithoutAdvancingTheFileOffset()
    {
        using var file = new TemporaryFile("ABC");
        byte[] destination = new byte[2];
        fixed (byte* bytes = destination)
        {
            VectorFileIo.IoVector* vectors = stackalloc VectorFileIo.IoVector[3]
            {
                new() { Base = null, Count = 0 }, new() { Base = bytes, Count = 2 }, new() { Base = null, Count = 0 }
            };
            Assert.Equal(2, VectorFileIo.Transfer(file.Descriptor, vectors, 3, 1, write: false));
        }
        Assert.Equal("BC", Encoding.UTF8.GetString(destination));
    }

    [Theory]
    [InlineData(-1)]
    [InlineData(1025)]
    public void InvalidVectorCountReturnsPalInvalidArgument(int count)
    {
        using var file = new TemporaryFile("ABC");
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, null, count, 0, write: false));
        Assert.Equal(InvalidArgument, Marshal.GetLastPInvokeError());
        Assert.Equal("ABC", Encoding.UTF8.GetString(file.ReadAll()));
    }

    [Fact]
    public void NegativeOffsetAndNullVectorArrayReturnPalInvalidArgument()
    {
        using var file = new TemporaryFile("ABC");
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, null, 0, -1, write: false));
        Assert.Equal(InvalidArgument, Marshal.GetLastPInvokeError());
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, null, 1, 0, write: false));
        Assert.Equal(InvalidArgument, Marshal.GetLastPInvokeError());
    }

    [Fact]
    public void NoVectorsReturnsZeroWithoutConsumingOrClosingTheHandle()
    {
        using var file = new TemporaryFile("ABC");
        Assert.Equal(0, VectorFileIo.Transfer(file.Descriptor, null, 0, 0, write: false));
        Assert.False(file.Handle.IsClosed);
        Assert.Equal("ABC", Encoding.UTF8.GetString(file.ReadAll()));
    }

    [Fact]
    public void NullNonemptyVectorAndOversizedVectorAreRejectedBeforeMemoryAccess()
    {
        using var file = new TemporaryFile("ABC");
        VectorFileIo.IoVector vector = new() { Base = null, Count = 1 };
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, &vector, 1, 0, write: false));
        Assert.Equal(InvalidArgument, Marshal.GetLastPInvokeError());
        byte value = 0;
        vector.Base = &value;
        vector.Count = (nuint)int.MaxValue + 1;
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, &vector, 1, 0, write: false));
        Assert.Equal(InvalidArgument, Marshal.GetLastPInvokeError());
    }

    [Fact]
    public void PositionalOverflowIsRejectedWithoutCreatingALargeSparseFile()
    {
        using var file = new TemporaryFile("ABC");
        byte value = (byte)'X';
        VectorFileIo.IoVector vector = new() { Base = &value, Count = 1 };
        Assert.Equal(-1, VectorFileIo.Transfer(file.Descriptor, &vector, 1, long.MaxValue, write: true));
        Assert.Equal(Overflow, Marshal.GetLastPInvokeError());
        Assert.Equal("ABC", Encoding.UTF8.GetString(file.ReadAll()));
    }

    [Theory]
    [InlineData(false)]
    [InlineData(true)]
    public void AnInvalidLaterVectorReturnsBytesAlreadyTransferred(bool write)
    {
        using var file = new TemporaryFile("ABC");
        byte[] data = write ? "XY"u8.ToArray() : Filled(2);
        fixed (byte* bytes = data)
        {
            VectorFileIo.IoVector* vectors = stackalloc VectorFileIo.IoVector[2]
            {
                new() { Base = bytes, Count = 2 }, new() { Base = null, Count = 1 }
            };
            Assert.Equal(2, VectorFileIo.Transfer(file.Descriptor, vectors, 2, 0, write));
        }
        Assert.Equal(write ? "XYC" : "ABC", Encoding.UTF8.GetString(file.ReadAll()));
        if (!write) Assert.Equal("AB", Encoding.UTF8.GetString(data));
    }

    [Fact]
    public void BorrowedHandleRemainsUsableAfterMultipleTransfers()
    {
        using var file = new TemporaryFile("ABC");
        byte value = (byte)'X';
        VectorFileIo.IoVector vector = new() { Base = &value, Count = 1 };
        Assert.Equal(1, VectorFileIo.Transfer(file.Descriptor, &vector, 1, 1, write: true));
        value = 0;
        Assert.Equal(1, VectorFileIo.Transfer(file.Descriptor, &vector, 1, 1, write: false));
        Assert.Equal((byte)'X', value);
        Assert.False(file.Handle.IsClosed);
        Assert.Equal(3, RandomAccess.GetLength(file.Handle));
    }

    [Fact]
    public void ActualUnmanagedEntryPointsPerformIoRatherThanReturningStubResults()
    {
        using var file = new TemporaryFile("ABC");
        delegate* unmanaged<nint, VectorFileIo.IoVector*, int, long, long> read = &VectorFileIo.Read;
        delegate* unmanaged<nint, VectorFileIo.IoVector*, int, long, long> write = &VectorFileIo.Write;
        byte value = (byte)'Z';
        VectorFileIo.IoVector vector = new() { Base = &value, Count = 1 };
        Assert.Equal(1, write(file.Descriptor, &vector, 1, 2));
        value = 0;
        Assert.Equal(1, read(file.Descriptor, &vector, 1, 2));
        Assert.Equal((byte)'Z', value);
        Assert.Equal("ABZ", Encoding.UTF8.GetString(file.ReadAll()));
    }

    private static byte[] Filled(int count) => Enumerable.Repeat((byte)0xCC, count).ToArray();

    private sealed class TemporaryFile : IDisposable
    {
        private readonly string _path = Path.Combine(Path.GetTempPath(), "XFESystemVector-" + Guid.NewGuid().ToString("N") + ".tmp");
        public SafeFileHandle Handle { get; }
        public nint Descriptor => Handle.DangerousGetHandle();

        public TemporaryFile(string content)
        {
            Handle = File.OpenHandle(_path, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete, FileOptions.RandomAccess);
            RandomAccess.Write(Handle, Encoding.UTF8.GetBytes(content), 0);
        }

        public byte[] ReadAll()
        {
            byte[] result = new byte[(int)RandomAccess.GetLength(Handle)];
            Assert.Equal(result.Length, RandomAccess.Read(Handle, result, 0));
            return result;
        }

        public void Dispose() { Handle.Dispose(); File.Delete(_path); }
    }
}