Files
VRIO/tests/VPlasma.Core.Tests/VPlasmaPipeServiceTests.cs
T
CydandClaude Fable 5 e507f1740c Named-pipe transport for the DOSBox-X fork's namedpipe backend
Both devices now serve \.\pipe\vrio and \.\pipe\vplasma for the whole
app lifetime alongside the COM rows — the com0com-free path. Framing per
the pinned contract (0x00 len data / 0x01 DTR+RTS lines, one lines frame
on connect, unknown type = log + drop): PipeFraming/PipeFrameDecoder and
VRioPipeService (TX paced at the wire rate, peer DTR edges feed
HostHandshake) in VRio.Core, listener-only VPlasmaPipeService twin in
VPlasma.Core. Busy pipe names retry, so vRIO's built-in glass and the
standalone vPLASMA coexist. README documents the transport and the fork
conf lines.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-12 23:03:02 -05:00

99 lines
3.2 KiB
C#

using System.Diagnostics;
using System.IO.Pipes;
using VPlasma.Core.Device;
using Xunit;
namespace VPlasma.Core.Tests;
public class VPlasmaPipeServiceTests
{
private static string UniqueName() => $"vplasma-test-{Guid.NewGuid():N}";
private static void WaitFor(Func<bool> condition, int timeoutMs = 2000)
{
var clock = Stopwatch.StartNew();
while (!condition())
{
Assert.True(clock.ElapsedMilliseconds < timeoutMs, "timed out waiting for a condition");
Thread.Sleep(10);
}
}
private static NamedPipeClientStream Connect(string name)
{
var pipe = new NamedPipeClientStream(".", name, PipeDirection.InOut, PipeOptions.Asynchronous);
var clock = Stopwatch.StartNew();
while (true)
{
try
{
pipe.Connect(200);
return pipe;
}
catch (Exception ex) when (ex is IOException or TimeoutException)
{
if (clock.ElapsedMilliseconds >= 3000)
throw;
Thread.Sleep(25);
}
}
}
[Fact]
public void Framed_text_reaches_the_display_and_lines_are_ignored()
{
string name = UniqueName();
var device = new VPlasmaDevice();
using var service = new VPlasmaPipeService(device, name);
service.Start();
using var client = Connect(name);
var hello = new byte[2];
Assert.Equal(1, client.Read(hello, 0, 1)); // on-connect lines frame,
Assert.Equal(1, client.Read(hello, 1, 1)); // possibly split
Assert.Equal(new byte[] { 0x01, 0x03 }, hello); // DTR+RTS: display present
// RTS chatter (DOS console redirection toggles it per byte) then "HI",
// the data frame torn in half mid-payload.
client.Write(new byte[] { 0x01, 0x02, 0x00, 0x02, (byte)'H' }, 0, 5);
client.Flush();
Thread.Sleep(20);
client.Write(new byte[] { (byte)'I' }, 0, 1);
client.Flush();
WaitFor(() => device.BytesReceived == 2);
Assert.Equal(2, device.TextCharsDrawn);
}
[Fact]
public void Unknown_frame_type_drops_the_client_and_server_recovers()
{
string name = UniqueName();
var device = new VPlasmaDevice();
using var service = new VPlasmaPipeService(device, name);
service.Start();
using (var bad = Connect(name))
{
var hello = new byte[2];
bad.Read(hello, 0, 2);
bad.Write(new byte[] { 0x7F }, 0, 1); // not a frame type
bad.Flush();
// Contract: log + drop, no resync — our next read sees the close.
var eof = Task.Run(() =>
{
try { return bad.Read(new byte[1], 0, 1) == 0; }
catch (IOException) { return true; }
});
Assert.True(eof.Wait(2000), "server did not drop the connection");
Assert.True(eof.Result);
}
using var good = Connect(name);
var again = new byte[2];
Assert.Equal(1, good.Read(again, 0, 1));
Assert.Equal(1, good.Read(again, 1, 1));
Assert.Equal(new byte[] { 0x01, 0x03 }, again);
}
}