- Introduce `ForceProcessingEnabled`, `InstallSuccessfulProcessingOverride`, and `EnqueueIncomingLine` helpers to streamline test setup. - Add new test suite `GenesisSmartReaderStopTests` to validate `Stop` behavior for different processing states. - Enhance `GenesisSmartReader` state management with a dedicated `Reset` method and improved `Stop` logic. - Improve buffer initialization in `ProcessOptoLine` to address null-value edge cases. - Update assembly version to `3.9.3043.1`.
389 lines
14 KiB
C#
389 lines
14 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 });
|
|
}
|
|
|
|
private static FieldInfo GetAnyField(object target, string fieldName)
|
|
{
|
|
var field = target.GetType().GetField(
|
|
fieldName,
|
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
|
|
|
if (field == null)
|
|
throw new MissingFieldException(target.GetType().FullName, fieldName);
|
|
|
|
return field;
|
|
}
|
|
|
|
private static void SetField(object target, string fieldName, object value)
|
|
{
|
|
GetAnyField(target, fieldName).SetValue(target, value);
|
|
}
|
|
|
|
private static T GetField<T>(object target, string fieldName)
|
|
{
|
|
return (T)GetAnyField(target, fieldName).GetValue(target);
|
|
}
|
|
|
|
private static void WaitForStartupFlushToFinish(GenesisSmartReader reader, int timeoutMs = 3000)
|
|
{
|
|
bool finished = WaitUntil(() =>
|
|
{
|
|
return !GetPrivateField<bool>(reader, "_startupFlushActive");
|
|
}, timeoutMs);
|
|
|
|
Assert.IsTrue(finished, "Startup flush did not finish in time.");
|
|
}
|
|
|
|
private static void ForceProcessingEnabled(GenesisSmartReader reader)
|
|
{
|
|
SetField(reader, "_startupFlushActive", false);
|
|
SetField(reader, "_stopQueueData", false);
|
|
SetField(reader, "dataStreamState", DataStreamState.ProcessAndSave);
|
|
SetField(reader, "startDataProcessing", true);
|
|
|
|
var signal = GetField<AutoResetEvent>(reader, "_queueSignal");
|
|
signal.Set();
|
|
}
|
|
|
|
private static void EnqueueIncomingLine(GenesisSmartReader reader, string line)
|
|
{
|
|
var queue = GetField<System.Collections.Concurrent.ConcurrentQueue<(DateTime Timestamp, string Line)>>(
|
|
reader, "_receivedLines");
|
|
|
|
var signal = GetField<AutoResetEvent>(reader, "_queueSignal");
|
|
|
|
queue.Enqueue((DateTime.UtcNow, line));
|
|
signal.Set();
|
|
}
|
|
|
|
private static void InstallSuccessfulProcessingOverride(GenesisSmartReader reader)
|
|
{
|
|
Func<string, bool> handler = line =>
|
|
{
|
|
var optoData = GetField<OptoTelegramRaw[]>(reader, "optoData");
|
|
int count = GetField<int>(reader, "optoDataCount");
|
|
|
|
optoData[count].Counter = count;
|
|
optoData[count].SetFlags(OptoTelegramFlags.OK);
|
|
|
|
SetField(reader, "optoDataCount", count + 1);
|
|
return true;
|
|
};
|
|
|
|
SetField(reader, "TestProcessLineOverride", handler);
|
|
}
|
|
|
|
[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 ProcessingLoop_ShouldConsumeQueuedLine()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
|
|
ForceProcessingEnabled(reader);
|
|
InstallSuccessfulProcessingOverride(reader);
|
|
|
|
EnqueueIncomingLine(reader, "TEST_LINE_1");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetField<int>(reader, "optoDataCount");
|
|
return optoDataCount == 1;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Expected processing loop to consume queued line.");
|
|
|
|
int finalCount = GetField<int>(reader, "optoDataCount");
|
|
Assert.AreEqual(1, finalCount);
|
|
}
|
|
|
|
|
|
[TestMethod]
|
|
public void ProcessingLoop_ShouldConsumeQueuedLine_AndInsertTelegram()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
fake.Encoding = System.Text.Encoding.ASCII;
|
|
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
|
|
ForceProcessingEnabled(reader);
|
|
|
|
EnqueueIncomingLine(reader,
|
|
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
return optoDataCount >= 1;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Expected processing loop to consume queued line.");
|
|
|
|
int finalCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
var optoData = GetPrivateField<OptoTelegramRaw[]>(reader, "optoData");
|
|
|
|
Assert.AreEqual(1, finalCount);
|
|
Assert.AreEqual(1, optoData[0].IChannel());
|
|
}
|
|
|
|
[TestMethod]
|
|
public void ProcessingLoop_ShouldConsumeAllQueuedLines()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
|
|
ForceProcessingEnabled(reader);
|
|
InstallSuccessfulProcessingOverride(reader);
|
|
|
|
EnqueueIncomingLine(reader, "TEST_LINE_1");
|
|
EnqueueIncomingLine(reader, "TEST_LINE_2");
|
|
EnqueueIncomingLine(reader, "TEST_LINE_3");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetField<int>(reader, "optoDataCount");
|
|
return optoDataCount == 3;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Expected all queued lines to be processed.");
|
|
|
|
int finalCount = GetField<int>(reader, "optoDataCount");
|
|
var queueAfter = GetField<System.Collections.Concurrent.ConcurrentQueue<(DateTime Timestamp, string Line)>>(
|
|
reader, "_receivedLines");
|
|
|
|
Assert.AreEqual(3, finalCount);
|
|
Assert.IsTrue(queueAfter.IsEmpty);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IncomingLines_ShouldBeProcessedByBackgroundProcessingLoop()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
WaitForStartupFlushToFinish(reader);
|
|
|
|
fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
return optoDataCount >= 1;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Expected background processing loop to process line.");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StopDataStreamProcessing_ShouldStopReadAndProcessingLoops()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
|
|
reader.StopDataStreamProcessing();
|
|
|
|
var readLoopTask = GetPrivateField<System.Threading.Tasks.Task>(reader, "_readLoopTask");
|
|
var readLoopCts = GetPrivateField<CancellationTokenSource>(reader, "_readLoopCts");
|
|
var processLoopTask = GetPrivateField<System.Threading.Tasks.Task>(reader, "_processLoopTask");
|
|
var processLoopCts = GetPrivateField<CancellationTokenSource>(reader, "_processLoopCts");
|
|
|
|
Assert.IsNull(readLoopTask);
|
|
Assert.IsNull(readLoopCts);
|
|
Assert.IsNull(processLoopTask);
|
|
Assert.IsNull(processLoopCts);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void IncomingLine_AfterStop_ShouldNotBeProcessed()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
|
|
reader.StopDataStreamProcessing();
|
|
|
|
int before = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
fake.EnqueueLine("@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331");
|
|
|
|
Thread.Sleep(300);
|
|
|
|
int after = GetPrivateField<int>(reader, "optoDataCount");
|
|
|
|
Assert.AreEqual(before, after, "No new telegram should be processed after stop.");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void InvalidLine_ShouldNotCrashProcessingLoop()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
reader.Start();
|
|
WaitForStartupFlushToFinish(reader);
|
|
|
|
fake.EnqueueLine("INVALID_LINE_THAT_WILL_NOT_PARSE");
|
|
fake.EnqueueLine("@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
return optoDataCount >= 1;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Processing loop should survive invalid input and continue.");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void StartStop_StartAgain_ShouldStillProcessData()
|
|
{
|
|
var fake = new FakeSerialDriver();
|
|
var reader = new GenesisSmartReader(CreateCfg(), () => fake);
|
|
|
|
reader.Initialize();
|
|
InitializeThreeChannelState(reader);
|
|
|
|
reader.Start();
|
|
reader.StopDataStreamProcessing();
|
|
|
|
reader.Start();
|
|
WaitForStartupFlushToFinish(reader);
|
|
|
|
fake.EnqueueLine("@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD");
|
|
|
|
bool processed = WaitUntil(() =>
|
|
{
|
|
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
|
|
return optoDataCount >= 1;
|
|
}, 3000);
|
|
|
|
Assert.IsTrue(processed, "Reader should still process data after restart.");
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Start_ShouldOpenPort_AndStartBothBackgroundLoops()
|
|
{
|
|
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");
|
|
var processLoopTask = GetPrivateField<System.Threading.Tasks.Task>(reader, "_processLoopTask");
|
|
|
|
Assert.IsNotNull(readLoopTask);
|
|
Assert.IsNotNull(processLoopTask);
|
|
}
|
|
|
|
}
|
|
} |