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

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
using System.Buffers.Binary;
using XFEEDToolBox.Core.Session;
using XFEExtension.NetCore.XUnit.Attributes;

namespace XFEEDToolBox.Test;

[TestFixture]public class StartupCrashRecoveryTests
{
    private static readonly DateTimeOffset At=DateTimeOffset.Parse("2026-10-25T22:33:52Z");
    private static GameSessionTarget Target=>new(74248,At.AddSeconds(-15).UtcTicks,"fixture-game","fixture-launcher",false);
    private static byte[] Dump(int pid=74248,uint exception=0xc0000005)
    {
        var data=new byte[256];void Put(int offset,uint value)=>BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(offset),value);
        Put(0,0x504d444d);Put(8,2);Put(12,32);Put(20,(uint)At.ToUnixTimeSeconds());
        Put(32,6);Put(36,12);Put(40,64);Put(72,exception);
        Put(44,15);Put(48,12);Put(52,80);Put(80,12);Put(84,1);Put(88,(uint)pid);return data;
    }
    [Test]public void OnlyFreshExceptionDumpForTheExactNewProcessAuthorizesRecovery()
    {
        using var temp=new TemporaryDirectory();var path=Path.Combine(temp.Path,"crash.dmp");File.WriteAllBytes(path,Dump());
        var proof=StartupCrashRecovery.Read(path,Target,At.AddSeconds(5));Check.That(proof is{ProcessId:74248,ExceptionCode:0xc0000005});
        Check.That(StartupCrashRecovery.Read(path,Target with{ProcessId=42},At) is null);
        Check.That(StartupCrashRecovery.Read(path,Target with{StartedUtcTicks=At.AddMinutes(1).UtcTicks},At) is null);
        Check.That(StartupCrashRecovery.Read(path,Target,At.AddMinutes(6)) is null);
        Check.That(StartupCrashRecovery.Read(path,Target,At.AddMinutes(-1)) is null);
        File.WriteAllBytes(path,Dump(exception:0));Check.That(StartupCrashRecovery.Read(path,Target,At) is null);
        File.WriteAllBytes(path,new byte[8]);Check.That(StartupCrashRecovery.Read(path,Target,At) is null);
    }
    [Test]public async Task OwnedStartupCrashRetriesOnceAndPreservesOriginalCallerScope()
    {
        var attempts=0;var recoveries=0;
        await StartupCrashRecovery.RunAsync((own,_)=>{own(Target);if(++attempts==1)throw new IOException("Startup pipe ended");return Task.CompletedTask;},
            (_,_)=>Task.FromResult(true),t=>new("fixture",t.ProcessId,0xc0000005,At),(_,n,_)=>{recoveries=n;return Task.CompletedTask;},default);
        Check.Equal(2,attempts);Check.Equal(1,recoveries);
    }
    [Test]public async Task OrdinaryExitLiveProcessAndUnownedInstanceAreNeverRelaunched()
    {
        foreach(var reason in new[]{0,1,2})
        {
            var attempts=0;var refused=false;
            try{await StartupCrashRecovery.RunAsync((own,_)=>{attempts++;if(reason!=2)own(Target);throw new IOException("fixture stop");},
                (_,_)=>Task.FromResult(reason!=1),t=>reason==0?null:new("fixture",t.ProcessId,0xc0000005,At),null,default);}
            catch(IOException){refused=true;}
            Check.That(refused);Check.Equal(1,attempts);
        }
    }
    [Test]public async Task RepeatedOwnedCrashesRemainBoundedAndCancellationCannotRestart()
    {
        var attempts=0;var refused=false;
        try{await StartupCrashRecovery.RunAsync((own,_)=>{attempts++;own(Target);throw new IOException();},(_,_)=>Task.FromResult(true),t=>new("fixture",t.ProcessId,0xc0000005,At),null,default);}
        catch(IOException){refused=true;}
        Check.That(refused);Check.Equal(3,attempts);
        using var stop=new CancellationTokenSource();attempts=0;
        try{await StartupCrashRecovery.RunAsync((own,_)=>{attempts++;own(Target);stop.Cancel();throw new OperationCanceledException(stop.Token);},(_,_)=>Task.FromResult(true),t=>new("fixture",t.ProcessId,0xc0000005,At),null,stop.Token);}
        catch(OperationCanceledException){}
        Check.Equal(1,attempts);
    }
    [Test]public async Task IdentityCargoAndSafetyRejectionRemainTerminalEvenWithAnOwnedCrashDump()
    {
        foreach(var failure in new Exception[]{new InvalidDataException("commander or actual cargo changed"),new NotSupportedException("unverified build"),
            new XFEEDToolBox.Core.Navigation.FlightThreatPauseException(),new IOException("transport wrapper",new InvalidDataException("original identity mismatch")),
            new AggregateException(new IOException("pipe ended"),new InvalidDataException("ship changed"))})
        {
            var attempts=0;var recoveries=0;var rejected=false;
            try{await StartupCrashRecovery.RunAsync((own,_)=>{own(Target);attempts++;throw failure;},(_,_)=>Task.FromResult(true),
                target=>new("fixture",target.ProcessId,0xc0000005,At),(_,_,_)=>{recoveries++;return Task.CompletedTask;},default);}
            catch(Exception actual){rejected=ReferenceEquals(actual,failure);}
            Check.That(rejected);Check.Equal(1,attempts);Check.Equal(0,recoveries);
        }
    }
}