tbf/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisSmartReaderThreadedReadTests.cs
Michal Buzik 08dff1272b Add overflow volume/timestamp handling and related tests for GenesisSmartReader:
- Extend `CalibrationRecord` constructor to include overflow fields (`OverflowVolumeCm`, `OverflowTimeS`).
- Implement additional unit tests in `OptoTelegramRawTest` to validate multi-rollover scenarios and extended calculations.
- Add `GenesisSmartReaderThreadedReadTests` to cover threaded read-loop functionality and serial processing.
- Enhance logging in `Pump` classes (`TurnOff` methods).
- Update project file to include new test files.
2026-04-02 09:25:17 +02:00

220 lines
8.1 KiB
C#

using System;
using System.Reflection;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
[TestClass]
public class GenesisSmartReaderThreadedReadTests
{
private const int ChannelCount = 3;
private static GenesisCfg CreateCfg()
{
var cfg = new GenesisCfg(null);
cfg.OptoComPortNr = 7;
cfg.RfidComPortNr = 8;
return cfg;
}
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 bool WaitUntil(Func<bool> condition, int timeoutMs = 1500, int pollMs = 20)
{
var start = Environment.TickCount;
while (Environment.TickCount - start < timeoutMs)
{
if (condition())
return true;
Thread.Sleep(pollMs);
}
return condition();
}
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 });
}
[TestMethod]
public void Start_ShouldOpenPort_AndStartBackgroundReadLoop()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen);
Assert.IsTrue(fake.OpenCalls >= 1);
var readLoopTask = GetPrivateField<System.Threading.Tasks.Task>(reader, "_readLoopTask");
Assert.IsNotNull(readLoopTask);
}
[TestMethod]
public void BackgroundReadLoop_ShouldMoveIncomingLinesToQueue()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
InitializeThreeChannelState(reader);
reader.Start();
fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
bool queued = WaitUntil(() =>
{
var queue = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
return !queue.IsEmpty;
});
Assert.IsTrue(queued, "Expected background task to enqueue received serial line.");
}
[TestMethod]
public void RunDeviceBefore_ShouldDrainQueue_AndInsertTelegram()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
InitializeThreeChannelState(reader);
reader.Start();
fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
bool queued = WaitUntil(() =>
{
var queue = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
return !queue.IsEmpty;
});
Assert.IsTrue(queued, "Expected line to be queued before processing.");
reader.RunDeviceBefore();
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
Assert.AreEqual(1, optoDataCount);
Assert.AreEqual(1, optoData[0].IChannel());
}
[TestMethod]
public void RunDeviceBefore_ShouldDrainAllQueuedLines()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
InitializeThreeChannelState(reader);
reader.Start();
fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
bool queued = WaitUntil(() =>
{
var queue = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
return !queue.IsEmpty;
});
Assert.IsTrue(queued);
reader.RunDeviceBefore();
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
var queueAfter = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
Assert.AreEqual(3, optoDataCount);
Assert.IsTrue(queueAfter.IsEmpty, "Queue should be empty after RunDeviceBefore drains it.");
}
[TestMethod]
public void StopDataStreamProcessing_ShouldStopReadLoop_AndClosePort()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
reader.Start();
Assert.IsTrue(fake.IsOpen);
reader.StopDataStreamProcessing();
Assert.IsFalse(fake.IsOpen);
var cts = GetPrivateField<System.Threading.CancellationTokenSource>(reader, "_readLoopCts");
var task = GetPrivateField<System.Threading.Tasks.Task>(reader, "_readLoopTask");
Assert.IsNull(cts);
Assert.IsNull(task);
}
[TestMethod]
public void IncomingLines_ShouldNotBeProcessedUntilRunDeviceBeforeIsCalled()
{
var fake = new FakeSerialDriver();
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
reader.Initialize();
InitializeThreeChannelState(reader);
reader.Start();
fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
bool queued = WaitUntil(() =>
{
var queue = GetPrivateField<System.Collections.Concurrent.ConcurrentQueue<string>>(reader, "_receivedLines");
return !queue.IsEmpty;
});
Assert.IsTrue(queued);
int optoDataCountBefore = GetPrivateField<int>(reader, "optoDataCount");
Assert.AreEqual(0, optoDataCountBefore, "Background thread should only enqueue, not process.");
reader.RunDeviceBefore();
int optoDataCountAfter = GetPrivateField<int>(reader, "optoDataCount");
Assert.AreEqual(1, optoDataCountAfter);
}
}
}