using Nonemm.Digital; namespace Nonemm.Digital.Tests; /// The engine driven through a bridge that goes nowhere. public class MmttyEngineTests { private static readonly TimeSpan Patience = TimeSpan.FromSeconds(5); private static MmttyEngine Engine(FakeBridge bridge) => new(bridge, new MmttyOptions { EnginePath = "/opt/mmtty/mmtty.exe" }, Patience); private static async Task StartAsync(MmttyEngine engine, FakeBridge bridge) { Task starting = engine.StartAsync(); await WaitForAsync(() => bridge.Sent.Count > 0); bridge.Say("connected", "Ver1.70"); await starting.WaitAsync(Patience); } private static async Task WaitForAsync(Func ready) { DateTime giveUp = DateTime.UtcNow + Patience; while (!ready() && DateTime.UtcNow < giveUp) { await Task.Delay(5); } } [Fact] public async Task TheEngineIsOpenedWithTitlePortAndCommandLine() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); await StartAsync(engine, bridge); (string verb, string[] fields) = BridgeLine.Read(bridge.Sent[0]); Assert.Equal("open", verb); Assert.Equal("RTTY Engine 1", fields[0]); Assert.Equal(@"""Z:\opt\mmtty\mmtty.exe"" -r -Z", fields[2]); Assert.True(engine.IsConnected); Assert.Equal("Ver1.70", engine.Version); } [Fact] public async Task AnEngineThatFailsToStartIsStartedAgain() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); Task starting = engine.StartAsync(); await WaitForAsync(() => bridge.Sent.Count > 0); bridge.Say("disconnected", "2"); await WaitForAsync(() => bridge.Sent.Count > 1); bridge.Say("connected", "Ver1.70"); await starting.WaitAsync(Patience); Assert.Equal(2, bridge.Sent.Count); Assert.All(bridge.Sent, line => Assert.StartsWith("open\t", line)); } [Fact] public async Task AnEngineThatNeverStartsGivesUpRatherThanHanging() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); Task starting = engine.StartAsync(); for (int tries = 0; tries < 12; tries++) { await WaitForAsync(() => bridge.Sent.Count > tries); bridge.Say("disconnected", "2"); } await Assert.ThrowsAsync(() => starting.WaitAsync(Patience)); } [Fact] public async Task DecodedCharactersArriveAsText() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); List heard = []; engine.Received += (_, text) => heard.Add(text); await StartAsync(engine, bridge); bridge.Say("rx", "79"); bridge.Say("rx", "77"); await WaitForAsync(() => heard.Count == 2); Assert.Equal("OM", string.Concat(heard)); } [Fact] public async Task TransmitIsReportedBothWays() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); List changes = []; engine.TransmitChanged += (_, sending) => changes.Add(sending); await StartAsync(engine, bridge); bridge.Say("tx", "1"); await WaitForAsync(() => changes.Count == 1); Assert.True(engine.IsTransmitting); bridge.Say("tx", "0"); await WaitForAsync(() => changes.Count == 2); Assert.False(engine.IsTransmitting); } [Fact] public async Task TuningKeepsTheShiftTheEngineReported() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); await StartAsync(engine, bridge); bridge.Say("mark", "2125"); bridge.Say("space", "2295"); await WaitForAsync(() => engine.SpaceHertz == 2295); await engine.TuneAsync(1500); Assert.Equal("post\t9\t1500", bridge.Sent[^2]); Assert.Equal("post\t10\t1670", bridge.Sent[^1]); } [Fact] public async Task AbortDropsPtt() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); await StartAsync(engine, bridge); await engine.AbortAsync(); Assert.Equal("ptt\t0", bridge.Sent[^1]); } [Fact] public async Task ABridgeThatDiesWhileStartingFailsTheStart() { using FakeBridge bridge = new(); using MmttyEngine engine = Engine(bridge); Task starting = engine.StartAsync(); await WaitForAsync(() => bridge.Sent.Count > 0); bridge.Stop("the bridge exited with code 1"); await Assert.ThrowsAsync(() => starting.WaitAsync(Patience)); } }