using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NativeGameInputChannelTests
{
private sealed class Fixture:IAsyncDisposable
{
private readonly CancellationTokenSource stop=new();
private readonly Task navigation,interaction;
public readonly int Pid=Random.Shared.Next(300000000,400000000);
public int Reads,Writes;
public bool AlwaysOther,Verified=true,Stale;
public Fixture(){navigation=Serve("Navigation");interaction=Serve("Interaction");}
private async Task Serve(string service)
{
try
{
while(!stop.IsCancellationRequested)
{
await using var server=new NamedPipeServerStream($"XFEEDToolBox.{service}.{Pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
await server.WaitForConnectionAsync(stop.Token);var buffer=new byte[4096];var count=await server.ReadAsync(buffer,stop.Token);
using var request=JsonDocument.Parse(buffer.AsMemory(0,count));object result;
if(service=="Navigation")
result=new{entityId="42",localPlayerShipVerified=Verified,shipHealth=new{entityId="42",verified=Verified,observedAtUnixMilliseconds=DateTimeOffset.UtcNow.AddSeconds(Stale?-2:0).ToUnixTimeMilliseconds()}};
else if(request.RootElement.GetProperty("op").GetString()=="read")
result=new{sequence=Interlocked.Increment(ref Reads),entityId=AlwaysOther||Reads<=2?"99":"42",observedAtUnixMilliseconds=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),hardpoints=false,cargoScoop=false,landingGear=false,appliedGeneration=0};
else {Interlocked.Increment(ref Writes);result=new{generation=1,queued=true};}
var bytes=JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result});await server.WriteAsync(bytes,stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);server.WaitForPipeDrain();
}
}
catch(OperationCanceledException)when(stop.IsCancellationRequested){}
}
public async ValueTask DisposeAsync(){stop.Cancel();await Task.WhenAll(navigation,interaction);stop.Dispose();}
public NativeGameInputChannel Channel()=>new(Pid,new string('a',64),"unused");
}
[Test]public async Task OtherShipSamplesAreSkippedUntilTheVerifiedMainShipIsObserved()
{
await using var fixture=new Fixture();var value=await fixture.Channel().ReadAsync(CancellationToken.None);
Check.Equal(42UL,value.EntityId);Check.That(fixture.Reads>=3);Check.Equal(0,fixture.Writes);
}
[Test]public async Task PersistentOtherShipCannotProduceAPlayerInputFrame()
{
await using var fixture=new Fixture{AlwaysOther=true};
try{await fixture.Channel().ReadAsync(CancellationToken.None);throw new Exception("Expected unavailable telemetry");}
catch(FlightTelemetryUnavailableException){}
Check.Equal(0,fixture.Writes);
}
[Test]public async Task UnverifiedOrStaleMainShipBlocksBothObservationAndWrites()
{
foreach(var stale in new[]{false,true})
{
await using var fixture=new Fixture{Verified=stale,Stale=stale};var channel=fixture.Channel();
try{await channel.ReadAsync(CancellationToken.None);throw new Exception("Expected unavailable telemetry");}catch(FlightTelemetryUnavailableException){}
try{await channel.SetAsync(new(1,42,DateTimeOffset.UtcNow,false,false,false),"landingGear",false,CancellationToken.None);throw new Exception("Expected unavailable telemetry");}catch(FlightTelemetryUnavailableException){}
Check.Equal(0,fixture.Reads);Check.Equal(0,fixture.Writes);
}
}
[Test]public async Task ChangedMainShipBlocksAnOldInputCommandBeforeItsPipeWrite()
{
await using var fixture=new Fixture();
try{await fixture.Channel().SetAsync(new(1,99,DateTimeOffset.UtcNow,false,false,false),"landingGear",false,CancellationToken.None);throw new Exception("Expected ship rejection");}
catch(InvalidOperationException){}
Check.Equal(0,fixture.Writes);
}
}
using System.IO.Pipes;
using System.Text.Json;
using XFEEDToolBox.Core.Navigation;
using XFEExtension.NetCore.XUnit.Attributes;
namespace XFEEDToolBox.Test;
[TestFixture]
public class NativeGameInputChannelTests
{
private sealed class Fixture:IAsyncDisposable
{
private readonly CancellationTokenSource stop=new();
private readonly Task navigation,interaction;
public readonly int Pid=Random.Shared.Next(300000000,400000000);
public int Reads,Writes;
public bool AlwaysOther,Verified=true,Stale;
public Fixture(){navigation=Serve("Navigation");interaction=Serve("Interaction");}
private async Task Serve(string service)
{
try
{
while(!stop.IsCancellationRequested)
{
await using var server=new NamedPipeServerStream($"XFEEDToolBox.{service}.{Pid}",PipeDirection.InOut,1,PipeTransmissionMode.Byte,PipeOptions.Asynchronous);
await server.WaitForConnectionAsync(stop.Token);var buffer=new byte[4096];var count=await server.ReadAsync(buffer,stop.Token);
using var request=JsonDocument.Parse(buffer.AsMemory(0,count));object result;
if(service=="Navigation")
result=new{entityId="42",localPlayerShipVerified=Verified,shipHealth=new{entityId="42",verified=Verified,observedAtUnixMilliseconds=DateTimeOffset.UtcNow.AddSeconds(Stale?-2:0).ToUnixTimeMilliseconds()}};
else if(request.RootElement.GetProperty("op").GetString()=="read")
result=new{sequence=Interlocked.Increment(ref Reads),entityId=AlwaysOther||Reads<=2?"99":"42",observedAtUnixMilliseconds=DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),hardpoints=false,cargoScoop=false,landingGear=false,appliedGeneration=0};
else {Interlocked.Increment(ref Writes);result=new{generation=1,queued=true};}
var bytes=JsonSerializer.SerializeToUtf8Bytes(new{ok=true,result});await server.WriteAsync(bytes,stop.Token);await server.WriteAsync(new byte[]{10},stop.Token);server.WaitForPipeDrain();
}
}
catch(OperationCanceledException)when(stop.IsCancellationRequested){}
}
public async ValueTask DisposeAsync(){stop.Cancel();await Task.WhenAll(navigation,interaction);stop.Dispose();}
public NativeGameInputChannel Channel()=>new(Pid,new string('a',64),"unused");
}
[Test]public async Task OtherShipSamplesAreSkippedUntilTheVerifiedMainShipIsObserved()
{
await using var fixture=new Fixture();var value=await fixture.Channel().ReadAsync(CancellationToken.None);
Check.Equal(42UL,value.EntityId);Check.That(fixture.Reads>=3);Check.Equal(0,fixture.Writes);
}
[Test]public async Task PersistentOtherShipCannotProduceAPlayerInputFrame()
{
await using var fixture=new Fixture{AlwaysOther=true};
try{await fixture.Channel().ReadAsync(CancellationToken.None);throw new Exception("Expected unavailable telemetry");}
catch(FlightTelemetryUnavailableException){}
Check.Equal(0,fixture.Writes);
}
[Test]public async Task UnverifiedOrStaleMainShipBlocksBothObservationAndWrites()
{
foreach(var stale in new[]{false,true})
{
await using var fixture=new Fixture{Verified=stale,Stale=stale};var channel=fixture.Channel();
try{await channel.ReadAsync(CancellationToken.None);throw new Exception("Expected unavailable telemetry");}catch(FlightTelemetryUnavailableException){}
try{await channel.SetAsync(new(1,42,DateTimeOffset.UtcNow,false,false,false),"landingGear",false,CancellationToken.None);throw new Exception("Expected unavailable telemetry");}catch(FlightTelemetryUnavailableException){}
Check.Equal(0,fixture.Reads);Check.Equal(0,fixture.Writes);
}
}
[Test]public async Task ChangedMainShipBlocksAnOldInputCommandBeforeItsPipeWrite()
{
await using var fixture=new Fixture();
try{await fixture.Channel().SetAsync(new(1,99,DateTimeOffset.UtcNow,false,false,false),"landingGear",false,CancellationToken.None);throw new Exception("Expected ship rejection");}
catch(InvalidOperationException){}
Check.Equal(0,fixture.Writes);
}
}