tbf/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderTest.cs

791 lines
32 KiB
C#

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<T>(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<Task>(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<OptoTelegramRaw[]>(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<OptoTelegramRaw[]>(reader, "optoData");
var optoDataCount = GetPrivateField<int>(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<double[]>(reader, "volumeRawExtLast");
var timestampExtLast = GetPrivateField<double[]>(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<int>(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<double[]>(reader, "volumeRawExtLast");
var timestampExtLast = GetPrivateField<double[]>(reader, "timestampExtLast");
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
var optoDataCount = GetPrivateField<int>(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<OptoTelegramRaw[]>(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<OptoTelegramRaw[]>(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<string>();
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<string>();
var processed = new List<string>();
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<Task>();
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);
}
}
}