tbf/TBFTests/Rig/RegisterReaders/GenesisRegReader/implementations/GenesisReaderTests.cs

365 lines
16 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.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 Debug_ProcessOptoLine_FormatVariants()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
string[] variants =
{
"@h 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
"@he 1 0 0A1F59C4 00017A43 00115C45 72E1596B 00000400 00001998 7D91B652 7D4F37E6 000191E6 0C 062E4A9C 5331",
"@he\t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331",
"@he \t1\t0\t0A1F59C4\t00017A43\t00115C45\t72E1596B\t00000400\t00001998\t7D91B652\t7D4F37E6\t000191E6\t0C\t062E4A9C\t5331"
};
for (int i = 0; i < variants.Length; i++)
{
bool blockCompleted = false;
reader.ProcessOptoLine(variants[i], DataStreamState.ProcessAndSave, out blockCompleted);
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Console.WriteLine($"Variant {i}: {variants[i]}");
Console.WriteLine($" blockCompleted={blockCompleted}");
Console.WriteLine($" optoDataCount={optoDataCount}");
Console.WriteLine("--------------------------------");
}
Assert.Fail("Inspect which variant, if any, is accepted.");
}
[TestMethod]
public void RealInput_ShouldCalculate_StartEndVolumes_AndTimes()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLines();
Assert.IsTrue(realInputLines.Length > 0, "No test input lines were provided.");
int processedCount = 0;
int firstValidIx = -1;
int lastValidIx = -1;
Console.WriteLine("=== BEGIN INPUT PARSING ===");
for (int i = 0; i < realInputLines.Length; i++)
{
string line = realInputLines[i];
bool blockCompleted = false;
Exception parseException = null;
try
{
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
}
catch (Exception ex)
{
parseException = ex;
}
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Console.WriteLine($"[{i}] line = {line}");
Console.WriteLine($"[{i}] blockCompleted = {blockCompleted}");
Console.WriteLine($"[{i}] optoDataCount = {optoDataCount}");
if (parseException != null)
{
Console.WriteLine($"[{i}] EXCEPTION = {parseException.GetType().Name}: {parseException.Message}");
}
if (optoDataCount > processedCount)
{
if (firstValidIx < 0)
firstValidIx = processedCount;
lastValidIx = optoDataCount - 1;
processedCount = optoDataCount;
Console.WriteLine(
$"[{i}] ACCEPTED -> processedCount={processedCount}, firstValidIx={firstValidIx}, lastValidIx={lastValidIx}");
}
else
{
Console.WriteLine($"[{i}] NOT ACCEPTED");
}
Console.WriteLine("--------------------------------------------------");
}
Console.WriteLine("=== END INPUT PARSING ===");
Console.WriteLine($"Final processedCount = {processedCount}");
Console.WriteLine($"Final firstValidIx = {firstValidIx}");
Console.WriteLine($"Final lastValidIx = {lastValidIx}");
if (processedCount <= 0)
{
Assert.Fail(
"No valid calibration telegrams were parsed.\n" +
"Check the following:\n" +
"1. exact telegram prefix (for example @h vs @he)\n" +
"2. exact separators (spaces vs tabs)\n" +
"3. exact CRC / checksum\n" +
"4. whether ProcessOptoLine expects a complete multi-line block format\n" +
"See test output for per-line diagnostics.");
}
reader.TestStartTelegramIx = firstValidIx;
reader.TestEndTelegramIx = lastValidIx;
Console.WriteLine("=== BEFORE POST PROCESSING ===");
Console.WriteLine($"TestStartTelegramIx = {reader.TestStartTelegramIx}");
Console.WriteLine($"TestEndTelegramIx = {reader.TestEndTelegramIx}");
object[] markArgs = { 0, 0 };
try
{
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
Console.WriteLine("AddTestStartEndMarksToData OK");
InvokePrivate(reader, "DataStreamPostProcessing");
Console.WriteLine("DataStreamPostProcessing OK");
InvokePrivate(reader, "PrepareCalculatedChannelData");
Console.WriteLine("PrepareCalculatedChannelData OK");
}
catch (Exception ex)
{
Assert.Fail(
"Post-processing failed.\n" +
$"Exception: {ex.GetType().Name}: {ex.Message}\n" +
$"StackTrace:\n{ex.StackTrace}");
}
Console.WriteLine("=== CALCULATED VALUES ===");
Console.WriteLine($"NoSamples = {reader.NoSamples}");
Console.WriteLine($"VolumeLtrStart = {reader.VolumeLtrStart}");
Console.WriteLine($"VolumeLtrEnd = {reader.VolumeLtrEnd}");
Console.WriteLine($"TimestampSecStart = {reader.TimestampSecStart}");
Console.WriteLine($"TimestampSecEnd = {reader.TimestampSecEnd}");
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");
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
$"Timestamp ordering invalid: start={reader.TimestampSecStart}, end={reader.TimestampSecEnd}");
Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
$"Volume ordering invalid: start={reader.VolumeLtrStart}, end={reader.VolumeLtrEnd}");
// When parser input is finally correct, replace these expected values:
const double expectedVolumeStart = 0.0;
const double expectedVolumeEnd = 0.0;
const double expectedTimeStart = 0.0;
const double expectedTimeEnd = 0.0;
const double tolerance = 0.000001;
Console.WriteLine("=== EXPECTED VS ACTUAL ===");
Console.WriteLine($"expectedVolumeStart = {expectedVolumeStart}, actual = {reader.VolumeLtrStart}");
Console.WriteLine($"expectedVolumeEnd = {expectedVolumeEnd}, actual = {reader.VolumeLtrEnd}");
Console.WriteLine($"expectedTimeStart = {expectedTimeStart}, actual = {reader.TimestampSecStart}");
Console.WriteLine($"expectedTimeEnd = {expectedTimeEnd}, actual = {reader.TimestampSecEnd}");
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");
}
[TestMethod]
public void RealInput_ShouldSupport_RolloverNormalization()
{
var reader = new GenesisSmartReader();
InitializeReaderForTest(reader);
string[] realInputLines = LoadRealInputLinesWithRollover();
Assert.IsTrue(realInputLines.Length > 0, "No rollover input lines were provided.");
for (int i = 0; i < realInputLines.Length; i++)
{
string line = realInputLines[i];
bool blockCompleted;
reader.ProcessOptoLine(line, DataStreamState.ProcessAndSave, out blockCompleted);
int optoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Console.WriteLine($"[{i}] line={line}");
Console.WriteLine($"[{i}] optoDataCount={optoDataCount}, blockCompleted={blockCompleted}");
}
int finalOptoDataCount = GetPrivateField<int>(reader, "optoDataCount");
Assert.IsTrue(
finalOptoDataCount > 1,
"Need at least 2 valid telegrams. Check exact telegram format / CRC in LoadRealInputLinesWithRollover().");
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = finalOptoDataCount - 1;
object[] markArgs = { 0, 0 };
InvokePrivate(reader, "AddTestStartEndMarksToData", markArgs);
InvokePrivate(reader, "DataStreamPostProcessing");
InvokePrivate(reader, "PrepareCalculatedChannelData");
Console.WriteLine($"VolumeLtrStart={reader.VolumeLtrStart}");
Console.WriteLine($"VolumeLtrEnd={reader.VolumeLtrEnd}");
Console.WriteLine($"TimestampSecStart={reader.TimestampSecStart}");
Console.WriteLine($"TimestampSecEnd={reader.TimestampSecEnd}");
Assert.IsTrue(reader.TimestampSecEnd >= reader.TimestampSecStart,
"Normalized end time should be >= start time");
Assert.IsTrue(reader.VolumeLtrEnd >= reader.VolumeLtrStart,
"Normalized end volume should be >= start volume");
}
private static void InitializeReaderForTest(GenesisSmartReader reader)
{
const int channelCount = 3;
SetPrivateField(reader, "volumeRawExtLast", new double[channelCount]);
SetPrivateField(reader, "timestampExtLast", new double[channelCount]);
SetPrivateField(reader, "lastTimestamp", new double[channelCount]);
SetPrivateField(reader, "timestampSec", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "timestampSec0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "lastVolumeRaw", new double[channelCount]);
SetPrivateField(reader, "volumeLtr", Enumerable.Repeat(double.NaN, channelCount).ToArray());
SetPrivateField(reader, "volumeLtr0", Enumerable.Repeat(double.NaN, channelCount).ToArray());
var optoData = new OptoTelegramRaw[GenesisSmartReader.OptoDataBufferSize];
for (int i = 0; i < optoData.Length; i++)
optoData[i] = new OptoTelegramRaw();
SetPrivateField(reader, "optoData", optoData);
SetPrivateField(reader, "optoDataCount", 0);
SetPrivateField(reader, "toBeFlushed", new OptoTelegramRaw());
SetPrivateField(reader, "flowDirectionDetection", new FlowDirectionDetection());
SetPrivateField(reader, "dataStreamState", DataStreamState.ProcessAndSave);
SetPrivateField(reader, "synchronized", false);
SetPrivateField(reader, "synchronized2", false);
SetPrivateField(reader, "partOfTelegram", string.Empty);
SetPrivateField(reader, "startDataProcessing", true);
reader.TestStartTelegramIx = 0;
reader.TestEndTelegramIx = 0;
}
private static string[] LoadRealInputLines()
{
return new[]
{
// Replace these with REAL, unchanged captured telegram lines from the device.
"@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",
};
}
private static string[] LoadRealInputLinesWithRollover()
{
return new[]
{
// Replace these with REAL, unchanged captured telegram lines spanning the rollover case.
"@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",
};
}
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.");
}
private static T GetPrivateField<T>(object target, string fieldName)
{
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)
{
Type type = target.GetType();
while (type != null)
{
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;
}
Assert.Fail($"Method '{methodName}' not found.");
return null;
}
}
}