- 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.
220 lines
8.1 KiB
C#
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);
|
|
}
|
|
}
|
|
} |