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.
369 lines
21 KiB
C#
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(); }
|
|
}
|
|
}
|
|
}
|
|
|