Compare commits

...
Author SHA1 Message Date
michal c8b5732046 Fix Genesis scheduling and synchronization for up to 10 workers
(cherry picked from commit 684513ee59)
2026-09-10 22:59:56 +02:00
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
12 changed files with 789 additions and 178 deletions
+43 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@@ -1776,4 +1776,46 @@
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve"> <data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Nominální surový kalibrační faktor odpovídající 100 %. Tato hodnota se používá pro výpočet položky „iPerl CalibFactor (%)“ v konfiguraci výsledků.</value> <value>Nominální surový kalibrační faktor odpovídající 100 %. Tato hodnota se používá pro výpočet položky „iPerl CalibFactor (%)“ v konfiguraci výsledků.</value>
</data> </data>
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
Účel: Identifikuje komponentu, na kterou odkazují testy procedury.
Použití: Vyhledání komponenty a výběr Method v proceduře.
Rozsah hodnot: Jedinečný název komponenty.
Výchozí hodnota: SmartCommunicationGenesis
Uplatnění: Po přejmenování je nutný restart; zkontrolujte odkazy v procedurách.</value></data>
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
Účel: Časový limit pro požadavek na zrušení jednotlivého Genesis GCI volání.
Použití: Volání GciBridge z GenesisCommunicationForm (připojení, login, registry a související slot operace).
Rozsah hodnot: Celé číslo: 5005000 ms.
Výchozí hodnota: 1800 ms.
Uplatnění: Načte se na začátku aktivity vodoměru. Každé volání/pokus má vlastní limit; více kroků může trvat déle. Zrušení musí podporovat spodní vrstva ovladače. Ostatní uživatelé bridge zachovají původní limity.</value></data>
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
Účel: Uložené nastavení opakování komunikace.
Použití: Aktuální tok Genesis slot/GCI jej nečte; nespouští opakování neúspěšné operace.
Rozsah hodnot: Celé číslo: 110.
Výchozí hodnota: 4
Uplatnění: Zachováno pro kompatibilitu; bez vlivu na opakování slot/GCI.</value></data>
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
Účel: Uložená prodleva mezi opakováními komunikace.
Použití: Aktuální tok Genesis slot/GCI ji nečte; neřídí zpoždění zavření formuláře.
Rozsah hodnot: Celé číslo: 05000 ms.
Výchozí hodnota: 0 ms
Uplatnění: Zachováno pro kompatibilitu; bez vlivu na prodlevy slot/GCI.</value></data>
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
Účel: Počet pracovních vláken pro nakonfigurované hlavy Genesis.
Použití: GenesisCommunicationForm: vytvoření vláken a rozdělení slotů. Nemění počet vodoměrů.
Rozsah hodnot: Celé číslo: 110.
Výchozí hodnota: 10
Uplatnění: Vyžaduje restart. Sestavení TURA_SPECIAL používají samostatné přidělování slotů.</value></data>
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
Účel: Počet neúspěšných vodoměrů, při kterém se zobrazí výsledek kontroly.
Použití: GenesisCommunicationSeq: aktivita iPERL_check při sestavení s IPERL. Uživatel může pokračovat nebo přerušit; nejde o obecný limit chyb slotů.
Rozsah hodnot: Celé číslo: 140. Práh: počet neúspěšných &gt;= nastavení.
Výchozí hodnota: 10
Uplatnění: Použije se při následující příslušné kontrole; ne při běžných slot aktivitách.</value></data>
</root> </root>
+43 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@@ -2238,4 +2238,46 @@
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve"> <data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Roher Kalibrierfaktor, der 100 % entspricht. Dieser Wert wird zur Berechnung von „iPerl CalibFactor (%)“ in der Ergebnis-Konfiguration verwendet.</value> <value>Roher Kalibrierfaktor, der 100 % entspricht. Dieser Wert wird zur Berechnung von „iPerl CalibFactor (%)“ in der Ergebnis-Konfiguration verwendet.</value>
</data> </data>
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
Zweck: Identifiziert die Komponente, auf die die Tests der Prozedur verweisen.
Verwendung: Komponentensuche und Auswahl von Method in der Prozedur.
Wertebereich: Eindeutiger Komponentenname.
Standardwert: SmartCommunicationGenesis
Anwendung: Nach Umbenennung neu starten und Prozedurverweise prüfen.</value></data>
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
Zweck: Frist für die Abbruchanforderung eines einzelnen Genesis-GCI-Aufrufs.
Verwendung: GciBridge-Aufrufe aus GenesisCommunicationForm (Verbindung, Login, Register und zugehörige Slot-Operationen).
Wertebereich: Ganzzahl: 5005000 ms.
Standardwert: 1800 ms.
Anwendung: Zu Beginn jeder Zähleraktivität gelesen. Jeder Aufruf/Versuch erhält eine eigene Frist; mehrere Schritte können länger dauern. Der Treiber muss den Abbruch unterstützen. Andere Bridge-Aufrufer behalten ihre bisherigen Zeitlimits.</value></data>
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
Zweck: Gespeicherte Einstellung für Kommunikationswiederholungen.
Verwendung: Der aktuelle Genesis-Slot/GCI-Ablauf liest diesen Wert nicht; fehlgeschlagene Operationen werden dadurch nicht wiederholt.
Wertebereich: Ganzzahl: 110.
Standardwert: 4
Anwendung: Für Kompatibilität gespeichert; keine Wirkung auf Slot/GCI-Wiederholungen.</value></data>
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
Zweck: Gespeicherte Pause zwischen Kommunikationswiederholungen.
Verwendung: Der aktuelle Genesis-Slot/GCI-Ablauf liest diesen Wert nicht; die Verzögerung beim Schließen des Formulars bleibt unverändert.
Wertebereich: Ganzzahl: 05000 ms.
Standardwert: 0 ms
Anwendung: Für Kompatibilität gespeichert; keine Wirkung auf Slot/GCI-Pausen.</value></data>
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
Zweck: Anzahl der Arbeitsthreads für konfigurierte Genesis-Köpfe.
Verwendung: GenesisCommunicationForm: Thread-Erstellung und Slot-Verteilung. Ändert nicht die Anzahl der Zähler.
Wertebereich: Ganzzahl: 110.
Standardwert: 10
Anwendung: Neustart erforderlich. TURA_SPECIAL-Builds verwenden eine separate Slot-Zuweisung.</value></data>
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
Zweck: Anzahl fehlgeschlagener Zähler, ab der das Prüfergebnis angezeigt wird.
Verwendung: GenesisCommunicationSeq: Aktivität iPERL_check bei Builds mit IPERL. Der Benutzer kann fortfahren oder abbrechen; kein allgemeiner Grenzwert für Slot-Fehler.
Wertebereich: Ganzzahl: 140. Schwelle: Fehleranzahl &gt;= Einstellung.
Standardwert: 10
Anwendung: Gilt bei der nächsten entsprechenden Prüfung, nicht bei normalen Slot-Aktivitäten.</value></data>
</root> </root>
+43 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@@ -2509,4 +2509,46 @@
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve"> <data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Raw calibration factor representing 100 %. This value is used to calculate 'iPerl CalibFactor (%)' in the results configuration.</value> <value>Raw calibration factor representing 100 %. This value is used to calculate 'iPerl CalibFactor (%)' in the results configuration.</value>
</data> </data>
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
Purpose: Identifies the component referenced by procedure tests.
Used in: Component lookup and procedure Method selection.
Allowed values: Use a unique component name.
Default: SmartCommunicationGenesis.
Application: Restart required after renaming; review procedure references.</value></data>
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
Purpose: Cancellation deadline for each Genesis GCI request.
Used in: GciBridge requests made by GenesisCommunicationForm (connect, login, registers and related slot operations).
Allowed values: Integer: 5005000 ms.
Default: 1800 ms.
Application: Read at the start of each meter activity. Each request/attempt gets its own deadline; a multi-step test can take longer. Cancellation is cooperative: the underlying driver must honor the token. Legacy callers outside this flow retain their original timeouts.</value></data>
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
Purpose: Stored communication retry setting.
Used in: Not consumed by the current Genesis slot/GCI workflow; does not cause failed slot operations to retry.
Allowed values: Integer: 110.
Default: 4.
Application: Stored for compatibility; no slot/GCI retry effect.</value></data>
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
Purpose: Stored delay between communication retries.
Used in: Not consumed by the current Genesis slot/GCI workflow; does not control the form closing delay.
Allowed values: Integer: 05000 ms.
Default: 0 ms.
Application: Stored for compatibility; no slot/GCI delay effect.</value></data>
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
Purpose: Number of worker threads for processing configured Genesis heads.
Used in: GenesisCommunicationForm: worker creation and slot distribution. Does not change the number of configured meters.
Allowed values: Integer: 110.
Default: 10.
Application: Restart required. TURA_SPECIAL builds use a separate slot scheduling branch.</value></data>
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
Purpose: Number of failed meters that triggers a check-result prompt.
Used in: GenesisCommunicationSeq: iPERL_check activity, when compiled with IPERL. User may Continue or Abort. Not a general slot-error stop limit.
Allowed values: Integer: 140.
Default: 10. Threshold uses failed count &gt;= setting.
Application: Applied to the next applicable check; no effect on ordinary slot activities.</value></data>
</root> </root>
+43 -1
View File
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<root> <root>
<!-- <!--
Microsoft ResX Schema Microsoft ResX Schema
@@ -274,4 +274,46 @@
<data name="IperlCalibFactorNominalTooltip" xml:space="preserve"> <data name="IperlCalibFactorNominalTooltip" xml:space="preserve">
<value>Nominálny surový kalibračný faktor zodpovedajúci 100 %. Táto hodnota sa používa na výpočet položky „iPerl CalibFactor (%)“ v konfigurácii výsledkov.</value> <value>Nominálny surový kalibračný faktor zodpovedajúci 100 %. Táto hodnota sa používa na výpočet položky „iPerl CalibFactor (%)“ v konfigurácii výsledkov.</value>
</data> </data>
<data name="GenesisTooltip_nameTextBox" xml:space="preserve"><value>Name
Účel: Identifikuje komponent, na ktorý odkazujú testy procedúry.
Použitie: Vyhľadanie komponentu a výber Method v procedúre.
Rozsah hodnôt: Jedinečný názov komponentu.
Prednastavená hodnota: SmartCommunicationGenesis
Uplatnenie: Po premenovaní je potrebný reštart; skontrolujte odkazy v procedúrach.</value></data>
<data name="GenesisTooltip_commTimeoutTextBox" xml:space="preserve"><value>CommTimeout
Účel: Časový limit pre požiadavku na zrušenie jednotlivého Genesis GCI volania.
Použitie: Volania GciBridge z GenesisCommunicationForm (pripojenie, login, registre a súvisiace slot operácie).
Rozsah hodnôt: Celé číslo: 5005000 ms.
Prednastavená hodnota: 1800 ms.
Uplatnenie: Načíta sa na začiatku aktivity vodomera. Každé volanie/pokus má vlastný limit; viac krokov môže trvať dlhšie. Zrušenie musí podporovať spodná vrstva ovládača. Ostatní používatelia bridge zachovajú pôvodné limity.</value></data>
<data name="GenesisTooltip_maxCommRetriesTextBox" xml:space="preserve"><value>MaxCommRetries
Účel: Uložené nastavenie opakovania komunikácie.
Použitie: Aktuálny tok Genesis slot/GCI ho nečíta; nespúšťa opakovanie neúspešnej operácie.
Rozsah hodnôt: Celé číslo: 110.
Prednastavená hodnota: 4
Uplatnenie: Zachované pre kompatibilitu; bez vplyvu na opakovanie slot/GCI.</value></data>
<data name="GenesisTooltip_delayBetweenRetriesTextBox" xml:space="preserve"><value>DelayBetweenRetries
Účel: Uložená pauza medzi opakovaniami komunikácie.
Použitie: Aktuálny tok Genesis slot/GCI ju nečíta; neriadi oneskorenie zatvorenia formulára.
Rozsah hodnôt: Celé číslo: 05000 ms.
Prednastavená hodnota: 0 ms
Uplatnenie: Zachované pre kompatibilitu; bez vplyvu na pauzy slot/GCI.</value></data>
<data name="GenesisTooltip_nrThreadsTextBox" xml:space="preserve"><value>NrThreads
Účel: Počet pracovných vlákien pre nakonfigurované hlavy Genesis.
Použitie: GenesisCommunicationForm: vytvorenie vlákien a rozdelenie slotov. Nemení počet vodomerov.
Rozsah hodnôt: Celé číslo: 110.
Prednastavená hodnota: 10
Uplatnenie: Vyžaduje reštart. Zostavenia TURA_SPECIAL používajú samostatné prideľovanie slotov.</value></data>
<data name="GenesisTooltip_iperlCheckErrorsToStopTextBox" xml:space="preserve"><value>IperlCheckErrorsToStop
Účel: Počet neúspešných vodomerov, pri ktorom sa zobrazí výsledok kontroly.
Použitie: GenesisCommunicationSeq: aktivita iPERL_check pri zostavení s IPERL. Používateľ môže pokračovať alebo prerušiť; nejde o všeobecný limit chýb slotov.
Rozsah hodnôt: Celé číslo: 140. Prah: počet neúspešných &gt;= nastavenie.
Prednastavená hodnota: 10
Uplatnenie: Použije sa pri nasledujúcej príslušnej kontrole; nie pri bežných slot aktivitách.</value></data>
</root> </root>
@@ -1,4 +1,4 @@
using CordonelPreadjustmentUi; using CordonelPreadjustmentUi;
using CordonelPreadjustmentUi.Processes.Itinerary; using CordonelPreadjustmentUi.Processes.Itinerary;
using GenesisCordonelInterface.API; using GenesisCordonelInterface.API;
using GenesisCordonelInterface.Core; using GenesisCordonelInterface.Core;
@@ -37,6 +37,41 @@ using UdsWriterType = TBF.Rig.Output.DataStorage.UniDataStorageWriter.Writer;
namespace TBF.Rig.BridgeComponents.GciBridge namespace TBF.Rig.BridgeComponents.GciBridge
{ {
/// <summary>Per-async-flow Genesis request timeout; absent for legacy callers.</summary>
public static class GenesisRequestTimeout
{
private static readonly AsyncLocal<int?> current = new AsyncLocal<int?>();
private static readonly ILog timeoutLog = LogManager.GetLogger(typeof(GenesisRequestTimeout));
public static IDisposable Begin(int milliseconds)
{
if (milliseconds < 500 || milliseconds > 5000) throw new ArgumentOutOfRangeException(nameof(milliseconds));
var previous = current.Value;
current.Value = milliseconds;
return new Scope(previous);
}
public static CancellationTokenSource CreateDeadline(CancellationToken callerToken, string operation)
{
var milliseconds = current.Value;
if (!milliseconds.HasValue) return null;
var source = CancellationTokenSource.CreateLinkedTokenSource(callerToken);
timeoutLog.DebugFormat("Genesis CommTimeout applied: Operation={0}, TimeoutMs={1}", operation, milliseconds.Value);
source.Token.Register(() => timeoutLog.WarnFormat("Genesis request cancellation requested: Operation={0}, TimeoutMs={1}, CallerCancelled={2}", operation, milliseconds.Value, callerToken.IsCancellationRequested));
source.CancelAfter(milliseconds.Value);
return source;
}
private sealed class Scope : IDisposable
{
private readonly int? previous;
private bool disposed;
public Scope(int? previous) { this.previous = previous; }
public void Dispose() { if (!disposed) { current.Value = previous; disposed = true; } }
}
}
/// <summary> /// <summary>
/// TBF bridge component for integration with the sibling GCI project. /// TBF bridge component for integration with the sibling GCI project.
/// The component can be linked to UniDataStorage reader and writer components. /// The component can be linked to UniDataStorage reader and writer components.
@@ -288,6 +323,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
GciPublicModels.GciInitSlotRequest request, GciPublicModels.GciInitSlotRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "InitSlotAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (request == null) if (request == null)
@@ -369,6 +406,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
GciPublicModels.GciInitSlotRequest request, GciPublicModels.GciInitSlotRequest request,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "UpdateSlotAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (request == null) if (request == null)
@@ -447,6 +486,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slotId, int slotId,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetSlotAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -523,6 +564,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "CleanSlotAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slot <= 0) if (slot <= 0)
@@ -599,6 +642,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
public async Task<GciPublicModels.GciCleanAllSlotsResult> CleanAllSlotsAsync( public async Task<GciPublicModels.GciCleanAllSlotsResult> CleanAllSlotsAsync(
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "CleanAllSlotsAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
GciPublicModels.GciCleanAllSlotsResult result = GciPublicModels.GciCleanAllSlotsResult result =
@@ -671,6 +716,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slotId, int slotId,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPcbIdAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -788,6 +835,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ConnectAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slot <= 0) if (slot <= 0)
@@ -820,6 +869,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slotId, int slotId,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "LoginAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -891,6 +942,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slotId, int slotId,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "DisconnectAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -970,6 +1023,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string password, string password,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "SetPasswordAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -1046,6 +1101,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string registerName, string registerName,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ReadRegisterAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -1136,6 +1193,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
bool refreshSystemState = false, bool refreshSystemState = false,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "WriteRegisterAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
if (slotId <= 0) if (slotId <= 0)
@@ -1239,6 +1298,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
string pcbId, string pcbId,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPasswordAsync");
token = deadline == null ? token : deadline.Token;
EnsureReader(); EnsureReader();
if (string.IsNullOrWhiteSpace(pcbId)) if (string.IsNullOrWhiteSpace(pcbId))
@@ -1317,6 +1378,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int meterSize, int meterSize,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "GetPreAdjustmentCalibrationParamsAsync");
token = deadline == null ? token : deadline.Token;
EnsureReader(); EnsureReader();
if (meterSize < 0) if (meterSize < 0)
@@ -1504,6 +1567,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
/// <returns>Complete workflow result including all retry statistics and operation results.</returns> /// <returns>Complete workflow result including all retry statistics and operation results.</returns>
public async Task<GciFullLoginResult> ConnectFullPassLoginWithRetryAsync(int slotId, CancellationToken token = default) public async Task<GciFullLoginResult> ConnectFullPassLoginWithRetryAsync(int slotId, CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "ConnectFullPassLoginWithRetryAsync");
token = deadline == null ? token : deadline.Token;
EnsureExternalInterface(); EnsureExternalInterface();
EnsureReader(); EnsureReader();
@@ -1605,6 +1670,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
IEnumerable<GciPublicModels.MeterBatchDebugStatus> selectedSlots, IEnumerable<GciPublicModels.MeterBatchDebugStatus> selectedSlots,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_DetectAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_DetectAsync); const string operation = nameof(PreAdjustment_DetectAsync);
try try
@@ -1678,6 +1745,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_PreparationAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_PreparationAsync); const string operation = nameof(PreAdjustment_PreparationAsync);
try try
@@ -1758,6 +1827,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_AmplitudeTestAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_AmplitudeTestAsync); const string operation = nameof(PreAdjustment_AmplitudeTestAsync);
try try
@@ -1838,6 +1909,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_TemperatureCalibrationAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_TemperatureCalibrationAsync); const string operation = nameof(PreAdjustment_TemperatureCalibrationAsync);
try try
@@ -1945,6 +2018,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_OffsetTestAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_OffsetTestAsync); const string operation = nameof(PreAdjustment_OffsetTestAsync);
try try
@@ -2025,6 +2100,8 @@ namespace TBF.Rig.BridgeComponents.GciBridge
int slot, int slot,
CancellationToken token = default) CancellationToken token = default)
{ {
using var deadline = GenesisRequestTimeout.CreateDeadline(token, "PreAdjustment_CompletionAsync");
token = deadline == null ? token : deadline.Token;
const string operation = nameof(PreAdjustment_CompletionAsync); const string operation = nameof(PreAdjustment_CompletionAsync);
try try
@@ -1,4 +1,4 @@
/// ///
/// Copyright (c) 2015-2019 Sensus Metering Systems /// Copyright (c) 2015-2019 Sensus Metering Systems
/// Author: Milan Hanajík /// Author: Milan Hanajík
/// ///
@@ -7,8 +7,44 @@ using TBF.Rig.RegisterReaders.GenesisRegReader.implementations;
namespace TBF.Rig.TestMethods.GenesisCommunication namespace TBF.Rig.TestMethods.GenesisCommunication
{ {
/// <summary>Tracks one completion per worker, activity and group.</summary>
public sealed class GenesisWorkerGroupCompletion
{
private readonly int workerCount;
private readonly System.Collections.Generic.HashSet<int> completed = new System.Collections.Generic.HashSet<int>();
private int activity, group;
private bool released;
public GenesisWorkerGroupCompletion(int workerCount)
{
if (workerCount < 1 || workerCount > 10) throw new ArgumentOutOfRangeException(nameof(workerCount));
this.workerCount = workerCount;
}
public void Begin(int activityStep, int groupNumber)
{
lock (completed) { activity = activityStep; group = groupNumber; released = false; completed.Clear(); }
}
public bool Complete(int activityStep, int groupNumber, int worker)
{
lock (completed)
{
if (released || activityStep != activity || groupNumber != group || worker < 0 || worker >= workerCount) return false;
if (!completed.Add(worker) || completed.Count != workerCount) return false;
released = true;
return true;
}
}
public static System.Collections.Generic.IEnumerable<int> BoardIndexes(int worker, int workers, int boards)
{
if (workers < 1 || workers > 10 || worker < 0 || worker >= workers || boards < 0) throw new ArgumentOutOfRangeException();
for (int index = worker; index < boards; index += workers) yield return index;
}
}
public class CommCompletedEventArgs : EventArgs public class CommCompletedEventArgs : EventArgs
{ {
public bool WorkerGroupCompleted;
public int ActivityStep;
public int Group;
public int ThreadId; public int ThreadId;
public int WMNr0; /// 0-based water meter position public int WMNr0; /// 0-based water meter position
public GenesisSmartReader Ihead; public GenesisSmartReader Ihead;
@@ -1,4 +1,4 @@
using TBF.Rig.TestMethods.iPerlCommunication; using TBF.Rig.TestMethods.iPerlCommunication;
/// ///
/// Copyright (c) 2015-2023 Sensus Slovensko a.s. /// Copyright (c) 2015-2023 Sensus Slovensko a.s.
/// ///
@@ -204,14 +204,14 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
static IList<iPerlCommunicationParams> multiTestParams; static IList<iPerlCommunicationParams> multiTestParams;
static int currentActivityStep; static volatile int currentActivityStep;
static int currentGroup; /// form -> worker thread (0 = none) static volatile int currentGroup; /// form -> worker thread (0 = none)
static int lastGroup; static int lastGroup;
static int completedCommCount; /// Number of completed communication steps private GenesisWorkerGroupCompletion groupCompletion;
static IList<Thread> workerThreads; static IList<Thread> workerThreads;
static IList<int> muxBrdOrGroup14Nrs; static IList<int> muxBrdOrGroup14Nrs;
static bool stopWorkerThreads; /// form -> worker thread static volatile bool stopWorkerThreads; /// form -> worker thread
/// <summary> Parameterless constructor (without watermeters, threads) </summary> /// <summary> Parameterless constructor (without watermeters, threads) </summary>
@@ -384,7 +384,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
/// ///
currentActivityStep = 0; currentActivityStep = 0;
currentGroup = 0; currentGroup = 0;
completedCommCount = 0;
stopWorkerThreads = false; stopWorkerThreads = false;
/// group numbers are >=1, lastGroup == 0 means there is no group /// group numbers are >=1, lastGroup == 0 means there is no group
@@ -409,6 +408,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
if (!muxBrdOrGroup14Nrs.Contains(iPerl.MuxBoardNrOrGroup14)) muxBrdOrGroup14Nrs.Add(iPerl.MuxBoardNrOrGroup14); if (!muxBrdOrGroup14Nrs.Contains(iPerl.MuxBoardNrOrGroup14)) muxBrdOrGroup14Nrs.Add(iPerl.MuxBoardNrOrGroup14);
} }
groupCompletion = new GenesisWorkerGroupCompletion(workerThreads.Count);
groupCompletion.Begin(0, 1);
log.InfoFormat("Genesis workers configured: Threads={0}, Group1Count={1}, LastGroup2={2}", workerThreads.Count, muxBrdOrGroup14Nrs.Count, lastGroup);
log.WarnFormat("nrThreads = {0}", workerThreads.Count); log.WarnFormat("nrThreads = {0}", workerThreads.Count);
} }
@@ -666,6 +668,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
for (int i = 0; i < multiTestParams.Count; i++ ) for (int i = 0; i < multiTestParams.Count; i++ )
{ {
while (activityStep != currentActivityStep && !stopWorkerThreads) Thread.Sleep(50);
if (stopWorkerThreads) break;
Test currentTest = tests[i]; Test currentTest = tests[i];
iPerlCommunicationParams currentTestParams = multiTestParams[i]; iPerlCommunicationParams currentTestParams = multiTestParams[i];
string currentActivity = currentTestParams.Activity; /// Current activity string currentActivity = currentTestParams.Activity; /// Current activity
@@ -707,7 +711,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
} }
//HOLD ON - if activity is like HoldSlotStr, ignore loop and do activity like HoldSlotStr //HOLD ON - if activity is like HoldSlotStr, ignore loop and do activity like HoldSlotStr
if (currentActivity.ToLower().Equals(HoldSlotStr.ToLower())) bool isHoldActivity = currentActivity.ToLower().Equals(HoldSlotStr.ToLower());
if (isHoldActivity)
{ {
log.Debug($"Activity '{currentActivity}' is HoldSlotStr, skipping loop and performing HoldOn operation. Thread: {threadID}"); log.Debug($"Activity '{currentActivity}' is HoldSlotStr, skipping loop and performing HoldOn operation. Thread: {threadID}");
@@ -716,7 +721,6 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
string resultStr = string.Empty; string resultStr = string.Empty;
error = HoldOn(threadID, ref resultStr); // wait for the hold on to complete - one time for each thread error = HoldOn(threadID, ref resultStr); // wait for the hold on to complete - one time for each thread
ProcessWaterMetersDialogConfirmation(threadID, resultStr,error); ProcessWaterMetersDialogConfirmation(threadID, resultStr,error);
return;
} }
for (int group = 1; group <= lastGroup; group++) for (int group = 1; group <= lastGroup; group++)
@@ -729,10 +733,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
if (stopWorkerThreads) break; if (stopWorkerThreads) break;
if (!isHoldActivity)
{
#if TURA_SPECIAL #if TURA_SPECIAL
int threadIx = threadID; /// Just one thread for TURA_SPECIAL int threadIx = threadID; /// Just one thread for TURA_SPECIAL
#else #else
for (int threadIx = threadID; threadIx < threadID + 10; threadIx += cfg.NrThreads)// - max threads = 4 => 10 foreach (int threadIx in GenesisWorkerGroupCompletion.BoardIndexes(threadID, workerThreads.Count, muxBrdOrGroup14Nrs.Count))
#endif #endif
{ {
bool wmFound = false; bool wmFound = false;
@@ -759,6 +765,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
CommErr error; CommErr error;
string resultStr = string.Empty; string resultStr = string.Empty;
using var requestTimeout = TBF.Rig.BridgeComponents.GciBridge.GenesisRequestTimeout.Begin(cfg.CommTimeout);
/// ///
/// RFID communication activity call /// RFID communication activity call
@@ -884,8 +892,13 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
if (stopWorkerThreads) break; if (stopWorkerThreads) break;
} }
if (stopWorkerThreads) break; }
} /// for (int group
if (stopWorkerThreads) break;
log.DebugFormat("Genesis worker group completed: Activity={0}, Group2={1}, Worker={2}", activityStep, group, threadID);
OnCommCompleted(null, new CommCompletedEventArgs(threadID, -1, null, null, string.Empty, CommErr.None)
{ WorkerGroupCompleted = true, ActivityStep = activityStep, Group = group });
} /// for (int group
TBF.UiBridge.Bridge.OnTestProgress(null, new TBF.UiBridge.TestProgressEventArgs(tests[i], Progress.Completed)); TBF.UiBridge.Bridge.OnTestProgress(null, new TBF.UiBridge.TestProgressEventArgs(tests[i], Progress.Completed));
activityStep++; activityStep++;
@@ -1608,21 +1621,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
} }
} }
#if !TURA_SPECIAL // Per-meter results update the UI only. Advance after every worker
/// // has finished all of its Group 1 assignments for this Group 2.
/// Branch if (!data.WorkerGroupCompleted || !groupCompletion.Complete(data.ActivityStep, data.Group, data.ThreadId)) return;
/// log.InfoFormat("Genesis group completed: Activity={0}, Group2={1}, Workers={2}", data.ActivityStep, data.Group, workerThreads.Count);
lock (this)
{
if (++completedCommCount < 4) return;
completedCommCount = 0;
}
#endif
if (currentGroup < lastGroup) if (currentGroup < lastGroup)
{ {
/// Go to the next step / next group /// Go to the next step / next group
currentGroup++; currentGroup++;
groupCompletion.Begin(currentActivityStep, currentGroup);
} }
else if (currentActivityStep + 1 < multiTestParams.Count) else if (currentActivityStep + 1 < multiTestParams.Count)
{ {
@@ -1630,16 +1639,17 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
currentActivityStep++; currentActivityStep++;
activityLabel.Text = multiTestParams[currentActivityStep].Activity; activityLabel.Text = multiTestParams[currentActivityStep].Activity;
currentGroup++; currentGroup++;
groupCompletion.Begin(currentActivityStep, currentGroup);
} }
else else
{ {
log.Debug("Wait for finish workerThreads 4 seconds"); log.Debug("Waiting for all Genesis workers; results remain visible for 4 seconds afterwards.");
Thread lastThread = workerThreads[data.ThreadId]; Thread[] finishingThreads = workerThreads.ToArray();
Task.Run(() => Task.Run(() =>
{ {
lastThread.Join(2000); foreach (var thread in finishingThreads) thread.Join();
if (!CanUseUi()) return; if (!CanUseUi()) return;
@@ -1,4 +1,4 @@
/// ///
/// Copyright (c) 2015-2022 Sensus Slovensko a.s. /// Copyright (c) 2015-2022 Sensus Slovensko a.s.
/// ///
@@ -58,6 +58,9 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
testMethodCfg.UseWebService = tmpCfg.UseWebService; testMethodCfg.UseWebService = tmpCfg.UseWebService;
testMethodCfg.BaseUrl = tmpCfg.BaseUrl; testMethodCfg.BaseUrl = tmpCfg.BaseUrl;
testMethodCfg.RelativeUrl = tmpCfg.RelativeUrl; testMethodCfg.RelativeUrl = tmpCfg.RelativeUrl;
log.InfoFormat("Genesis runtime configuration applied: Name={0}, CommTimeout={1}ms, MaxRetries={2}, DelayBetweenRetries={3}ms, ErrorsToStop={4}",
Name, testMethodCfg.CommTimeout, testMethodCfg.MaxCommRetries,
testMethodCfg.DelayBetweenRetries, testMethodCfg.IperlCheckErrorsToStop);
} }
} }
}; };
@@ -80,6 +83,12 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
: base(cfg) : base(cfg)
{ {
testMethodCfg = cfg as TestMethodCfg; testMethodCfg = cfg as TestMethodCfg;
if (testMethodCfg != null)
log.InfoFormat("Genesis component configuration loaded: Name={0}, CommTimeout={1}ms, MaxRetries={2}, DelayBetweenRetries={3}ms, NrThreads={4}, ErrorsToStop={5}",
testMethodCfg.Name, testMethodCfg.CommTimeout, testMethodCfg.MaxCommRetries,
testMethodCfg.DelayBetweenRetries, testMethodCfg.NrThreads, testMethodCfg.IperlCheckErrorsToStop);
else
log.Error("Genesis component received an incompatible or null configuration.");
CreateMilestonesAndConditions(); CreateMilestonesAndConditions();
} }
@@ -1,4 +1,4 @@
/// ///
/// Copyright (c) 2015-2020 Sensus Slovensko a.s. /// Copyright (c) 2015-2020 Sensus Slovensko a.s.
/// ///
@@ -12,15 +12,21 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
{ {
public partial class TestMethodCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl public partial class TestMethodCfgCtrl : Configs.ConfigCtrlUtils, IComponentCfgCtrl
{ {
public bool ShowMore { get { return false; } } private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(TestMethodCfgCtrl));
iPerlCommunication.TestMethodCfg config; public bool ShowMore { get { return false; } }
TestMethodCfg config;
public IComponentCfg Config public IComponentCfg Config
{ {
get { return config as IComponentCfg; } get { return config as IComponentCfg; }
set set
{ {
config = value as iPerlCommunication.TestMethodCfg; config = value as TestMethodCfg;
if (config == null)
log.ErrorFormat("Genesis Properties configuration rejected: expected {0}, received {1}", typeof(TestMethodCfg).FullName, value == null ? "null" : value.GetType().FullName);
else
LogConfiguration("loaded");
Redraw(); Redraw();
} }
} }
@@ -28,6 +34,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
public TestMethodCfgCtrl() public TestMethodCfgCtrl()
{ {
InitializeComponent(); InitializeComponent();
InitializeSettingToolTips();
dfltQ2corrFactorsGroupBox.Visible = false; dfltQ2corrFactorsGroupBox.Visible = false;
useWebServiceGroupBox.Visible = false; useWebServiceGroupBox.Visible = false;
var factorHint = new System.Windows.Forms.Label var factorHint = new System.Windows.Forms.Label
@@ -50,7 +57,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
void Redraw() void Redraw()
{ {
if (config == null) return; /// Control was not loaded, settings were not changed if (config == null) return; /// Control was not loaded, settings were not changed
classNameLabel.Text = config.Factory.ClassName; classNameLabel.Text = config.Factory == null ? "TestMethods.GenesisCommunication" : config.Factory.ClassName;
nameTextBox.Text = config.Name; nameTextBox.Text = config.Name;
commTimeoutTextBox.Text = config.CommTimeout.ToString(); commTimeoutTextBox.Text = config.CommTimeout.ToString();
maxCommRetriesTextBox.Text = config.MaxCommRetries.ToString(); maxCommRetriesTextBox.Text = config.MaxCommRetries.ToString();
@@ -58,22 +65,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
nrThreadsTextBox.Text = config.NrThreads.ToString(); nrThreadsTextBox.Text = config.NrThreads.ToString();
iperlCheckErrorsToStopTextBox.Text = config.IperlCheckErrorsToStop.ToString(); iperlCheckErrorsToStopTextBox.Text = config.IperlCheckErrorsToStop.ToString();
textBox15rl.Text = config.DfltQ2c_15_rl.ToString();
textBox15lr.Text = config.DfltQ2c_15_lr.ToString();
textBox20rl.Text = config.DfltQ2c_20_rl.ToString();
textBox20lr.Text = config.DfltQ2c_20_lr.ToString();
textBox25_63rl.Text = config.DfltQ2c_25_63_rl.ToString();
textBox25_63lr.Text = config.DfltQ2c_25_63_lr.ToString();
textBox25_10rl.Text = config.DfltQ2c_25_10_rl.ToString();
textBox25_10lr.Text = config.DfltQ2c_25_10_lr.ToString();
textBox32rl.Text = config.DfltQ2c_32_rl.ToString();
textBox32lr.Text = config.DfltQ2c_32_lr.ToString();
textBox40rl.Text = config.DfltQ2c_40_rl.ToString();
textBox40lr.Text = config.DfltQ2c_40_lr.ToString();
useWebServiceCheckBox.Checked = config.UseWebService;
baseUrlTextBox.Text = config.BaseUrl;
relativeUrlTextBox.Text = config.RelativeUrl;
} }
public void Unlock() public void Unlock()
@@ -85,28 +77,18 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
nrThreadsTextBox.Enabled = true; nrThreadsTextBox.Enabled = true;
iperlCheckErrorsToStopTextBox.Enabled = true; iperlCheckErrorsToStopTextBox.Enabled = true;
textBox15rl.Enabled = true;
textBox15lr.Enabled = true;
textBox20rl.Enabled = true;
textBox20lr.Enabled = true;
textBox25_63rl.Enabled = true;
textBox25_63lr.Enabled = true;
textBox25_10rl.Enabled = true;
textBox25_10lr.Enabled = true;
textBox32rl.Enabled = true;
textBox32lr.Enabled = true;
textBox40rl.Enabled = true;
textBox40lr.Enabled = true;
useWebServiceCheckBox.Enabled = true;
ManageCheckGroupBox(useWebServiceCheckBox, useWebServiceGroupBox);
baseUrlTextBox.Enabled = useWebServiceCheckBox.Enabled;
relativeUrlTextBox.Enabled = useWebServiceCheckBox.Enabled;
} }
public CfgUpdateFlags VerifyCfg(ref string message) public CfgUpdateFlags VerifyCfg(ref string message)
{ {
CfgUpdateFlags flags = CfgUpdateFlags.None; CfgUpdateFlags flags = CfgUpdateFlags.None;
if (config == null)
{
message += Environment.NewLine + "Genesis configuration is not loaded.";
log.Error("Genesis Properties validation failed: configuration is not loaded.");
return CfgUpdateFlags.Error;
}
int dummy; int dummy;
if (!int.TryParse(commTimeoutTextBox.Text, out dummy) || dummy < 500 || dummy > 5000) if (!int.TryParse(commTimeoutTextBox.Text, out dummy) || dummy < 500 || dummy > 5000)
@@ -122,79 +104,21 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
if (!int.TryParse(delayBetweenRetriesTextBox.Text, out dummy) || dummy < 0 || dummy > 5000) if (!int.TryParse(delayBetweenRetriesTextBox.Text, out dummy) || dummy < 0 || dummy > 5000)
{ {
flags |= CfgUpdateFlags.Error; flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Comm. timeout' should be in range 0 .. 5000"; message += Environment.NewLine + "'Delay between retries' should be in range 0 .. 5000";
} }
if (!int.TryParse(nrThreadsTextBox.Text, out dummy) || (dummy < 1 || dummy > 10)) if (!int.TryParse(nrThreadsTextBox.Text, out dummy) || (dummy < 1 || dummy > 10))
{ {
flags |= CfgUpdateFlags.Error; flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "'Nr. threads' should be in range 1 .. 10"; message += Environment.NewLine + "'Nr. threads' should be in range 1 .. 10";
} }
if (!int.TryParse(iperlCheckErrorsToStopTextBox.Text, out dummy) || ((dummy < 1) && (dummy > 40))) if (!int.TryParse(iperlCheckErrorsToStopTextBox.Text, out dummy) || ((dummy < 1) || (dummy > 40)))
{ {
flags |= CfgUpdateFlags.Error; flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + string.Format(Strings.Invalid_0, iperlCheckErrorsToStopLabel.Text); message += Environment.NewLine + string.Format(Strings.Invalid_0, iperlCheckErrorsToStopLabel.Text);
} }
if (!int.TryParse(textBox15rl.Text, out dummy) || dummy < -50 || dummy > 50) if ((flags & CfgUpdateFlags.Error) != 0)
{ log.WarnFormat("Genesis Properties validation failed: {0}", message);
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN15 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox15lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN15 LR should be in range -50 .. 50";
}
if (!int.TryParse(textBox20rl.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN20 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox20lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN20 LR should be in range -50 .. 50";
}
if (!int.TryParse(textBox25_63rl.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 6.3 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox25_63lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 6.3 LR should be in range -50 .. 50";
}
if (!int.TryParse(textBox25_10rl.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 10 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox25_10lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN25 Q3 10 LR should be in range -50 .. 50";
}
if (!int.TryParse(textBox32rl.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN32 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox32lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN32 LR should be in range -50 .. 50";
}
if (!int.TryParse(textBox40rl.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN40 RL should be in range -50 .. 50";
}
if (!int.TryParse(textBox40lr.Text, out dummy) || dummy < -50 || dummy > 50)
{
flags |= CfgUpdateFlags.Error;
message += Environment.NewLine + "Default Q2 correction factor DN40 LR should be in range -50 .. 50";
}
return flags; return flags;
} }
@@ -203,7 +127,8 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
{ {
CfgUpdateFlags flags = CfgUpdateFlags.None; CfgUpdateFlags flags = CfgUpdateFlags.None;
if (config == null) return CfgUpdateFlags.Error; /// Control was not loaded, settings were not changed string message = string.Empty;
if ((VerifyCfg(ref message) & CfgUpdateFlags.Error) != 0) return CfgUpdateFlags.Error;
if (config.Name != nameTextBox.Text) if (config.Name != nameTextBox.Text)
{ {
@@ -211,26 +136,11 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd); flags |= (CfgUpdateFlags.AnyChange | CfgUpdateFlags.RestartRqrd);
} }
var CommTimeout = config.CommTimeout;; var CommTimeout = config.CommTimeout;
var MaxCommRetries = config.MaxCommRetries; var MaxCommRetries = config.MaxCommRetries;
var DelayBetweenRetries = config.DelayBetweenRetries; var DelayBetweenRetries = config.DelayBetweenRetries;
var NrThreads = config.NrThreads; var NrThreads = config.NrThreads;
var IperlCheckErrorsToStop = config.IperlCheckErrorsToStop; var IperlCheckErrorsToStop = config.IperlCheckErrorsToStop;
var DfltQ2c_15_rl = config.DfltQ2c_15_rl;
var DfltQ2c_15_lr = config.DfltQ2c_15_lr;
var DfltQ2c_20_rl = config.DfltQ2c_20_rl;
var DfltQ2c_20_lr = config.DfltQ2c_20_lr;
var DfltQ2c_25_63_rl = config.DfltQ2c_25_63_rl;
var DfltQ2c_25_63_lr = config.DfltQ2c_25_63_lr;
var DfltQ2c_25_10_rl = config.DfltQ2c_25_10_rl;
var DfltQ2c_25_10_lr = config.DfltQ2c_25_10_lr;
var DfltQ2c_32_rl = config.DfltQ2c_32_rl;
var DfltQ2c_32_lr = config.DfltQ2c_32_lr;
var DfltQ2c_40_rl = config.DfltQ2c_40_rl;
var DfltQ2c_40_lr = config.DfltQ2c_40_lr;
var UseWebService = config.UseWebService;
var BaseUrl = config.BaseUrl;
var RelativeUrl = config.RelativeUrl;
flags |= UpdateDifferent(ref CommTimeout, commTimeoutTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange); flags |= UpdateDifferent(ref CommTimeout, commTimeoutTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref MaxCommRetries, maxCommRetriesTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange); flags |= UpdateDifferent(ref MaxCommRetries, maxCommRetriesTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
@@ -238,22 +148,7 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
flags |= UpdateDifferent(ref NrThreads, nrThreadsTextBox.Text, CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange); flags |= UpdateDifferent(ref NrThreads, nrThreadsTextBox.Text, CfgUpdateFlags.RestartRqrd | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref IperlCheckErrorsToStop, iperlCheckErrorsToStopTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange); flags |= UpdateDifferent(ref IperlCheckErrorsToStop, iperlCheckErrorsToStopTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_15_rl, textBox15rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_15_lr, textBox15lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_20_rl, textBox20rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_20_lr, textBox20lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_25_63_rl, textBox25_63rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_25_63_lr, textBox25_63lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_25_10_rl, textBox25_10rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_25_10_lr, textBox25_10lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_32_rl, textBox32rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_32_lr, textBox32lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_40_rl, textBox40rl.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref DfltQ2c_40_lr, textBox40lr.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref UseWebService, useWebServiceCheckBox.Checked, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref BaseUrl, baseUrlTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
flags |= UpdateDifferent(ref RelativeUrl, relativeUrlTextBox.Text, CfgUpdateFlags.InvokeCfgChange | CfgUpdateFlags.AnyChange);
config.CommTimeout = CommTimeout; config.CommTimeout = CommTimeout;
@@ -261,30 +156,44 @@ namespace TBF.Rig.TestMethods.GenesisCommunication
config.DelayBetweenRetries = DelayBetweenRetries; config.DelayBetweenRetries = DelayBetweenRetries;
config.NrThreads = NrThreads; config.NrThreads = NrThreads;
config.IperlCheckErrorsToStop = IperlCheckErrorsToStop; config.IperlCheckErrorsToStop = IperlCheckErrorsToStop;
config.DfltQ2c_15_rl = DfltQ2c_15_rl;
config.DfltQ2c_15_lr = DfltQ2c_15_lr;
config.DfltQ2c_20_rl = DfltQ2c_20_rl;
config.DfltQ2c_20_lr = DfltQ2c_20_lr;
config.DfltQ2c_25_63_rl = DfltQ2c_25_63_rl;
config.DfltQ2c_25_63_lr = DfltQ2c_25_63_lr;
config.DfltQ2c_25_10_rl = DfltQ2c_25_10_rl;
config.DfltQ2c_25_10_lr = DfltQ2c_25_10_lr;
config.DfltQ2c_32_rl = DfltQ2c_32_rl;
config.DfltQ2c_32_lr = DfltQ2c_32_lr;
config.DfltQ2c_40_rl = DfltQ2c_40_rl;
config.DfltQ2c_40_lr = DfltQ2c_40_lr;
config.UseWebService = UseWebService;
config.BaseUrl = BaseUrl;
config.RelativeUrl = RelativeUrl;
LogConfiguration("updated; flags=" + flags);
if ((flags & CfgUpdateFlags.InvokeCfgChange) != 0) if ((flags & CfgUpdateFlags.InvokeCfgChange) != 0)
{ {
iPerlCommunication.TestMethod.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config)); TestMethod.OnCfgChange(this, new CfgChangeArgs(CfgChangeCmd.CfgChange, config));
} }
return flags; return flags;
} }
private ToolTip settingsToolTip;
private void InitializeSettingToolTips()
{
if (components == null) components = new System.ComponentModel.Container();
settingsToolTip = new ToolTip(components) { AutoPopDelay = 30000, InitialDelay = 400, ReshowDelay = 100, ShowAlways = true };
settingsToolTip.SetToolTip(nameTextBox, Strings.ResourceManager.GetString("GenesisTooltip_nameTextBox", Strings.Culture));
settingsToolTip.SetToolTip(nameLabel, settingsToolTip.GetToolTip(nameTextBox));
settingsToolTip.SetToolTip(commTimeoutTextBox, Strings.ResourceManager.GetString("GenesisTooltip_commTimeoutTextBox", Strings.Culture));
settingsToolTip.SetToolTip(commTimeoutLabel, settingsToolTip.GetToolTip(commTimeoutTextBox));
settingsToolTip.SetToolTip(maxCommRetriesTextBox, Strings.ResourceManager.GetString("GenesisTooltip_maxCommRetriesTextBox", Strings.Culture));
settingsToolTip.SetToolTip(maxNrRetriesLabel, settingsToolTip.GetToolTip(maxCommRetriesTextBox));
settingsToolTip.SetToolTip(delayBetweenRetriesTextBox, Strings.ResourceManager.GetString("GenesisTooltip_delayBetweenRetriesTextBox", Strings.Culture));
settingsToolTip.SetToolTip(delayBetweenRetriesLabel, settingsToolTip.GetToolTip(delayBetweenRetriesTextBox));
settingsToolTip.SetToolTip(nrThreadsTextBox, Strings.ResourceManager.GetString("GenesisTooltip_nrThreadsTextBox", Strings.Culture));
settingsToolTip.SetToolTip(nrThreadsLabel, settingsToolTip.GetToolTip(nrThreadsTextBox));
settingsToolTip.SetToolTip(iperlCheckErrorsToStopTextBox, Strings.ResourceManager.GetString("GenesisTooltip_iperlCheckErrorsToStopTextBox", Strings.Culture));
settingsToolTip.SetToolTip(iperlCheckErrorsToStopLabel, settingsToolTip.GetToolTip(iperlCheckErrorsToStopTextBox));
}
private void LogConfiguration(string action)
{
log.InfoFormat("Genesis Properties {0}: Name={1}, CommTimeout={2}ms, MaxRetries={3}, DelayBetweenRetries={4}ms, NrThreads={5}, ErrorsToStop={6}; Q3 source=WaterMeterData.Text1-Text3; legacy fields preserved",
action, config.Name, config.CommTimeout, config.MaxCommRetries,
config.DelayBetweenRetries, config.NrThreads, config.IperlCheckErrorsToStop);
}
private void ManageCheckGroupBox(CheckBox chk, GroupBox grp) private void ManageCheckGroupBox(CheckBox chk, GroupBox grp)
{ {
/// Make sure the CheckBox isn't in the GroupBox. This will only happen the first time. /// Make sure the CheckBox isn't in the GroupBox. This will only happen the first time.
+213
View File
@@ -0,0 +1,213 @@
using System;
using System.Linq;
using System.Threading;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using TBF.Rig.TestMethods.GenesisCommunication;
namespace TBFTests
{
[TestClass]
[TestCategory("GenesisParallelScheduling")]
public class GenesisParallelSchedulingTests
{
[DataTestMethod]
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
public void EveryBoardIsAssignedExactlyOnceForAllSupportedWorkerCounts(int workers)
{
foreach (int boards in new[] { 0, 1, 2, 7, 10 })
{
var actual = Enumerable.Range(0, workers)
.SelectMany(worker => GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards)).OrderBy(x => x).ToArray();
CollectionAssert.AreEqual(Enumerable.Range(0, boards).ToArray(), actual);
}
}
[DataTestMethod]
[DataRow(1)] [DataRow(2)] [DataRow(3)] [DataRow(4)] [DataRow(5)]
[DataRow(6)] [DataRow(7)] [DataRow(8)] [DataRow(9)] [DataRow(10)]
public void GroupAdvancesOnlyAfterEveryDistinctWorkerFinishes(int count)
{
var completion = new GenesisWorkerGroupCompletion(count);
completion.Begin(0, 1);
Assert.IsFalse(completion.Complete(0, 2, 0));
Assert.IsFalse(completion.Complete(1, 1, 0));
Assert.IsFalse(completion.Complete(0, 1, -1));
Assert.IsFalse(completion.Complete(0, 1, count));
for (int worker = 0; worker < count; worker++)
{
Assert.AreEqual(worker == count - 1, completion.Complete(0, 1, worker));
Assert.IsFalse(completion.Complete(0, 1, worker), "Duplicate completion must not advance the group.");
}
completion.Begin(0, 2);
Assert.IsFalse(completion.Complete(0, 1, 0), "Late event from the previous group.");
for (int worker = count - 1; worker >= 0; worker--)
Assert.AreEqual(worker == 0, completion.Complete(0, 2, worker));
completion.Begin(1, 1);
Assert.IsFalse(completion.Complete(0, 2, 0));
}
[TestMethod]
public void MultipleActivitiesAndGroupsRequireAllWorkersIncludingIdleWorkers()
{
var completion = new GenesisWorkerGroupCompletion(10);
for (int activity = 0; activity < 3; activity++)
for (int group = 1; group <= 10; group++)
{
completion.Begin(activity, group);
Assert.IsFalse(completion.Complete(activity - 1, group, 0));
Assert.IsFalse(completion.Complete(activity, group - 1, 0));
// Also models HOLD ON: no board requests, but each worker must finish.
for (int worker = 0; worker < 10; worker++)
Assert.AreEqual(worker == 9, completion.Complete(activity, group, worker));
}
}
[TestMethod]
public void TenWorkersCanRunConcurrentlyAndSlowTenthWorkerHoldsGroup()
{
var completion = new GenesisWorkerGroupCompletion(10);
completion.Begin(0, 1);
using (var started = new CountdownEvent(10))
using (var firstNine = new CountdownEvent(9))
using (var run = new ManualResetEventSlim(false))
using (var slow = new ManualResetEventSlim(false))
{
int advances = 0;
var threads = Enumerable.Range(0, 10).Select(worker => new Thread(() =>
{
started.Signal();
run.Wait();
if (worker == 9) slow.Wait();
if (completion.Complete(0, 1, worker)) Interlocked.Increment(ref advances);
if (worker != 9) firstNine.Signal();
}) { IsBackground = true }).ToArray();
foreach (var thread in threads) thread.Start();
try
{
Assert.IsTrue(started.Wait(5000), "All ten workers must start before any finishes.");
run.Set();
Assert.IsTrue(firstNine.Wait(5000));
Assert.AreEqual(0, Volatile.Read(ref advances), "Nine completions must not release a ten-worker group.");
slow.Set();
}
finally
{
run.Set(); slow.Set();
foreach (var thread in threads) thread.Join(5000);
}
Assert.AreEqual(1, advances);
}
}
[TestMethod]
public void Parallel_Group1OneToTen_Group2One_AllTenCallsOverlap()
{
VerifyProcessingScenario(10, 10, 1);
}
[TestMethod]
public void Serial_Group1One_Group2OneToTen_NoCallsOverlap()
{
VerifyProcessingScenario(10, 1, 10);
}
[TestMethod]
public void Combined_FiveGroup1Boards_TwoGroup2Groups_ParallelWithinSerialBetween()
{
VerifyProcessingScenario(10, 5, 2);
}
[TestMethod]
public void Combined_FourWorkers_TenBoards_ThreeGroups_EveryMeterRunsOnce()
{
VerifyProcessingScenario(4, 10, 3);
}
[TestMethod]
public void Serial_OneWorker_TenGroup1Boards_AllCallsRunOnce()
{
VerifyProcessingScenario(1, 10, 1);
}
// Hardware-free harness using the production assignment and completion helpers.
// Calls are held at a rendezvous, so overlap is proven without timing guesses.
private static void VerifyProcessingScenario(int workers, int boards, int groups)
{
var completion = new GenesisWorkerGroupCompletion(workers);
completion.Begin(0, 1);
var sync = new object();
int currentGroup = 1, active = 0, advances = 0;
bool abort = false;
var calls = new int[groups, boards];
var finished = new int[groups];
var peaks = new int[groups];
var failures = new System.Collections.Generic.List<Exception>();
int parallelism = Math.Min(workers, boards);
var rendezvous = Enumerable.Range(0, groups).Select(_ => new CountdownEvent(parallelism)).ToArray();
var threads = Enumerable.Range(0, workers).Select(worker => new Thread(() =>
{
try
{
for (int group = 1; group <= groups; group++)
{
lock (sync)
{
while (currentGroup != group && !abort)
if (!Monitor.Wait(sync, 10000)) throw new TimeoutException("Group did not advance.");
if (abort) return;
}
bool firstCall = true;
foreach (int board in GenesisWorkerGroupCompletion.BoardIndexes(worker, workers, boards))
{
lock (sync)
{
for (int previous = 0; previous < group - 1; previous++)
Assert.AreEqual(boards, finished[previous], "Next Group 2 started before previous group completed.");
calls[group - 1, board]++;
active++;
peaks[group - 1] = Math.Max(peaks[group - 1], active);
}
if (firstCall)
{
rendezvous[group - 1].Signal();
Assert.IsTrue(rendezvous[group - 1].Wait(10000), "Assigned workers did not enter calls concurrently.");
firstCall = false;
}
lock (sync) { active--; finished[group - 1]++; }
}
if (completion.Complete(0, group, worker))
{
lock (sync)
{
Assert.AreEqual(boards, finished[group - 1]);
Assert.AreEqual(0, active);
advances++;
completion.Begin(0, group + 1);
currentGroup++;
Monitor.PulseAll(sync);
}
}
}
}
catch (Exception error)
{
lock (sync) { failures.Add(error); abort = true; Monitor.PulseAll(sync); }
}
}) { IsBackground = true }).ToArray();
foreach (var thread in threads) thread.Start();
bool allJoined = true;
foreach (var thread in threads) allJoined &= thread.Join(15000);
Assert.IsTrue(allJoined, "Workers did not terminate.");
foreach (var item in rendezvous) item.Dispose();
Assert.AreEqual(0, failures.Count, string.Join("\n", failures.Select(x => x.ToString())));
Assert.AreEqual(groups, advances);
for (int group = 0; group < groups; group++)
{
Assert.AreEqual(parallelism, peaks[group], "Unexpected maximum concurrent calls.");
for (int board = 0; board < boards; board++)
Assert.AreEqual(1, calls[group, board], "Meter was skipped or called more than once.");
}
}
}
}
+189
View File
@@ -165,6 +165,195 @@ namespace TBFTests
// ReSharper restore SqlResolve // 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) private static void Execute(SQLiteConnection connection, string sql)
{ {
using (var command = connection.CreateCommand()) { command.CommandText = sql; command.ExecuteNonQuery(); } using (var command = connection.CreateCommand()) { command.CommandText = sql; command.ExecuteNonQuery(); }
+1 -1
View File
@@ -231,7 +231,7 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
<ItemGroup><Compile Include="GenesisRecoveryTests.cs" /></ItemGroup> <ItemGroup><Compile Include="GenesisRecoveryTests.cs" /><Compile Include="GenesisParallelSchedulingTests.cs" /><Compile Include="Rig\RegisterReaders\GenesisRegReader\GenesisReadDataRegressionTests.cs" /></ItemGroup>
<!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. --> <!-- Project dependencies may copy an older SQLite interop DLL with a newer timestamp. -->
<Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build"> <Target Name="EnsureMatchingSQLiteInterop" AfterTargets="Build">
<Copy SourceFiles="@(SQLiteInteropFiles)" <Copy SourceFiles="@(SQLiteInteropFiles)"