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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Galaxy;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]
public class NativeGalaxyCatalogueProfileTests
{
    private static async Task Serve(int id,object result,CancellationToken token,Action<JsonElement>? inspect=null)
    {
        await using var server=new NamedPipeServerStream($"XFEEDToolBox.Navigation.{id}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
        await server.WaitForConnectionAsync(token);var buffer=new byte[65536];var count=0;
        while(true)
        {
            var read=await server.ReadAsync(buffer.AsMemory(count),token);if(read==0)throw new IOException("Empty request");count+=read;
            var parsed=Parse(buffer,count);if(parsed is{} request)
            {inspect?.Invoke(request);if(result is Func<JsonElement,object> choose)result=choose(request);break;}
            if(count==buffer.Length)throw new IOException("Request exceeded limit");
        }
        await server.WriteAsync(JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result}),token);await server.WriteAsync(new byte[]{10},token);
        if(OperatingSystem.IsWindows())server.WaitForPipeDrain();
        static JsonElement? Parse(byte[] bytes,int count)
        {
            var reader=new Utf8JsonReader(bytes.AsSpan(0,count),false,default);
            if(!JsonDocument.TryParseValue(ref reader,out var d))return null;
            using(d)return d.RootElement.Clone();
        }
    }
    [Test] public async Task ProfileReadsNativeChosenCentreAndActualInputsWithoutDefaultingQuality()
    {
        var id=Random.Shared.Next(111000000,112000000);using var stop=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var server=Task.Run(async()=>
        {
            await Serve(id,new{generation=9,done=false,error="",profile=(object?)null},stop.Token,r=>
            {Check.Equal("galaxy.catalogue.profile",r.GetProperty("op").GetString());Check.Equal(3d,r.GetProperty("eye")[2].GetDouble());Check.Equal(6d,r.GetProperty("focus")[2].GetDouble());});
            await Serve(id,new{generation=9,done=true,error="",profile=new{origin=new[]{49985d,40985d,24105d},queryCentre=new[]{4d,5d,6d},
                surfaceDensity=.4,qualityStarCount=0u,runtimeRadiusMultiplier=1d,sparseExpansion=4d,smallMode=false,originalViewMode=2,debugEnabled=false,colourBlend=.375,referenceKind="camera-eye"}},stop.Token,
                r=>Check.Equal("galaxy.catalogue.profile.result",r.GetProperty("op").GetString()));
        });
        var result=await new NativeGalaxyCatalog(id).CatalogueProfileAsync(new(1,2,3),new(4,5,6),stop.Token);await server;
        Check.That(result.HasLodInputs);Check.Equal(0u,result.QualityStarCount!.Value);Check.Equal(4d,result.QueryCentre!.X);Check.Equal(49985d,result.Origin.X);
        Check.Equal(.375,result.ColourBlend!.Value);
    }
    [Test] public void UninitialisedMapKeepsOriginAndUnknownLodInputs()
    {
        using var json=JsonDocument.Parse("{\"origin\":[1,2,3],\"queryCentre\":null,\"surfaceDensity\":null,\"qualityStarCount\":null,\"referenceKind\":\"camera-eye\"}");
        var result=NativeGalaxyCatalog.ReadCatalogueProfile(json.RootElement);Check.That(!result.HasLodInputs);
        Check.Equal(2d,result.Origin.Y);Check.That(result.QualityStarCount is null&&result.RuntimeRadiusMultiplier is null&&result.SmallMode is null);
    }
    [Test] public void ProfileRejectsUnboundSamplingAndUnsupportedProtocol()
    {
        using var malformed=JsonDocument.Parse("{\"origin\":[1,2,3],\"queryCentre\":[4,5,6],\"surfaceDensity\":0,\"referenceKind\":\"camera-eye\"}");
        Check.Throws<InvalidDataException>(()=>NativeGalaxyCatalog.ReadCatalogueProfile(malformed.RootElement));
        var catalog=new NativeGalaxyCatalog(123);using var description=JsonDocument.Parse("{\"processId\":123}");catalog.ApplyProtocolDescription(description.RootElement);
        Check.Throws<NotSupportedException>(()=>catalog.CatalogueProfileAsync(new(0,0,0),new(0,0,0)).GetAwaiter().GetResult());
    }
    [Test] public async Task CancellationRetiresOnlyTheAcceptedProfileGeneration()
    {
        var id=Random.Shared.Next(112000000,113000000);using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));using var caller=new CancellationTokenSource();
        var cancelled=false;var server=Task.Run(async()=>
        {
            await Serve(id,new{generation=42,done=false,error="",profile=(object?)null},deadline.Token,_=>caller.Cancel());
            await Serve(id,new{generation=42,done=true,cancelled=true,error="",profile=(object?)null},deadline.Token,r=>
            {cancelled=r.GetProperty("op").GetString()=="galaxy.catalogue.profile.cancel"&&r.GetProperty("generation").GetInt64()==42;});
        });
        try{await new NativeGalaxyCatalog(id).CatalogueProfileAsync(new(0,0,0),new(0,0,0),caller.Token);throw new Exception("Cancellation was ignored");}
        catch(OperationCanceledException){}
        await server;Check.That(cancelled);
    }
    [Test] public async Task ProfileCompletesWhileTheOldCellJobIsStillBusy()
    {
        var id=Random.Shared.Next(113000000,114000000);using var deadline=new CancellationTokenSource(TimeSpan.FromSeconds(5));
        var started=new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);var profileFinished=false;var cellsFinished=false;
        var server=Task.Run(async()=>
        {
            while(!cellsFinished||!profileFinished)
                await Serve(id,new Func<JsonElement,object>(r=>
                {
                    var op=r.GetProperty("op").GetString();
                    if(op=="galaxy.catalogue.profile")
                    {
                        profileFinished=true;
                        return new{generation=92,done=true,error="",profile=new{origin=new[]{1d,2d,3d},referenceKind="camera-eye"}};
                    }
                    if(op is "galaxy.cells" or "galaxy.result")
                    {
                        started.TrySetResult();cellsFinished=profileFinished;
                        return new{generation=91,done=cellsFinished,error="",total=0,items=Array.Empty<object>(),scope="requested-cells",requestedCells=1,
                            minimumMassCode=0,processedCells=cellsFinished?1:0,totalCells=1,failedCells=0,truncated=false,complete=cellsFinished,origin=new[]{1d,2d,3d}};
                    }
                    return new{generation=91,done=true};
                }),deadline.Token);
        });
        var catalogue=new NativeGalaxyCatalog(id);var cells=catalogue.CellsAsync(new(0,0,0),Enumerable.Repeat(1d,8).ToArray(),new[]{0UL},deadline.Token);
        await started.Task.WaitAsync(deadline.Token);
        var profile=await catalogue.CatalogueProfileAsync(new(0,0,0),new(0,0,0),deadline.Token).WaitAsync(TimeSpan.FromSeconds(2));
        Check.Equal(1d,profile.Origin.X);Check.That(profileFinished);var result=await cells;await server;Check.That(result.AreRequestedCellsCovered);
    }
    [Test] public void OptionalColourBlendKeepsItsOwnUnknownStateAndRejectsNonUnitWireValues()
    {
        const string fields="\"origin\":[1,2,3],\"queryCentre\":[4,5,6],\"surfaceDensity\":0.4,\"qualityStarCount\":0,\"runtimeRadiusMultiplier\":1,\"sparseExpansion\":4,\"smallMode\":false,\"originalViewMode\":2,\"debugEnabled\":false,\"referenceKind\":\"camera-eye\"";
        static GalaxyCatalogueProfile Parse(string body)
        {using var doc=JsonDocument.Parse(body);return NativeGalaxyCatalog.ReadCatalogueProfile(doc.RootElement);}
        foreach(var suffix in new[]{"",",\"colourBlend\":null"})
        {var profile=Parse("{"+fields+suffix+"}");Check.That(profile.HasLodInputs&&profile.ColourBlend is null);}
        foreach(var value in new[]{"0","0.375","1"})
        {var profile=Parse("{"+fields+",\"colourBlend\":"+value+"}");Check.That(profile.HasLodInputs);Check.Equal(double.Parse(value,System.Globalization.CultureInfo.InvariantCulture),profile.ColourBlend!.Value);}
        foreach(var invalid in new[]{"-0.01","1.01","1e309","\"0.5\"","true","[]","{}"})
            Check.Throws<InvalidDataException>(()=>Parse("{"+fields+",\"colourBlend\":"+invalid+"}"));
    }
}