using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Common; using JetBrains.Annotations; using Microsoft.VisualStudio.TestTools.UnitTesting; using TBF.Rig; using TBF.Rig.RegisterReaders.GenesisRegReader; using TBF.Rig.RegisterReaders.GenesisRegReader.common; using TBF.Rig.RegisterReaders.GenesisRegReader.implementations; namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations { [TestClass] [TestSubject(typeof(GenesisSmartReader))] public class GenesisSmartReaderTest { private const int ChannelCount = 3; private static GenesisCfg CreateCfg() { Factory factory = new Factory(); return new GenesisCfg(factory); } private static void SetPrivateField(object target, string fieldName, object value) { var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new MissingFieldException(target.GetType().FullName, fieldName); field.SetValue(target, value); } private static T GetPrivateField(object target, string fieldName) { var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic); if (field == null) throw new MissingFieldException(target.GetType().FullName, fieldName); return (T)field.GetValue(target); } private static void InvokePrepareCalculatedChannelData(GenesisSmartReader reader) { var method = typeof(GenesisSmartReader).GetMethod( "PrepareCalculatedChannelData", BindingFlags.Instance | BindingFlags.NonPublic); if (method == null) throw new MissingMethodException(typeof(GenesisSmartReader).FullName, "PrepareCalculatedChannelData"); method.Invoke(reader, null); } private static void InitializeThreeChannelState(GenesisSmartReader reader) { SetPrivateField(reader, "volumeRawExtLast", new double[ChannelCount]); SetPrivateField(reader, "timestampExtLast", new double[ChannelCount]); SetPrivateField(reader, "lastVolumeRaw", new double[ChannelCount]); SetPrivateField(reader, "lastTimestamp", new double[ChannelCount]); SetPrivateField(reader, "volumeLtr", new[] { 0.0, 0.0, 0.0 }); SetPrivateField(reader, "volumeLtr0", new[] { 0.0, 0.0, 0.0 }); SetPrivateField(reader, "timestampSec", new[] { 0.0, 0.0, 0.0 }); SetPrivateField(reader, "timestampSec0", new[] { 0.0, 0.0, 0.0 }); } private static void EnableProcessingLoopForTests(GenesisSmartReader reader) { SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave); SetPrivateField(reader, "startDataProcessing", true); reader.StopQueueData = false; } private static void WaitForStopToFinish(GenesisSmartReader reader, int timeoutMs = 3000) { var stopTask = GetPrivateField(reader, "_backgroundStopTask"); if (stopTask != null) { stopTask.Wait(timeoutMs); } } [TestMethod] public void Start_ShouldOpenPort_AndEnableProcessing() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.Start(); Assert.IsTrue(fake.IsOpen); Assert.IsTrue(fake.OpenCalls >= 1); Assert.AreEqual(2, fake.DiscardInCalls); Assert.AreEqual(2, fake.DiscardOutCalls); } [TestMethod] public void Stop_ShouldClosePort() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.Start(); Assert.IsTrue(fake.IsOpen); var optoData = GetPrivateField(reader, "optoData"); Assert.IsNotNull(optoData); if (optoData[0] == null) optoData[0] = new OptoTelegramRaw(); if (optoData[1] == null) optoData[1] = new OptoTelegramRaw(); optoData[0].Flags = OptoTelegramFlags.OK; optoData[0].TimestampExt = 100.0; optoData[0].VolumeRawExt = 10.0; optoData[1].Flags = OptoTelegramFlags.OK; optoData[1].TimestampExt = 101.0; optoData[1].VolumeRawExt = 11.0; SetPrivateField(reader, "optoDataCount", 2); SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave); reader.TestStartTelegramIx = 0; reader.TestEndTelegramIx = 1; reader.Stop(); WaitForStopToFinish(reader); Assert.IsFalse(fake.IsOpen); Assert.IsTrue(fake.CloseCalls >= 1); } [TestMethod] public void Run_ShouldCaptureStartTelegramIndex_WhenTimeReached() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); SetPrivateField(reader, "currentTelegramIx", 12); SetPrivateField(reader, "timeFromStart", 7); InitializeThreeChannelState(reader); var ev = reader.Run(); Assert.AreEqual(Event.ReadRegisterDone, ev); Assert.AreEqual(12, reader.TestStartTelegramIx); } [TestMethod] public void ProcessOptoLine_ShouldInsertMultipleTelegrams_WithCorrectChannels() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); bool reset; reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); var optoData = GetPrivateField(reader, "optoData"); var optoDataCount = GetPrivateField(reader, "optoDataCount"); Assert.AreEqual(3, optoDataCount); Assert.AreEqual(0, optoData[0].IChannel()); Assert.AreEqual(1, optoData[1].IChannel()); Assert.AreEqual(2, optoData[2].IChannel()); } [DataTestMethod] [DataRow("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)] [DataRow("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)] [DataRow("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)] public void ProcessOptoLine_ShouldUpdateExpectedChannel(string line, int expectedChannelIndex) { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer); var volumeRawExtLast = GetPrivateField(reader, "volumeRawExtLast"); var timestampExtLast = GetPrivateField(reader, "timestampExtLast"); for (int i = 0; i < ChannelCount; i++) { if (i == expectedChannelIndex) { Assert.IsTrue( volumeRawExtLast[i] != 0 || timestampExtLast[i] != 0, $"Channel {i} was expected to be updated, but it was not."); } else { Assert.AreEqual(0.0, volumeRawExtLast[i], $"Unexpected volume update in channel {i}"); Assert.AreEqual(0.0, timestampExtLast[i], $"Unexpected timestamp update in channel {i}"); } } } [TestMethod] public void Run_WhenChannel0IsAverageModeMinus2_ShouldReadUsingAllThreeChannels() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.Start(); SetPrivateField(reader, "channel0", -2); SetPrivateField(reader, "volumeLtr0", new[] { 10.0, 20.0, 30.0 }); SetPrivateField(reader, "volumeLtr", new[] { 13.0, 24.0, 35.0 }); SetPrivateField(reader, "timestampSec0", new[] { 100.0, 100.0, 100.0 }); SetPrivateField(reader, "timestampSec", new[] { 110.0, 112.0, 114.0 }); var ev = reader.Run(); Assert.AreEqual(Event.ReadRegisterDone, ev); Assert.AreEqual(4.0, reader.WMVolume, 1E-6); Assert.AreEqual(4000, reader.WMPulses); } [TestMethod] public void ProcessOptoLine_FirstValidTelegram_ShouldSetCurrentTelegramIndex() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); var line = "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD"; reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer); int currentTelegramIx = GetPrivateField(reader, "currentTelegramIx"); Assert.AreEqual(0, currentTelegramIx); } [DataTestMethod] [DataRow( "@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", 0)] [DataRow( "@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", 1)] [DataRow( "@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", 2)] public void ProcessOptoLine_ShouldInsertTelegramAndUpdateExpectedChannel(string line, int expectedChannelIndex) { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out bool resetDataBuffer); var volumeRawExtLast = GetPrivateField(reader, "volumeRawExtLast"); var timestampExtLast = GetPrivateField(reader, "timestampExtLast"); var optoData = GetPrivateField(reader, "optoData"); var optoDataCount = GetPrivateField(reader, "optoDataCount"); Assert.AreEqual(1, optoDataCount); var inserted = optoData[0]; Assert.IsNotNull(inserted); Assert.AreEqual(expectedChannelIndex, inserted.IChannel()); Assert.AreEqual(0, inserted.Counter); Assert.AreEqual(OptoTelegramFlags.OK, inserted.Flags); Assert.AreNotEqual(0.0, inserted.VolumeRawExt); Assert.AreNotEqual(0.0, inserted.TimestampExt); for (int i = 0; i < ChannelCount; i++) { if (i == expectedChannelIndex) { Assert.AreNotEqual(0.0, volumeRawExtLast[i], $"Expected channel {i} volume cache was not updated."); Assert.AreNotEqual(0.0, timestampExtLast[i], $"Expected channel {i} timestamp cache was not updated."); } else { Assert.AreEqual(0.0, volumeRawExtLast[i], $"Unexpected volume cache update in channel {i}."); Assert.AreEqual(0.0, timestampExtLast[i], $"Unexpected timestamp cache update in channel {i}."); } } Assert.AreEqual(volumeRawExtLast[expectedChannelIndex], inserted.VolumeRawExt, 1e-9); Assert.AreEqual(timestampExtLast[expectedChannelIndex], inserted.TimestampExt, 1e-9); } [TestMethod] public void VolumeLtrStart_And_VolumeLtrEnd_ShouldUsePreparedCachedChannelData() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); var optoData = GetPrivateField(reader, "optoData"); int idx = 0; for (int group = 0; group < 6; group++) { optoData[idx++] = CreateOptoRecord(0, 10 + group, 100 + group); optoData[idx++] = CreateOptoRecord(1, 20 + group, 100 + group); optoData[idx++] = CreateOptoRecord(2, 30 + group, 100 + group); } SetPrivateField(reader, "optoDataCount", 18); reader.TestStartTelegramIx = 1; reader.TestEndTelegramIx = 17; InvokePrepareCalculatedChannelData(reader); Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStart, 1e-9); Assert.AreEqual((15.0 + 25.0 + 36.0) / 3.0, reader.VolumeLtrEnd, 1e-9); Assert.AreEqual(100.0, reader.TimestampSecStart, 1e-9); Assert.AreEqual(105.0, reader.TimestampSecEnd, 1e-9); Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartAverage, 1e-9); Assert.AreEqual((15.0 + 25.0 + 36.0) / 3.0, reader.VolumeLtrEndAwerage, 1e-9); Assert.AreEqual((11.0 + 20.0 + 30.0) / 3.0, reader.VolumeLtrStartRawRaw, 1e-9); Assert.AreEqual((15.0 + 25.0 + 35.0) / 3.0, reader.VolumeLtrEndRawRaw, 1e-9); } private static OptoTelegramRaw CreateOptoRecord(int channel, double volumeRawExt, double timestampExt) { return new OptoTelegramRaw { Flags = OptoTelegramFlags.OK, VolumeRawExt = volumeRawExt, TimestampExt = timestampExt, iChannel = channel }; } [TestMethod] public void VolumeLtrEnd_ShouldAverageOnlyAvailableChannels() { var fake = new FakeSerialDriver(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); var optoData = GetPrivateField(reader, "optoData"); int idx = 0; for (int i = 0; i < 5; i++) { optoData[idx++] = CreateOptoRecord(0, 10 + i, 100 + i); optoData[idx++] = CreateOptoRecord(1, 20 + i, 100 + i); } SetPrivateField(reader, "optoDataCount", idx); reader.TestStartTelegramIx = 1; reader.TestEndTelegramIx = idx - 1; InvokePrepareCalculatedChannelData(reader); Assert.AreEqual(15.5, reader.VolumeLtrStart, 1e-9); Assert.AreEqual(19.5, reader.VolumeLtrEnd, 1e-9); Assert.AreEqual(15.5, reader.VolumeLtrStartAverage, 1e-9); Assert.AreEqual(19.5, reader.VolumeLtrEndAwerage, 1e-9); Assert.AreEqual(15.5, reader.VolumeLtrStartRawRaw, 1e-9); Assert.AreEqual(19.0, reader.VolumeLtrEndRawRaw, 1e-9); Assert.AreEqual(11.0, reader.VolumeLtrStartRawCh1, 1e-9); Assert.AreEqual(20.0, reader.VolumeLtrStartRawCh2, 1e-9); Assert.AreEqual(0.0, reader.VolumeLtrStartRawCh3, 1e-9); Assert.AreEqual(14.0, reader.VolumeLtrEndRawCh1, 1e-9); Assert.AreEqual(24.0, reader.VolumeLtrEndRawCh2, 1e-9); Assert.AreEqual(0.0, reader.VolumeLtrEndRawCh3, 1e-9); } [TestMethod] public void ProcessOptoLine_Block_f_h1_h2_h3_f_ShouldResetBuffersOnlyAfterLastF() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 1; bool reset; reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset, "Reset must not happen on the first @f."); reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset, "Reset must not happen after @h 1."); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset, "Reset must not happen after @h 2."); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset, "Reset must not happen immediately after @h 3."); reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset); //now some cycles must be runing, so reset must happen. //Assert.IsTrue(reset, "Reset must happen after trailing @f that closes the h1/h2/h3 block."); } [TestMethod] public void ProcessingLoop_FullBlock_ShouldDiscardBuffersAfterClosingF() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 1; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); reader.StartProcessingLoop(); try { reader.TestEnqueueLine("@f AA754B 4D0CEE78 5D89"); reader.TestEnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331"); reader.TestEnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD"); reader.TestEnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E"); reader.TestEnqueueLine("@f AA7C01 4D0CFE76 B08F"); Thread.Sleep(300); Assert.AreEqual(1, fake.DiscardInCalls, "Input buffer should be discarded once after completed block."); Assert.AreEqual(1, fake.DiscardOutCalls, "Output buffer should be discarded once after completed block."); } finally { reader.StopProcessingLoop(); } } [TestMethod] public void ProcessOptoLine_LastF_AfterH3_ShouldRequestBufferReset() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 1; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); bool reset; reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset); Assert.IsTrue(reset, "Closing @f after @h 3 must request reset."); } [TestMethod] public void ProcessOptoLine_LastF_AfterH3_ShouldNotRequestReset_WhenRepetitionCountIsGreaterThanOne() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 3; bool reset; reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset, "Reset must not happen after the first completed block when repetition count is 3."); } [TestMethod] public void ProcessOptoLine_ShouldResetOnlyAfterThirdCompletedBlock() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 3; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); bool reset; for (int repetition = 1; repetition <= 3; repetition++) { reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset); if (repetition < 3) Assert.IsFalse(reset, "Reset must not happen before third full block."); else Assert.IsTrue(reset, "Reset must happen on third full block."); } } [TestMethod] public void ProcessOptoLine_ShouldResetOnlyAfterFifthCompletedBlock() { var fake = new FakeSerialDriver(); fake.Open(); var reader = new GenesisSmartReader(CreateCfg(), () => fake); reader.Initialize(); reader.optoSerialPort = fake; reader.ResetAfterBlockRepetitions = 5; InitializeThreeChannelState(reader); EnableProcessingLoopForTests(reader); bool reset; for (int repetition = 1; repetition <= 5; repetition++) { reader.ProcessOptoLine("@f AA754B 4D0CEE78 5D89", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E", DataStreamState.ProcessAndSave, out reset); Assert.IsFalse(reset); reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset); if (repetition < 5) Assert.IsFalse(reset, "Reset must not happen before fifth full block."); else Assert.IsTrue(reset, "Reset must happen on fifth full block."); } } [TestMethod] public void ProcessingLoop_Should_Keep_FIFO_Order_For_Enqueued_Items() { var reader = new GenesisSmartReader(); var expected = Enumerable.Range(1, 200) .Select(i => $"LINE_{i:D4}") .ToList(); var actual = new List(); var sync = new object(); var allProcessed = new CountdownEvent(expected.Count); reader.TestProcessLineOverride = line => true; EnableProcessingLoopForTests(reader); reader.TestLineProcessed = line => { lock (sync) { actual.Add(line); } allProcessed.Signal(); }; reader.StartProcessingLoop(); try { foreach (var line in expected) { reader.TestEnqueueLine(line); } bool completed = allProcessed.Wait(TimeSpan.FromSeconds(10)); Assert.IsTrue(completed, "Timed out waiting for all queued items to be processed."); CollectionAssert.AreEqual(expected, actual, "Dequeued/processed order does not match enqueued order."); } finally { reader.StopProcessingLoop(); allProcessed.Dispose(); } } [TestMethod] public void ProcessingLoop_Should_Keep_FIFO_Order_With_Concurrent_Producers() { var reader = new GenesisSmartReader(); const int producerCount = 4; const int itemsPerProducer = 100; int totalItems = producerCount * itemsPerProducer; var expected = new List(); var processed = new List(); var expectedLock = new object(); var processedLock = new object(); int sequence = 0; var allProcessed = new CountdownEvent(totalItems); try { reader.TestProcessLineOverride = line => true; EnableProcessingLoopForTests(reader); reader.TestLineProcessed = line => { lock (processedLock) { processed.Add(line); } allProcessed.Signal(); }; reader.StartProcessingLoop(); var tasks = new List(); for (int p = 0; p < producerCount; p++) { int producerId = p; tasks.Add(Task.Run(() => { for (int i = 0; i < itemsPerProducer; i++) { int seq = Interlocked.Increment(ref sequence); string line = string.Format("{0:D6}|P{1}|I{2:D4}", seq, producerId, i); lock (expectedLock) { expected.Add(line); reader.TestEnqueueLine(line); } } })); } Task.WaitAll(tasks.ToArray()); bool completed = allProcessed.Wait(TimeSpan.FromSeconds(15)); Assert.IsTrue(completed, "Timed out waiting for all concurrent items to be processed."); CollectionAssert.AreEqual(expected, processed, "Processed order does not match actual enqueue order under concurrent producers."); } finally { reader.StopProcessingLoop(); allProcessed.Dispose(); } } [TestMethod] public void PrepareCalculatedChannelData_SimulateMode_ReturnsExpectedFakeValues() { var sut = new GenesisSmartReader(); sut.PrepareCalculatedChannelDataSimulationForTest(); Assert.IsFalse(sut.NoSamples); Assert.AreEqual(100.0, sut.VolumeLtrStartCh1, 0.0001); Assert.AreEqual(105.0, sut.VolumeLtrStartCh2, 0.0001); Assert.AreEqual(110.0, sut.VolumeLtrStartCh3, 0.0001); Assert.AreEqual(150.0, sut.VolumeLtrEndCh1, 0.0001); Assert.AreEqual(157.0, sut.VolumeLtrEndCh2, 0.0001); Assert.AreEqual(164.0, sut.VolumeLtrEndCh3, 0.0001); Assert.AreEqual(10.0, sut.TimestampSecStartCh1, 0.0001); Assert.AreEqual(10.0, sut.TimestampSecStartCh2, 0.0001); Assert.AreEqual(10.0, sut.TimestampSecStartCh3, 0.0001); Assert.AreEqual(20.0, sut.TimestampSecEndCh1, 0.0001); Assert.AreEqual(20.0, sut.TimestampSecEndCh2, 0.0001); Assert.AreEqual(20.0, sut.TimestampSecEndCh3, 0.0001); Assert.AreEqual(105.0, sut.VolumeLtrStartAverage, 0.0001); Assert.AreEqual(157.0, sut.VolumeLtrEndAwerage, 0.0001); Assert.AreEqual(10.0, sut.TimestampSecStart, 0.0001); Assert.AreEqual(20.0, sut.TimestampSecEnd, 0.0001); } } }