- Expand Q3 calibration tests with diverse scenarios in `GenesisSmartReader_Q3Test`. - Introduce channel-specific validation for calibration tests. - Adjust methods for Q3 calibration in `GenesisSmartReader` to support per-channel factors. - Add utilities for preparing simulation data and refinements for test consistency. - Update `CliRunner` with improved task management, timeout handling, and logging enhancements.
427 lines
15 KiB
C#
427 lines
15 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 GenesisSmartReader_Q3Test
|
|
{
|
|
private static readonly double[] ValidInitFactors = { 15625.0, 15625.0, 15625.0 };
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldRemainInvalid_WhenRawDataIsNull()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
InitializeReaderForQ3Test(sut);
|
|
|
|
SetPrivateField(sut, "_rawStartEndByChannel", null);
|
|
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 1000.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: ValidInitFactors,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsFalse(sut.Q3CalibValid);
|
|
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
|
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldRemainInvalid_WhenNoSamplesIsTrue()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
InitializeReaderForQ3Test(sut);
|
|
|
|
SetPrivateField(sut, "_rawStartEndByChannel", CreateMinimalStartEndData());
|
|
|
|
Assert.IsTrue(sut.NoSamples, "Expected NoSamples to be true for this test.");
|
|
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 1000.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: ValidInitFactors,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsFalse(sut.Q3CalibValid);
|
|
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
|
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldRemainInvalid_WhenRefVolumeIsZero()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
Assert.IsFalse(sut.NoSamples);
|
|
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 0.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: ValidInitFactors,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsFalse(sut.Q3CalibValid);
|
|
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
|
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldRemainInvalid_WhenRefTimeIsZero()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
Assert.IsFalse(sut.NoSamples);
|
|
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 1000.0,
|
|
refTime: 0.0,
|
|
initCalibFactor: ValidInitFactors,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsFalse(sut.Q3CalibValid);
|
|
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
|
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldMarkInvalid_WhenInitFactorsAreZero()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
Assert.IsFalse(sut.NoSamples);
|
|
|
|
var init = new[] { 0.0, 0.0, 0.0 };
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 1000.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: init,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsFalse(sut.Q3CalibValid);
|
|
CollectionAssert.AreEqual(new[] { false, false, false }, valid);
|
|
CollectionAssert.AreEqual(new[] { 0.0, 0.0, 0.0 }, calib);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CalculateQ3Calibration_ShouldPopulatePerChannelValues_WithPreparedSimulationData()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
|
|
|
|
Assert.IsFalse(sut.NoSamples);
|
|
|
|
sut.CalculateQ3Calibration(200.0, 120.0);
|
|
|
|
Assert.IsNotNull(sut.Q3CalibValue);
|
|
Assert.AreEqual(3, sut.Q3CalibValue.Length);
|
|
|
|
Assert.IsTrue(sut.Q3CalibValue.All(v => !Double.IsNaN(v)));
|
|
Assert.IsTrue(sut.Q3CalibValue.All(v => v >= 0.0));
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldCalculateExpectedValues_ForKnownInput()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
var raw = CreateKnownStartEndData(
|
|
// start volume / end volume / start time / end time
|
|
(100.0, 150.0, 10.0, 20.0), // dV = 50, dT = 10
|
|
(105.0, 165.0, 10.0, 22.0), // dV = 60, dT = 12
|
|
(110.0, 180.0, 10.0, 25.0) // dV = 70, dT = 15
|
|
);
|
|
|
|
SetPrivateField(sut, "_rawStartEndByChannel", raw);
|
|
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw); // important for NoSamples == false
|
|
SetPrivateField(sut, "optoDataCount", 2);
|
|
|
|
sut.TestStartTelegramIx = 0;
|
|
sut.TestEndTelegramIx = 1;
|
|
|
|
Assert.IsFalse(sut.NoSamples, "Expected prepared data to produce valid samples.");
|
|
|
|
var init = new[] { 15625.0, 15625.0, 15625.0 };
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 200.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: init,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
double expectedCh1 = (200.0 / 600.0) * 15625.0;
|
|
double expectedCh2 = (200.0 / 600.0) * 15625.0;
|
|
double expectedCh3 = (200.0 / 560.0) * 15625.0;
|
|
|
|
Assert.AreEqual(expectedCh1, calib[0], 0.0001);
|
|
Assert.AreEqual(expectedCh2, calib[1], 0.0001);
|
|
Assert.AreEqual(expectedCh3, calib[2], 0.0001);
|
|
|
|
Assert.IsFalse(valid[0]);
|
|
Assert.IsFalse(valid[1]);
|
|
Assert.IsFalse(valid[2]);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void GetQ3Calibration_ShouldMarkChannelValid_WhenDifferenceIsWithinFivePercent()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
// We want recalculatedDeltaVolume == refVolume, so calculated factor == initial factor.
|
|
// refVolume = 200, refTime = 120
|
|
// choose dT = 10, dV = 16.6666666666667 => coef = 12 => recalculated dV = 200
|
|
|
|
var raw = CreateKnownStartEndData(
|
|
(100.0, 116.6666666666667, 10.0, 20.0),
|
|
(200.0, 216.6666666666667, 10.0, 20.0),
|
|
(300.0, 316.6666666666667, 10.0, 20.0)
|
|
);
|
|
|
|
SetPrivateField(sut, "_rawStartEndByChannel", raw);
|
|
SetPrivateField(sut, "_recalculatedStartEndByChannel", raw);
|
|
SetPrivateField(sut, "optoDataCount", 2);
|
|
sut.TestStartTelegramIx = 0;
|
|
sut.TestEndTelegramIx = 1;
|
|
|
|
Assert.IsFalse(sut.NoSamples, "Expected prepared data to produce valid samples.");
|
|
|
|
var init = new[] { 15625.0, 15625.0, 15625.0 };
|
|
var valid = new bool[3];
|
|
var calib = new double[3];
|
|
|
|
sut.GetQ3Calibration(
|
|
refVolume: 200.0,
|
|
refTime: 120.0,
|
|
initCalibFactor: init,
|
|
ref valid,
|
|
ref calib);
|
|
|
|
Assert.IsTrue(valid[0], $"Ch1 invalid, value={calib[0]}");
|
|
Assert.IsTrue(valid[1], $"Ch2 invalid, value={calib[1]}");
|
|
Assert.IsTrue(valid[2], $"Ch3 invalid, value={calib[2]}");
|
|
|
|
Assert.AreEqual(15625.0, calib[0], 0.001);
|
|
Assert.AreEqual(15625.0, calib[1], 0.001);
|
|
Assert.AreEqual(15625.0, calib[2], 0.001);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CalculateQ3Calibration_ShouldComputeExpectedChannelValues_FromSimulationData()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
|
|
|
|
sut.CalculateQ3Calibration(200.0, 120.0);
|
|
|
|
// initial values remain exposed through Q3CalibValue
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[0], 0.000001);
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[1], 0.000001);
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[2], 0.000001);
|
|
|
|
// calculated values
|
|
Assert.AreEqual(5208.33333333333, sut.Q3Calib_Ch1Value, 0.000001);
|
|
Assert.AreEqual(5008.01282051282, sut.Q3Calib_Ch2Value, 0.000001);
|
|
Assert.AreEqual(4822.53086419753, sut.Q3Calib_Ch3Value, 0.000001);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void CalculateQ3Calibration_ShouldKeepInitialArray_AndUpdateCalculatedChannelProperties()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
|
|
InvokePrivateByName(sut, "SetQ3Calibration", new object[] { ValidInitFactors });
|
|
|
|
sut.CalculateQ3Calibration(200.0, 120.0);
|
|
|
|
// Q3CalibValue currently exposes INITIAL calibration values, not calculated ones
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[0], 0.000001);
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[1], 0.000001);
|
|
Assert.AreEqual(15625.0, sut.Q3CalibValue[2], 0.000001);
|
|
|
|
// Per-channel public properties expose calculated values
|
|
Assert.IsTrue(sut.Q3Calib_Ch1Value > 0.0);
|
|
Assert.IsTrue(sut.Q3Calib_Ch2Value > 0.0);
|
|
Assert.IsTrue(sut.Q3Calib_Ch3Value > 0.0);
|
|
|
|
Assert.AreNotEqual(sut.Q3CalibValue[0], sut.Q3Calib_Ch1Value, 0.000001);
|
|
Assert.AreNotEqual(sut.Q3CalibValue[1], sut.Q3Calib_Ch2Value, 0.000001);
|
|
Assert.AreNotEqual(sut.Q3CalibValue[2], sut.Q3Calib_Ch3Value, 0.000001);
|
|
}
|
|
|
|
[TestMethod]
|
|
public void PrepareCalculatedChannelDataSimulationForTest_ShouldPrepareUsableData()
|
|
{
|
|
var sut = new GenesisSmartReader();
|
|
|
|
InvokePrivateByName(sut, "PrepareCalculatedChannelDataSimulationForTest");
|
|
|
|
Assert.IsFalse(sut.NoSamples);
|
|
Assert.IsTrue(sut.VolumeLtrStart >= 0.0);
|
|
Assert.IsTrue(sut.VolumeLtrEnd >= 0.0);
|
|
Assert.IsTrue(sut.TimestampSecEnd >= sut.TimestampSecStart);
|
|
}
|
|
|
|
private static void InitializeReaderForQ3Test(GenesisSmartReader reader)
|
|
{
|
|
SetPrivateField(reader, "_rawStartEndByChannel", null);
|
|
SetPrivateField(reader, "_recalculatedStartEndByChannel", null);
|
|
SetPrivateField(reader, "optoDataCount", 0);
|
|
|
|
reader.TestStartTelegramIx = -1;
|
|
reader.TestEndTelegramIx = -1;
|
|
}
|
|
|
|
private static OptoTelegramRaw[][] CreateMinimalStartEndData()
|
|
{
|
|
return new[]
|
|
{
|
|
new[]
|
|
{
|
|
CreateRecord(0.0, 0.0, 1),
|
|
CreateRecord(15625.0, 120.0, 1)
|
|
},
|
|
new[]
|
|
{
|
|
CreateRecord(0.0, 0.0, 2),
|
|
CreateRecord(15625.0, 120.0, 2)
|
|
},
|
|
new[]
|
|
{
|
|
CreateRecord(0.0, 0.0, 3),
|
|
CreateRecord(15625.0, 120.0, 3)
|
|
}
|
|
};
|
|
}
|
|
|
|
private static OptoTelegramRaw[][] CreateKnownStartEndData(
|
|
(double startVolume, double endVolume, double startTime, double endTime) ch1,
|
|
(double startVolume, double endVolume, double startTime, double endTime) ch2,
|
|
(double startVolume, double endVolume, double startTime, double endTime) ch3)
|
|
{
|
|
return new[]
|
|
{
|
|
new[]
|
|
{
|
|
CreateRecord(ch1.startVolume, ch1.startTime, 1),
|
|
CreateRecord(ch1.endVolume, ch1.endTime, 1)
|
|
},
|
|
new[]
|
|
{
|
|
CreateRecord(ch2.startVolume, ch2.startTime, 2),
|
|
CreateRecord(ch2.endVolume, ch2.endTime, 2)
|
|
},
|
|
new[]
|
|
{
|
|
CreateRecord(ch3.startVolume, ch3.startTime, 3),
|
|
CreateRecord(ch3.endVolume, ch3.endTime, 3)
|
|
}
|
|
};
|
|
}
|
|
|
|
private static OptoTelegramRaw CreateRecord(double volumeRawExt, double timestampExt, int channel)
|
|
{
|
|
return new OptoTelegramRaw
|
|
{
|
|
VolumeRawExt = volumeRawExt,
|
|
TimestampExt = timestampExt,
|
|
iChannel = channel,
|
|
Flags = OptoTelegramFlags.OK
|
|
};
|
|
}
|
|
|
|
private static object InvokePrivateByName(object target, string methodName, params object[] args)
|
|
{
|
|
Type type = target.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var candidates = type.GetMethods(
|
|
BindingFlags.Instance |
|
|
BindingFlags.Static |
|
|
BindingFlags.Public |
|
|
BindingFlags.NonPublic |
|
|
BindingFlags.DeclaredOnly)
|
|
.Where(m => m.Name == methodName)
|
|
.ToList();
|
|
|
|
foreach (var method in candidates)
|
|
{
|
|
var parameters = method.GetParameters();
|
|
|
|
if (parameters.Length != (args?.Length ?? 0))
|
|
continue;
|
|
|
|
try
|
|
{
|
|
return method.Invoke(target, args);
|
|
}
|
|
catch (ArgumentException)
|
|
{
|
|
}
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
Assert.Fail($"Method '{methodName}' not found or no matching overload accepted the provided arguments.");
|
|
return null;
|
|
}
|
|
|
|
private static void SetPrivateField(object target, string fieldName, object value)
|
|
{
|
|
Type type = target.GetType();
|
|
|
|
while (type != null)
|
|
{
|
|
var field = type.GetField(
|
|
fieldName,
|
|
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly);
|
|
|
|
if (field != null)
|
|
{
|
|
field.SetValue(target, value);
|
|
return;
|
|
}
|
|
|
|
type = type.BaseType;
|
|
}
|
|
|
|
Assert.Fail($"Field '{fieldName}' not found.");
|
|
}
|
|
}
|
|
} |