- Replace property getters with concise expressions in `GenesisSmartReader`. - Implement FIFO and concurrent producer-consumer unit tests for `ProcessingLoop`. - Add startup flush mechanism for improved input handling on initialization. - Refactor processing loop with structured start/stop tasks and cancellation tokens. - Fix thread-safety issues and enhance logic in `Stop` and `StopDataStreamProcessing`. - Add internal visibility for tests in `TBFTests` via `InternalsVisibleTo`. - Update assembly version to `3.9.3038.1`.
511 lines
17 KiB
C#
511 lines
17 KiB
C#
using System;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
|
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
|
|
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
|
|
|
|
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
|
|
{
|
|
[TestClass]
|
|
public class GenesisSmartReaderChannelTests
|
|
{
|
|
private GenesisSmartReader _reader;
|
|
|
|
[TestInitialize]
|
|
public void Setup()
|
|
{
|
|
_reader = new GenesisSmartReader();
|
|
}
|
|
|
|
private static OptoTelegramRaw Rec(
|
|
int channel,
|
|
double time,
|
|
double volume,
|
|
OptoTelegramFlags flags = OptoTelegramFlags.OK)
|
|
{
|
|
return new OptoTelegramRaw
|
|
{
|
|
iChannel = channel,
|
|
TimestampExt = time,
|
|
VolumeRawExt = volume,
|
|
Flags = flags
|
|
};
|
|
}
|
|
|
|
private object InvokePrivate(string methodName, params object[] args)
|
|
{
|
|
var mi = typeof(GenesisSmartReader).GetMethod(
|
|
methodName,
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
Assert.IsNotNull(mi, $"Method '{methodName}' was not found.");
|
|
|
|
return mi.Invoke(_reader, args);
|
|
}
|
|
|
|
private static T GetPrivateField<T>(object instance, string fieldName)
|
|
{
|
|
var fi = instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
|
Assert.IsNotNull(fi, $"Field '{fieldName}' was not found.");
|
|
return (T)fi.GetValue(instance);
|
|
}
|
|
|
|
private static void SetPrivateField(object instance, string fieldName, object value)
|
|
{
|
|
var fi = instance.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
|
|
Assert.IsNotNull(fi, $"Field '{fieldName}' was not found.");
|
|
fi.SetValue(instance, value);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GroupRecordsPerChannel_Groups_Valid_Records_By_Channel()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
Rec(0, 13, 110),
|
|
Rec(1, 14, 220),
|
|
Rec(2, 15, 330),
|
|
};
|
|
|
|
var grouped = (OptoTelegramRaw[][])InvokePrivate(
|
|
"GroupRecordsPerChannel",
|
|
(object)input, 0, input.Length - 1, input.Length);
|
|
|
|
Assert.AreEqual(3, grouped.Length);
|
|
|
|
Assert.AreEqual(2, grouped[0].Length);
|
|
Assert.AreEqual(2, grouped[1].Length);
|
|
Assert.AreEqual(2, grouped[2].Length);
|
|
|
|
Assert.AreEqual(100, grouped[0][0].VolumeRawExt);
|
|
Assert.AreEqual(110, grouped[0][1].VolumeRawExt);
|
|
|
|
Assert.AreEqual(200, grouped[1][0].VolumeRawExt);
|
|
Assert.AreEqual(220, grouped[1][1].VolumeRawExt);
|
|
|
|
Assert.AreEqual(300, grouped[2][0].VolumeRawExt);
|
|
Assert.AreEqual(330, grouped[2][1].VolumeRawExt);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GroupRecordsPerChannel_Ignores_Invalid_Records()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100, OptoTelegramFlags.OK),
|
|
Rec(1, 11, 200, OptoTelegramFlags.InvalidTelegram),
|
|
Rec(2, 12, 300, OptoTelegramFlags.OK),
|
|
Rec(0, 13, 110, OptoTelegramFlags.SyncError),
|
|
Rec(1, 14, 220, OptoTelegramFlags.OK),
|
|
};
|
|
|
|
var grouped = (OptoTelegramRaw[][])InvokePrivate(
|
|
"GroupRecordsPerChannel",
|
|
(object)input, 0, input.Length - 1, input.Length);
|
|
|
|
Assert.AreEqual(1, grouped[0].Length);
|
|
Assert.AreEqual(1, grouped[1].Length);
|
|
Assert.AreEqual(1, grouped[2].Length);
|
|
|
|
Assert.AreEqual(100, grouped[0][0].VolumeRawExt);
|
|
Assert.AreEqual(220, grouped[1][0].VolumeRawExt);
|
|
Assert.AreEqual(300, grouped[2][0].VolumeRawExt);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GroupRecordsPerChannel_Swaps_Start_And_End_When_Reversed()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(0, 12, 120),
|
|
};
|
|
|
|
var grouped = (OptoTelegramRaw[][])InvokePrivate(
|
|
"GroupRecordsPerChannel",
|
|
(object)input, 2, 0, input.Length);
|
|
|
|
Assert.AreEqual(2, grouped[0].Length);
|
|
Assert.AreEqual(1, grouped[1].Length);
|
|
Assert.AreEqual(0, grouped[2].Length);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GroupRecordsPerChannel_Returns_Empty_For_Invalid_Range()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
};
|
|
|
|
var grouped = (OptoTelegramRaw[][])InvokePrivate(
|
|
"GroupRecordsPerChannel",
|
|
(object)input, -1, 1, input.Length);
|
|
|
|
Assert.AreEqual(3, grouped.Length);
|
|
Assert.IsTrue(grouped.All(x => x.Length == 0));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void TimeDeltaPerChannel_Computes_First_Last_Deltas()
|
|
{
|
|
var grouped = new[]
|
|
{
|
|
new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(0, 18, 170),
|
|
Rec(0, 15, 120)
|
|
},
|
|
new[]
|
|
{
|
|
Rec(1, 20, 500),
|
|
Rec(1, 23, 530)
|
|
},
|
|
new[]
|
|
{
|
|
Rec(2, 30, 900)
|
|
}
|
|
};
|
|
|
|
var result = (ValueTuple<int, double?, double?>[])InvokePrivate(
|
|
"TimeDeltaPerChannel",
|
|
(object)grouped);
|
|
|
|
Assert.AreEqual(0, result[0].Item1);
|
|
Assert.AreEqual(5, result[0].Item2);
|
|
Assert.AreEqual(20, result[0].Item3);
|
|
|
|
Assert.AreEqual(1, result[1].Item1);
|
|
Assert.AreEqual(3, result[1].Item2);
|
|
Assert.AreEqual(30, result[1].Item3);
|
|
|
|
Assert.AreEqual(2, result[2].Item1);
|
|
Assert.IsNull(result[2].Item2);
|
|
Assert.IsNull(result[2].Item3);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void VolumeStartPerChannel_Returns_Last_Seen_Value_Backward_From_Index()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(0, 12, 120),
|
|
Rec(2, 13, 300),
|
|
Rec(1, 14, 220),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"VolumeStartPerChannel",
|
|
(object)input, input.Length, 4);
|
|
|
|
Assert.AreEqual(120, result[0]);
|
|
Assert.AreEqual(220, result[1]);
|
|
Assert.AreEqual(300, result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void TimeStartPerChannel_Returns_Last_Seen_Time_Backward_From_Index()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(0, 12, 120),
|
|
Rec(2, 13, 300),
|
|
Rec(1, 14, 220),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"TimeStartPerChannel",
|
|
(object)input, input.Length, 4);
|
|
|
|
Assert.AreEqual(12, result[0]);
|
|
Assert.AreEqual(14, result[1]);
|
|
Assert.AreEqual(13, result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void VolumeEndPerChannel_Averages_Last_N_Samples_Per_Channel()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
Rec(0, 13, 120),
|
|
Rec(1, 14, 220),
|
|
Rec(2, 15, 330),
|
|
Rec(0, 16, 140),
|
|
Rec(1, 17, 240),
|
|
Rec(2, 18, 360),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"VolumeEndPerChannel",
|
|
(object)input, input.Length, 8, 2);
|
|
|
|
Assert.AreEqual((140 + 120) / 2.0, result[0]);
|
|
Assert.AreEqual((240 + 220) / 2.0, result[1]);
|
|
Assert.AreEqual((360 + 330) / 2.0, result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void TimeEndPerChannel_Averages_Last_N_Samples_Per_Channel()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
Rec(0, 13, 120),
|
|
Rec(1, 14, 220),
|
|
Rec(2, 15, 330),
|
|
Rec(0, 16, 140),
|
|
Rec(1, 17, 240),
|
|
Rec(2, 18, 360),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"TimeEndPerChannel",
|
|
(object)input, input.Length, 8, 2);
|
|
|
|
Assert.AreEqual((16 + 13) / 2.0, result[0]);
|
|
Assert.AreEqual((17 + 14) / 2.0, result[1]);
|
|
Assert.AreEqual((18 + 15) / 2.0, result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void VolumeStartPerChannel_Returns_All_Nulls_For_OutOfRange_Index()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"VolumeStartPerChannel",
|
|
(object)input, input.Length, -1);
|
|
|
|
Assert.IsNull(result[0]);
|
|
Assert.IsNull(result[1]);
|
|
Assert.IsNull(result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void TimeEndPerChannel_Uses_One_Sample_When_SamplesPerChannel_Is_Zero()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
Rec(0, 13, 120),
|
|
Rec(1, 14, 220),
|
|
Rec(2, 15, 330),
|
|
};
|
|
|
|
var result = (double?[])InvokePrivate(
|
|
"TimeEndPerChannel",
|
|
(object)input, input.Length, 5, 0);
|
|
|
|
Assert.AreEqual(13, result[0]);
|
|
Assert.AreEqual(14, result[1]);
|
|
Assert.AreEqual(15, result[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Precreated_OptoData_Private_Methods_Read_Correct_Values()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
Rec(0, 13, 120),
|
|
Rec(1, 14, 220),
|
|
Rec(2, 15, 330),
|
|
Rec(0, 16, 140),
|
|
Rec(1, 17, 240),
|
|
Rec(2, 18, 360),
|
|
};
|
|
|
|
SetPrivateField(_reader, "optoData", input);
|
|
SetPrivateField(_reader, "optoDataCount", input.Length);
|
|
|
|
_reader.TestStartTelegramIx = 5;
|
|
_reader.TestEndTelegramIx = 8;
|
|
|
|
Assert.AreEqual(input.Length, GetPrivateField<int>(_reader, "optoDataCount"));
|
|
|
|
var startVolumes = (double?[])InvokePrivate(
|
|
"VolumeStartPerChannel",
|
|
(object)input, input.Length, 5);
|
|
|
|
var endVolumes = (double?[])InvokePrivate(
|
|
"VolumeEndPerChannel",
|
|
(object)input, input.Length, 8, 2 * GenesisSmartReader.StartEndFilterSamplesCount2 + 1);
|
|
|
|
var startTimes = (double?[])InvokePrivate(
|
|
"TimeStartPerChannel",
|
|
(object)input, input.Length, 5);
|
|
|
|
var endTimes = (double?[])InvokePrivate(
|
|
"TimeEndPerChannel",
|
|
(object)input, input.Length, 8, 2 * GenesisSmartReader.StartEndFilterSamplesCount2 + 1);
|
|
|
|
Assert.AreEqual(120, startVolumes[0]);
|
|
Assert.AreEqual(220, startVolumes[1]);
|
|
Assert.AreEqual(330, startVolumes[2]);
|
|
|
|
Assert.AreEqual(13, startTimes[0]);
|
|
Assert.AreEqual(14, startTimes[1]);
|
|
Assert.AreEqual(15, startTimes[2]);
|
|
|
|
Assert.AreEqual((140 + 120 + 100) / 3.0, endVolumes[0]);
|
|
Assert.AreEqual((240 + 220 + 200) / 3.0, endVolumes[1]);
|
|
Assert.AreEqual((360 + 330 + 300) / 3.0, endVolumes[2]);
|
|
|
|
Assert.AreEqual((16 + 13 + 10) / 3.0, endTimes[0]);
|
|
Assert.AreEqual((17 + 14 + 11) / 3.0, endTimes[1]);
|
|
Assert.AreEqual((18 + 15 + 12) / 3.0, endTimes[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Public_Channel_Properties_Default_To_Zero_When_NoSamples_Is_True()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 11, 200),
|
|
Rec(2, 12, 300),
|
|
};
|
|
|
|
SetPrivateField(_reader, "optoData", input);
|
|
SetPrivateField(_reader, "optoDataCount", input.Length);
|
|
|
|
_reader.TestStartTelegramIx = 5;
|
|
_reader.TestEndTelegramIx = 8;
|
|
|
|
Assert.IsTrue(_reader.NoSamples);
|
|
|
|
Assert.AreEqual(0, _reader.VolumeLtrStartCh1);
|
|
Assert.AreEqual(0, _reader.VolumeLtrStartCh2);
|
|
Assert.AreEqual(0, _reader.VolumeLtrStartCh3);
|
|
|
|
Assert.AreEqual(0, _reader.VolumeLtrEndCh1);
|
|
Assert.AreEqual(0, _reader.VolumeLtrEndCh2);
|
|
Assert.AreEqual(0, _reader.VolumeLtrEndCh3);
|
|
|
|
Assert.AreEqual(0, _reader.TimestampSecStartCh1);
|
|
Assert.AreEqual(0, _reader.TimestampSecStartCh2);
|
|
Assert.AreEqual(0, _reader.TimestampSecStartCh3);
|
|
|
|
Assert.AreEqual(0, _reader.TimestampSecEndCh1);
|
|
Assert.AreEqual(0, _reader.TimestampSecEndCh2);
|
|
Assert.AreEqual(0, _reader.TimestampSecEndCh3);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void Copy_Should_Copy_From_Source_Record()
|
|
{
|
|
var source = Rec(2, 123.5, 456.7, OptoTelegramFlags.OK_TestEnd);
|
|
source.Counter = 77;
|
|
source.RefFlow = 9.5f;
|
|
source.FlowRaw = 111;
|
|
source.VolumeRaw = 222;
|
|
source.Impedance = 333;
|
|
source.CheckSum = 44;
|
|
|
|
var target = new OptoTelegramRaw();
|
|
target.Copy(source);
|
|
|
|
Assert.AreEqual(source.iChannel, target.iChannel);
|
|
Assert.AreEqual(source.TimestampExt, target.TimestampExt);
|
|
Assert.AreEqual(source.VolumeRawExt, target.VolumeRawExt);
|
|
Assert.AreEqual(source.Flags, target.Flags);
|
|
Assert.AreEqual(source.Counter, target.Counter);
|
|
Assert.AreEqual(source.RefFlow, target.RefFlow);
|
|
Assert.AreEqual(source.FlowRaw, target.FlowRaw);
|
|
Assert.AreEqual(source.VolumeRaw, target.VolumeRaw);
|
|
Assert.AreEqual(source.Impedance, target.Impedance);
|
|
Assert.AreEqual(source.CheckSum, target.CheckSum);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RecalculateVolumeAndTimeDeltaPerChannel_Returns_Recalculated_Output()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(0, 20, 200),
|
|
|
|
Rec(1, 10, 500),
|
|
Rec(1, 30, 560),
|
|
|
|
Rec(2, 10, 1000),
|
|
Rec(2, 15, 1050),
|
|
};
|
|
|
|
var result = (OptoTelegramRaw[][])InvokePrivate(
|
|
"RecalculateVolumeAndTimeDeltaPerChannel",
|
|
(object)input,
|
|
input.Length,
|
|
0,
|
|
input.Length - 1);
|
|
|
|
Assert.IsNotNull(result);
|
|
Assert.AreEqual(3, result.Length);
|
|
|
|
for (int ch = 0; ch < 3; ch++)
|
|
{
|
|
Assert.IsNotNull(result[ch]);
|
|
Assert.AreEqual(2, result[ch].Length);
|
|
Assert.IsNotNull(result[ch][0]);
|
|
Assert.IsNotNull(result[ch][1]);
|
|
|
|
Assert.AreEqual(10, result[ch][0].TimestampExt, 0.0001);
|
|
Assert.AreEqual(30, result[ch][1].TimestampExt, 0.0001);
|
|
}
|
|
|
|
Assert.AreEqual(100, result[0][0].VolumeRawExt, 0.0001);
|
|
Assert.AreEqual(300, result[0][1].VolumeRawExt, 0.0001);
|
|
|
|
Assert.AreEqual(500, result[1][0].VolumeRawExt, 0.0001);
|
|
Assert.AreEqual(560, result[1][1].VolumeRawExt, 0.0001);
|
|
|
|
Assert.AreEqual(1000, result[2][0].VolumeRawExt, 0.0001);
|
|
Assert.AreEqual(1200, result[2][1].VolumeRawExt, 0.0001);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void RecalculateVolumeAndTimeDeltaPerChannel_Returns_Null_When_No_Channel_Has_Two_Records()
|
|
{
|
|
var input = new[]
|
|
{
|
|
Rec(0, 10, 100),
|
|
Rec(1, 20, 200),
|
|
Rec(2, 30, 300),
|
|
};
|
|
|
|
var result = (OptoTelegramRaw[][])InvokePrivate(
|
|
"RecalculateVolumeAndTimeDeltaPerChannel",
|
|
(object)input,
|
|
input.Length,
|
|
0,
|
|
input.Length - 1);
|
|
|
|
Assert.IsNull(result);
|
|
}
|
|
}
|
|
} |