Files
tbf/TBFTests/GenesisRecoveryTests.cs
michal 02de7c6c57 FIX GENESIS
Enhance Genesis configuration and validation mechanisms

- Introduce new test methods for testing Genesis properties, boundary validations, runtime behavior, and tooltips with culture-specific fallback.
- Expand configuration validation logic with enhanced error messaging and logging.
- Simplify Genesis property UI: remove legacy settings and refactor controls for robust behavior.
- Add detailed logging for runtime and configuration updates.
- Provide tooltips and localized support for Genesis properties in multiple languages (e.g., `de`, `sk`).
- Update project file to include new test files.
2026-09-10 21:33:55 +02:00

369 lines
21 KiB
C#

using System;
using System.Data.SQLite;
using System.IO;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Results.Entities;
using Results.Entities.helpers;
using GenesisCalibrationFactors = TBF.Rig.TestMethods.GenesisCommunication.GenesisCalibrationFactors;
namespace TBFTests
{
[TestClass]
public class GenesisRecoveryTests
{
[TestMethod]
public void TextFieldsPreserveConfiguredFactors()
{
double[] factors; string error;
Assert.IsTrue(GenesisCalibrationFactors.TryParse("17969", "17969", "17969", out factors, out error));
CollectionAssert.AreEqual(new double[] { 17969, 17969, 17969 }, factors);
Assert.IsTrue(GenesisCalibrationFactors.TryParse(" 17969 ", "18000", "19000", out factors, out error));
CollectionAssert.AreEqual(new double[] { 17969, 18000, 19000 }, factors);
}
[TestMethod]
public void IncompleteOrInvalidFactorsAreRejectedBeforeWriting()
{
foreach (var invalid in new[] { null, "", "0", "-1", "65536", "NaN", "1.5", "bad" })
{
double[] factors; string error;
Assert.IsFalse(GenesisCalibrationFactors.TryParse("17969", invalid, "17969", out factors, out error));
Assert.IsNull(factors);
StringAssert.Contains(error, "Text2");
}
}
[TestMethod]
public void EmptyLegacyConfigurationKeepsDefault()
{
double[] factors; string error;
Assert.IsTrue(GenesisCalibrationFactors.TryParse(null, " ", "", out factors, out error));
CollectionAssert.AreEqual(new double[] { 15625, 15625, 15625 }, factors);
}
[TestMethod]
public void MultipleMetersHaveIndependentStableChannelRecords()
{
var test = new TestRslt();
var first = new WaterMeter { WMPosition = 1 };
var second = new WaterMeter { WMPosition = 2 };
var a = test.GetCalibrationFactors(first);
var b = test.GetCalibrationFactors(second);
a[0].BaseCalibFactor = 17969;
b[0].BaseCalibFactor = 19000;
Assert.AreEqual(6, test.CalibFactorResultsToSave.Count);
Assert.AreSame(a[0], test.GetCalibrationFactors(first)[0]);
Assert.AreEqual(17969, a[0].BaseCalibFactor);
Assert.AreEqual(19000, b[0].BaseCalibFactor);
for (int i = 0; i < 3; i++) Assert.AreEqual(i + 1, b[i].CalibFactorIndex);
}
[TestMethod]
public void GenesisFactoryAndProcedureActivitiesAreAvailable()
{
var factory = TBF.Rig.TbfComponents.CmpntFactoryFromClassName("TestMethods.GenesisCommunication");
Assert.IsNotNull(factory);
var config = factory.DefaultConfig() as TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg;
Assert.IsNotNull(config);
Assert.AreEqual(10, config.NrThreads);
var parameters = new TBF.Rig.TestMethods.GenesisCommunication.iPerlCommunicationParams(true);
CollectionAssert.Contains(new System.Collections.Generic.List<string>(parameters.ParamValues(0)), "Prepare Q3 Calibration Slot");
Assert.IsNotNull(TBF.Rig.TbfComponents.CmpntFactoryFromClassName("TestMethods.iPerlCommunication"));
}
[TestMethod]
public void MappedCalibrationRecordsRoundTripForTwoMeters()
{
string path = Path.Combine(Path.GetTempPath(), "genesis-roundtrip-" + Guid.NewGuid() + ".sqlite");
var previousType = Results.DB.DbType;
var previousConnection = Results.DB.ConnectionString;
var previousFactory = Results.DB.SessionFactory;
try
{
Results.DB.DbType = Common.DBType.SQLite;
Results.DB.ConnectionString = path;
using (var factory = Results.DB.CreateSessionFactory(true))
{
int testId;
using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
var test = new TestRslt();
session.Save(test);
testId = test.Id;
for (int meter = 1; meter <= 2; meter++)
foreach (var factor in test.GetCalibrationFactors(new WaterMeter { WMPosition = meter }))
{
factor.BaseCalibFactor = 17969 + meter;
factor.CalculatedCalibFactor = 18000 + meter;
factor.Stored = true;
factor.IsCalibFactorValid = true;
session.Save(factor);
}
transaction.Commit();
}
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
using (var session = factory.OpenSession())
{
var rows = TestRsltCalibFactorHelper.GetByTestRsltId(session, testId);
Assert.AreEqual(6, rows.Count);
foreach (var row in rows)
{
Assert.AreEqual(17969 + row.WaterMeterPosition, row.BaseCalibFactor);
Assert.IsTrue(row.Stored);
Assert.IsTrue(row.IsCalibFactorValid);
}
}
}
}
finally
{
Results.DB.DbType = previousType;
Results.DB.ConnectionString = previousConnection;
Results.DB.SessionFactory = previousFactory;
SQLiteConnection.ClearAllPools();
if (File.Exists(path)) File.Delete(path);
}
}
// This test creates a temporary schema at runtime; no IDE data source exists.
// ReSharper disable SqlResolve
[TestMethod]
public void SQLiteEnsureCreatesAndUpgradesWithoutLosingExistingData()
{
string path = Path.Combine(Path.GetTempPath(), "genesis-migration-" + Guid.NewGuid() + ".sqlite");
try
{
using (var connection = new SQLiteConnection("Data Source=" + path))
{
connection.Open();
Execute(connection, "CREATE TABLE WaterMeterData(Id INTEGER PRIMARY KEY)");
Execute(connection, "CREATE TABLE WaterMeter(Id INTEGER PRIMARY KEY, CalibFactorNominal REAL NOT NULL DEFAULT 4096)");
Execute(connection, "CREATE TABLE MeterTestRslt(Id INTEGER PRIMARY KEY, FlipMode INT NULL)");
Execute(connection, "INSERT INTO WaterMeter(Id, CalibFactorNominal) VALUES(1, 17969)");
Execute(connection, "INSERT INTO MeterTestRslt(Id, FlipMode) VALUES(1, 7)");
// Simulate a partially migrated database from a previous special build.
Execute(connection, "CREATE TABLE TestRsltCalibFactor(Id INTEGER PRIMARY KEY, TestRsltId INT)");
Execute(connection, "INSERT INTO TestRsltCalibFactor(Id, TestRsltId) VALUES(1, 12)");
}
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
DatabaseMigrationHelper.EnsureSchema(Common.DBType.SQLite, path);
using (var connection = new SQLiteConnection("Data Source=" + path))
{
connection.Open();
Assert.AreEqual(17969d, Convert.ToDouble(Scalar(connection, "SELECT CalibFactorNominal FROM WaterMeter WHERE Id=1")));
Assert.AreEqual(7L, Convert.ToInt64(Scalar(connection, "SELECT FlipMode FROM MeterTestRslt WHERE Id=1")));
Assert.AreEqual(0L, Convert.ToInt64(Scalar(connection, "SELECT Q3Channel FROM WaterMeter WHERE Id=1")));
Assert.AreEqual(12L, Convert.ToInt64(Scalar(connection, "SELECT TestRsltId FROM TestRsltCalibFactor WHERE Id=1")));
Execute(connection, "UPDATE TestRsltCalibFactor SET WaterMeterPosition=2, CalibFactorIndex=3, BaseCalibFactor=17969, IsCalibFactorValid=1, Stored=1 WHERE Id=1");
Assert.AreEqual(17969L, Convert.ToInt64(Scalar(connection, "SELECT BaseCalibFactor FROM TestRsltCalibFactor WHERE WaterMeterPosition=2 AND CalibFactorIndex=3")));
Assert.AreEqual(0L, Convert.ToInt64(Scalar(connection, "SELECT COUNT(*) FROM MeterTestCalibFactorRslt")));
}
}
finally { SQLiteConnection.ClearAllPools(); if (File.Exists(path)) File.Delete(path); }
}
// ReSharper restore SqlResolve
[TestMethod]
public void GenesisPropertiesLoadUpdateAndPreserveLegacySettings()
{
var cfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(new TBF.Rig.TestMethods.GenesisCommunication.Factory());
cfg.Name = "Genesis test";
cfg.DfltQ2c_15_rl = 17969;
cfg.UseWebService = true;
cfg.BaseUrl = "legacy-base";
cfg.RelativeUrl = "legacy-path";
// Exercise the existing XML contract before opening Properties.
using (var writer = new StringWriter())
{
TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg.Serializer.Serialize(writer, cfg);
using (var reader = new StringReader(writer.ToString()))
cfg = (TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg)TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg.Serializer.Deserialize(reader);
}
// Name is stored in the component entity, outside the XML payload.
cfg.Name = "Genesis test";
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
{
ctrl.Config = cfg;
Assert.AreSame(cfg, ctrl.Config);
Assert.AreEqual("Genesis test", ctrl.Controls.Find("nameTextBox", true)[0].Text);
Assert.AreEqual("1800", ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text);
string message = "";
Assert.AreEqual(Common.CfgUpdateFlags.None, ctrl.VerifyCfg(ref message), message);
ctrl.Unlock();
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2300";
bool notified = false;
EventHandler<TBF.Rig.CfgChangeArgs> handler = (sender, args) => { notified = true; };
TBF.Rig.TestMethods.GenesisCommunication.TestMethod.CfgChangeHandler += handler;
try { ctrl.UpdateCfg(); }
finally { TBF.Rig.TestMethods.GenesisCommunication.TestMethod.CfgChangeHandler -= handler; }
Assert.IsTrue(notified);
Assert.AreEqual(2300, cfg.CommTimeout);
Assert.AreEqual(17969, cfg.DfltQ2c_15_rl);
Assert.IsTrue(cfg.UseWebService);
Assert.AreEqual("legacy-base", cfg.BaseUrl);
Assert.AreEqual("legacy-path", cfg.RelativeUrl);
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "41";
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2400";
Assert.AreNotEqual(0, (int)(ctrl.UpdateCfg() & Common.CfgUpdateFlags.Error));
Assert.AreEqual(2300, cfg.CommTimeout);
}
}
[TestMethod]
public void GenesisPropertiesValidateAllNumericBoundariesAndExposeTooltips()
{
var cfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(new TBF.Rig.TestMethods.GenesisCommunication.Factory());
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
{
ctrl.Config = cfg;
ctrl.Unlock();
var tips = (System.Windows.Forms.ToolTip)typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl)
.GetField("settingsToolTip", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(ctrl);
var names = new[] { "commTimeoutTextBox", "maxCommRetriesTextBox", "delayBetweenRetriesTextBox", "nrThreadsTextBox", "iperlCheckErrorsToStopTextBox" };
var minima = new[] { 500, 1, 0, 1, 1 };
var maxima = new[] { 5000, 10, 5000, 10, 40 };
for (int i = 0; i < names.Length; i++)
{
var field = ctrl.Controls.Find(names[i], true)[0];
string original = field.Text;
Assert.IsFalse(string.IsNullOrWhiteSpace(tips.GetToolTip(field)));
Assert.IsTrue(tips.GetToolTip(field).Split('\n').Length >= 6);
foreach (var value in new[] { minima[i], maxima[i] })
{
field.Text = value.ToString();
string message = "";
Assert.AreEqual(Common.CfgUpdateFlags.None, ctrl.VerifyCfg(ref message), names[i] + message);
}
foreach (var value in new[] { (minima[i] - 1).ToString(), (maxima[i] + 1).ToString(), "bad", "", "1.5" })
{
field.Text = value;
string message = "";
Assert.AreNotEqual(0, (int)(ctrl.VerifyCfg(ref message) & Common.CfgUpdateFlags.Error), names[i] + ":" + value);
}
field.Text = original;
}
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2500";
ctrl.Controls.Find("maxCommRetriesTextBox", true)[0].Text = "6";
ctrl.Controls.Find("delayBetweenRetriesTextBox", true)[0].Text = "300";
ctrl.Controls.Find("nrThreadsTextBox", true)[0].Text = "3";
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "2";
var flags = ctrl.UpdateCfg();
Assert.AreNotEqual(0, (int)(flags & Common.CfgUpdateFlags.RestartRqrd));
Assert.AreEqual(2500, cfg.CommTimeout);
Assert.AreEqual(6, cfg.MaxCommRetries);
Assert.AreEqual(300, cfg.DelayBetweenRetries);
Assert.AreEqual(3, cfg.NrThreads);
Assert.AreEqual(2, cfg.IperlCheckErrorsToStop);
StringAssert.Contains(tips.GetToolTip(ctrl.Controls.Find("commTimeoutTextBox", true)[0]), "Genesis");
}
}
[TestMethod]
public void GenesisRuntimeReceivesLiveSettingsButKeepsRestartOnlyThreadCount()
{
var factory = new TBF.Rig.TestMethods.GenesisCommunication.Factory();
var runtimeCfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(factory);
var editedCfg = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfg(factory);
var component = new TBF.Rig.TestMethods.GenesisCommunication.TestMethod(runtimeCfg);
var eventField = typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethod).GetField("CfgChangeHandler",
System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);
var originalHandlers = eventField.GetValue(null);
try
{
component.StartChangeHandler();
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
{
ctrl.Config = editedCfg;
ctrl.Unlock();
ctrl.Controls.Find("commTimeoutTextBox", true)[0].Text = "2500";
ctrl.Controls.Find("maxCommRetriesTextBox", true)[0].Text = "6";
ctrl.Controls.Find("delayBetweenRetriesTextBox", true)[0].Text = "300";
ctrl.Controls.Find("iperlCheckErrorsToStopTextBox", true)[0].Text = "2";
ctrl.Controls.Find("nrThreadsTextBox", true)[0].Text = "3";
ctrl.UpdateCfg();
Assert.AreEqual(2500, runtimeCfg.CommTimeout);
Assert.AreEqual(6, runtimeCfg.MaxCommRetries);
Assert.AreEqual(300, runtimeCfg.DelayBetweenRetries);
Assert.AreEqual(2, runtimeCfg.IperlCheckErrorsToStop);
Assert.AreEqual(10, runtimeCfg.NrThreads);
Assert.AreEqual(3, editedCfg.NrThreads);
}
}
finally { eventField.SetValue(null, originalHandlers); }
}
[TestMethod]
public void GenesisTooltipsFollowUiCultureAndFallbackToEnglish()
{
var original = System.Threading.Thread.CurrentThread.CurrentUICulture;
try
{
var cultures = new[] { "en-US", "sk-SK", "cs-CZ", "de-DE", "fr-FR" };
var headings = new[] { "Default:", "Prednastaven\u00e1 hodnota:", "V\u00fdchoz\u00ed hodnota:", "Standardwert:", "Default:" };
for (int i = 0; i < cultures.Length; i++)
{
System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(cultures[i]);
using (var ctrl = new TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl())
{
var tips = (System.Windows.Forms.ToolTip)typeof(TBF.Rig.TestMethods.GenesisCommunication.TestMethodCfgCtrl)
.GetField("settingsToolTip", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(ctrl);
foreach (var field in new[] { "nameTextBox", "commTimeoutTextBox", "maxCommRetriesTextBox", "delayBetweenRetriesTextBox", "nrThreadsTextBox", "iperlCheckErrorsToStopTextBox" })
StringAssert.Contains(tips.GetToolTip(ctrl.Controls.Find(field, true)[0]), headings[i], cultures[i] + field);
var timeout = tips.GetToolTip(ctrl.Controls.Find("commTimeoutTextBox", true)[0]);
StringAssert.Contains(timeout, "500\u20135000 ms");
StringAssert.Contains(timeout, "1800 ms");
Assert.AreEqual(timeout, tips.GetToolTip(ctrl.Controls.Find("commTimeoutLabel", true)[0]));
}
}
}
finally { System.Threading.Thread.CurrentThread.CurrentUICulture = original; }
}
[TestMethod]
public async System.Threading.Tasks.Task GenesisTimeoutChangesCancellationDeadlineAndPreservesLegacyCallers()
{
Assert.IsNull(TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "legacy"));
using (TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.Begin(5000))
using (var longDeadline = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "long-test"))
{
using (TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.Begin(500))
using (var shortDeadline = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "short-test"))
{
try
{
await System.Threading.Tasks.Task.Delay(3000, shortDeadline.Token);
Assert.Fail("Expected the configured 500 ms deadline to cancel the operation.");
}
catch (System.OperationCanceledException) { Assert.IsTrue(shortDeadline.IsCancellationRequested); }
Assert.IsFalse(longDeadline.IsCancellationRequested);
}
using (var restored = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "restored-test"))
{
await System.Threading.Tasks.Task.Delay(700);
Assert.IsFalse(restored.IsCancellationRequested);
}
using (var caller = new System.Threading.CancellationTokenSource())
using (var linked = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(caller.Token, "caller-test"))
{
caller.Cancel();
Assert.IsTrue(linked.IsCancellationRequested);
}
}
Assert.IsNull(TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.CreateDeadline(System.Threading.CancellationToken.None, "legacy-after"));
}
private static void Execute(SQLiteConnection connection, string sql)
{
using (var command = connection.CreateCommand()) { command.CommandText = sql; command.ExecuteNonQuery(); }
}
private static object Scalar(SQLiteConnection connection, string sql)
{
using (var command = connection.CreateCommand()) { command.CommandText = sql; return command.ExecuteScalar(); }
}
}
}