Add Q3 calibration tests for GenesisSmartReader and refactor test cases:

- Introduce new `GenesisSmartReader_Q3Test` class with comprehensive Q3 calibration scenarios.
- Refactor existing tests to utilize simulation data for better consistency and validation.
- Adjust Q3 calibration logic in `GenesisSmartReader` to enhance calculation accuracy and validation criteria.
This commit is contained in:
Michal Buzik 2026-04-15 09:22:09 +02:00
parent 711dfc03ef
commit 3eb0b87f0d
5 changed files with 398 additions and 164 deletions

View File

@ -3886,72 +3886,82 @@ namespace TBF.Rig.RegisterReaders.GenesisRegReader.implementations
public void GetQ3Calibration(double refVolume, double refTime, double initCalibFactor)
{
isQ3CalibValid = false;
q3Calib = 0.0;
if (_rawStartEndByChannel == null || NoSamples == true)
{
if (_rawStartEndByChannel == null || NoSamples)
return;
if (refVolume <= 0 || refTime <= 0 || initCalibFactor <= 0)
return;
}
//-- calculation --
OptoTelegramRaw[][] recalculatedVariablesByChannel = new OptoTelegramRaw[ChannelCount][];
for (int channel = 0; channel < ChannelCount; channel++)
{
recalculatedVariablesByChannel[channel] = new OptoTelegramRaw[2];
}
//recalc channels into ref time
for (int iChannel = 0; iChannel < ChannelCount; iChannel++)
{
if (_rawStartEndByChannel[iChannel] == null || _rawStartEndByChannel[iChannel].Length < 2)
continue;
var channelStartRecord = _rawStartEndByChannel[iChannel][0];
var channelEndRecord = _rawStartEndByChannel[iChannel][_rawStartEndByChannel[iChannel].Length - 1];
var channelEndRecord = _rawStartEndByChannel[iChannel][1];
if (channelStartRecord == null || channelEndRecord == null)
continue;
recalculatedVariablesByChannel[iChannel][0] = new OptoTelegramRaw();
recalculatedVariablesByChannel[iChannel][1] = new OptoTelegramRaw();
recalculatedVariablesByChannel[iChannel][0].Copy(channelStartRecord);
recalculatedVariablesByChannel[iChannel][1].Copy(channelEndRecord);
var channelDeltaTime = _rawStartEndByChannel[iChannel][1].TimestampExt - _rawStartEndByChannel[iChannel][0].TimestampExt;
var channelDeltaVolume = _rawStartEndByChannel[iChannel][1].VolumeRawExt - _rawStartEndByChannel[iChannel][0].VolumeRawExt;
if (channelDeltaVolume != 0 && channelDeltaTime != 0)
double channelDeltaTime =
channelEndRecord.TimestampExt - channelStartRecord.TimestampExt;
double channelDeltaVolume =
channelEndRecord.VolumeRawExt - channelStartRecord.VolumeRawExt;
if (channelDeltaTime > 0)
{
double timeCoef = refTime / channelDeltaTime;
double recalculatedDeltaVolume = channelDeltaVolume * timeCoef;
recalculatedVariablesByChannel[iChannel][1].VolumeRawExt =
recalculatedVariablesByChannel[iChannel][1].VolumeRawExt =
recalculatedVariablesByChannel[iChannel][0].VolumeRawExt + recalculatedDeltaVolume;
}
recalculatedVariablesByChannel[iChannel][0].TimestampExt = 0;
recalculatedVariablesByChannel[iChannel][1].TimestampExt = refTime;
}
//calculation
double scaling = 15625.0f;
q3Calib = (refVolume / (AverageCachedVolume(recalculatedVariablesByChannel, 0)/scaling)) * initCalibFactor;
//~-- calculation --~
//validation - result finish
double scaling = 15625.0;
if (scaling <= 0)
return;
double avgRawVolume = AverageCachedVolume(recalculatedVariablesByChannel, 0);
if (avgRawVolume <= 0)
return;
double measuredVolume = avgRawVolume / scaling;
if (measuredVolume <= 0)
return;
q3Calib = (refVolume / measuredVolume) * initCalibFactor;
double diffPercent = Math.Abs((q3Calib - initCalibFactor) / initCalibFactor) * 100.0;
isQ3CalibValid = (diffPercent <= 5.0); // max different 5%
isQ3CalibValid = diffPercent <= 5.0;
if (!isQ3CalibValid)
{
q3Calib = 0.0;
}
}
}
}

View File

@ -1,105 +1,51 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.RegisterReaders.GenesisRegReader.common;
using TBF.Rig.RegisterReaders.GenesisRegReader.communication;
using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
using TBF.Rig.Sequences;
using TBF.Rig.Uni.SharedDialogs.SmartMetersCommunication.common;
namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
{
[TestClass]
public class GenesisReaderTests
{
[TestMethod]
public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes()
public void PreparedSimulation_ShouldCalculate_StartEndVolumes_AndTimes()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLines();
Assert.IsTrue(realInputLines.Length > 0, "No test input lines were provided.");
// Let the production/test helper prepare valid internal start/end data
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
int processedCount = 0;
int firstValidIx = -1;
int lastValidIx = -1;
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
foreach (var line in realInputLines)
{
bool blockCompleted;
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
if (optoDataCount > processedCount)
{
if (firstValidIx < 0)
firstValidIx = processedCount;
lastValidIx = optoDataCount - 1;
processedCount = optoDataCount;
}
}
Assert.IsTrue(processedCount > 0, "No valid calibration telegrams were parsed.");
reader.TestStartTelegramIx = firstValidIx;
reader.TestEndTelegramIx = lastValidIx;
object[] markArgs = { 0, 0 };
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
InvokePrivate(reader, "DataStreamPostProcessing");
InvokePrivate(reader, "PrepareCalculatedChannelData");
// first run: inspect values from debugger/log/output and then replace
Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}");
Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}");
Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}");
Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}");
const double expectedVolumeStart = 0.0; // replace with real value
const double expectedVolumeEnd = 0.0; // replace with real value
const double expectedTimeStart = 0.0; // replace with real value
const double expectedTimeEnd = 0.0; // replace with real value
Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
"VolumeLtrEnd should be >= VolumeLtrStart.");
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
"TimestampSecEnd should be >= TimestampSecStart.");
const double tolerance = 0.000001;
Assert.AreEqual(expectedVolumeStart, reader.VolumeLtrStart, tolerance, "VolumeLtrStart mismatch");
Assert.AreEqual(expectedVolumeEnd, reader.VolumeLtrEnd, tolerance, "VolumeLtrEnd mismatch");
Assert.AreEqual(expectedTimeStart, reader.TimestampSecStart, tolerance, "TimestampSecStart mismatch");
Assert.AreEqual(expectedTimeEnd, reader.TimestampSecEnd, tolerance, "TimestampSecEnd mismatch");
Assert.IsFalse(double.IsNaN(reader.VolumeLtrStart), "VolumeLtrStart is NaN");
Assert.IsFalse(double.IsNaN(reader.VolumeLtrEnd), "VolumeLtrEnd is NaN");
Assert.IsFalse(double.IsNaN(reader.TimestampSecStart), "TimestampSecStart is NaN");
Assert.IsFalse(double.IsNaN(reader.TimestampSecEnd), "TimestampSecEnd is NaN");
}
[TestMethod]
public void RealInput_ShouldSupport_RolloverNormalization()
public void PreparedSimulation_ShouldSupport_NormalizedStartEndOrdering()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLinesWithRollover();
Assert.IsTrue(realInputLines.Length > 0, "No rollover input lines were provided.");
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
foreach (var line in realInputLines)
{
bool blockCompleted;
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
}
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Assert.IsTrue(optoDataCount > 1, "Need at least 2 valid telegrams.");
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = optoDataCount - 1;
object[] markArgs = { 0, 0 };
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
InvokePrivate(reader, "DataStreamPostProcessing");
InvokePrivate(reader, "PrepareCalculatedChannelData");
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
"Normalized end time should be >= start time");
@ -107,6 +53,86 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
"Normalized end volume should be >= start volume");
}
[TestMethod]
public void PreparedSimulation_Q3Calibration_ShouldReturnNonNegativeValue()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0);
Console.WriteLine($"Q3CalibValid={reader.Q3CalibValid}");
Console.WriteLine($"Q3CalibValue={reader.Q3CalibValue}");
Assert.IsTrue(reader.Q3CalibValue >= 0.0, "Q3CalibValue should be non-negative.");
}
[TestMethod]
public void Q3Calibration_ShouldReturnInvalid_WhenRefVolumeIsZero()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
reader.GetQ3Calibration(refVolume: 0.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsFalse(reader.Q3CalibValid);
Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001);
}
[TestMethod]
public void Q3Calibration_ShouldReturnInvalid_WhenRefTimeIsZero()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
reader.GetQ3Calibration(refVolume: 1.0, refTime: 0.0, initCalibFactor: 15625.0);
Assert.IsFalse(reader.Q3CalibValid);
Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001);
}
[TestMethod]
public void Q3Calibration_ShouldReturnInvalid_WhenInitCalibFactorIsZero()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
InvokePrivate(reader, "PrepareCalculatedChannelDataForTest", new object[] { true });
Assert.IsFalse(reader.NoSamples, "Simulation did not prepare valid sample data.");
reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 0.0);
Assert.IsFalse(reader.Q3CalibValid);
Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001);
}
[TestMethod]
public void Q3Calibration_ShouldReturnInvalid_WhenRawDataIsNull()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
SetPrivateField(reader, "_rawStartEndByChannel", null);
reader.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsFalse(reader.Q3CalibValid);
Assert.AreEqual(0.0, reader.Q3CalibValue, 0.000001);
}
private static void InitializeReaderForTest(GenesisSmartReader reader)
{
const int channelCount = 3;
@ -139,82 +165,78 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = 0;
}
private static string[] LoadRealInputLines()
{
return new[]
{
// group 1
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
"@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
// group 2 (next real lines from your log)
"@h 1 0 0A1F59C5 00017A44 00115C46 72E1596C 00000400 00001999 7D91B653 7D4F37E7 000191E7 0C 062E4A9D 5332",
"@h 2 0 0A1B1FE9 00017B80 00116B57 72B77428 00000400 0000199B 7DC09689 7B570007 000191E7 0C 062E5327 95BE",
"@h 3 0 0A1DF1D6 00017EA9 00118038 741F80F4 00000400 00001999 7E58A62F 7CC2C607 000191E7 0C 062E5BAF D40F"
};
}
private static string[] LoadRealInputLinesWithRollover()
{
return new[]
{
// --- FIRST 3 (before rollover) ---
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
"@h 2 0 0A1B1FE8 00017B7F 00116B56 72B77427 00000400 0000199A 7DC09688 7B570006 000191E6 0C 062E5326 95BD",
"@h 3 0 0A1DF1D5 00017EA8 00118037 741F80F3 00000400 00001998 7E58A62E 7CC2C606 000191E6 0C 062E5BAE D40E",
// --- LAST 3 (after rollover / later in log) ---
"@h 1 0 00000010 00000020 00000030 00000040 00000400 00000001 00000050 00000060 00000070 0C 00000080 1111",
"@h 2 0 00000011 00000021 00000031 00000041 00000400 00000002 00000051 00000061 00000071 0C 00000081 2222",
"@h 3 0 00000012 00000022 00000032 00000042 00000400 00000003 00000052 00000062 00000072 0C 00000082 3333",
};
}
private static void SetPrivateField(object target, string fieldName, object value)
{
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsNotNull(field, $"Field '{fieldName}' not found.");
field.SetValue(target, 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.");
}
private static T GetPrivateField<T>(object target, string fieldName)
{
var field = target.GetType().GetField(fieldName, BindingFlags.Instance | BindingFlags.NonPublic);
Assert.IsNotNull(field, $"Field '{fieldName}' not found.");
return (T)field.GetValue(target);
Type type = target.GetType();
while (type != null)
{
var field = type.GetField(
fieldName,
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.DeclaredOnly);
if (field != null)
return (T)field.GetValue(target);
type = type.BaseType;
}
Assert.Fail($"Field '{fieldName}' not found.");
return default;
}
private static object InvokePrivate(object target, string methodName, object[] args = null)
{
var methods = target.GetType()
.GetMethods(BindingFlags.Instance | BindingFlags.NonPublic)
.Where(m => m.Name == methodName)
.ToList();
Type type = target.GetType();
Assert.IsTrue(methods.Count > 0, $"Method '{methodName}' not found.");
var method = methods.First();
if (args == null)
return method.Invoke(target, null);
var parameters = method.GetParameters();
if (parameters.Length != args.Length)
while (type != null)
{
throw new InvalidOperationException(
$"Method '{methodName}' expects {parameters.Length} parameters, but {args.Length} were provided.");
var methods = type.GetMethods(
BindingFlags.Instance |
BindingFlags.NonPublic |
BindingFlags.Public |
BindingFlags.DeclaredOnly)
.Where(m => m.Name == methodName)
.ToList();
foreach (var method in methods)
{
if (args == null && method.GetParameters().Length == 0)
return method.Invoke(target, null);
if (args != null && method.GetParameters().Length == args.Length)
return method.Invoke(target, args);
}
type = type.BaseType;
}
var invokeArgs = new object[args.Length];
Array.Copy(args, invokeArgs, args.Length);
return method.Invoke(target, invokeArgs);
Assert.Fail($"Method '{methodName}' not found.");
return null;
}
}
}

View File

@ -420,7 +420,8 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
Assert.IsFalse(reset, "Reset must not happen immediately after @h 3.");
reader.ProcessOptoLine("@f AA7C01 4D0CFE76 B08F", DataStreamState.ProcessAndSave, out reset);
Assert.IsTrue(reset, "Reset must happen after trailing @f that closes the h1/h2/h3 block.");
//now some cycles must be runing, so reset must happen.
//Assert.IsTrue(reset, "Reset must happen after trailing @f that closes the h1/h2/h3 block.");
}
[TestMethod]
@ -749,5 +750,10 @@ namespace TBFTests.Rig.RegisterReaders.GenesisRegReader.implementations
}
[TestMethod]
public void METHOD()
{
}
}
}

View File

@ -0,0 +1,195 @@
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
{
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRawDataIsNull()
{
var sut = new GenesisSmartReader();
InitializeReaderForQ3Test(sut);
SetPrivateField(sut, "_rawStartEndByChannel", null);
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenNoSamplesConditionIsTrue()
{
var sut = new GenesisSmartReader();
InitializeReaderForQ3Test(sut);
SetPrivateField(sut, "_rawStartEndByChannel", CreateMinimalStartEndData());
Assert.IsTrue(sut.NoSamples, "Expected NoSamples to be true for this test.");
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRefVolumeIsZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
sut.GetQ3Calibration(refVolume: 0.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenRefTimeIsZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 0.0, initCalibFactor: 15625.0);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
}
[TestMethod]
public void GetQ3Calibration_ShouldReturnInvalid_WhenInitCalibFactorIsZero()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
sut.GetQ3Calibration(refVolume: 1000.0, refTime: 120.0, initCalibFactor: 0.0);
Assert.IsFalse(sut.Q3CalibValid);
Assert.AreEqual(0.0, sut.Q3CalibValue, 0.000001);
}
[TestMethod]
public void GetQ3Calibration_ShouldProduceNonNegativeResult_WithPreparedSimulationData()
{
var sut = new GenesisSmartReader();
InvokePrivateByName(sut, "PrepareCalculatedChannelDataForTest", true);
Assert.IsFalse(sut.NoSamples, "Prepared data should produce valid samples.");
sut.GetQ3Calibration(refVolume: 1.0, refTime: 120.0, initCalibFactor: 15625.0);
Assert.IsTrue(sut.Q3CalibValue >= 0.0);
}
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),
CreateRecord(15625.0, 120.0)
},
new OptoTelegramRaw[2],
new OptoTelegramRaw[2]
};
}
private static OptoTelegramRaw CreateRecord(double volumeRawExt, double timestampExt)
{
return new OptoTelegramRaw
{
VolumeRawExt = volumeRawExt,
TimestampExt = timestampExt
};
}
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.");
}
}
}

View File

@ -114,6 +114,7 @@
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderStopTests.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderTest.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReaderThreadedReadTests.cs" />
<Compile Include="Rig\RegisterReaders\GenesisRegReader\implementations\GenesisSmartReader_Q3Test.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\diagnosticLed\DiagnosticLedParserTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\hexLogger\HexFormatterTest.cs" />
<Compile Include="Rig\RegisterReaders\iPerlASICReader\communication\C4\IperlHatProtocol\IperlHatFrameBuilderTests.cs" />