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 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 async Task 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(await Task.WhenAny(eof, Task.Delay(2000)) == eof, "server did not drop the connection"); Assert.True(await eof); } 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); } }