From a0c8b63c46625ab586741ed7173e264278705a5b Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 9 Nov 2026 18:12:17 -0500 Subject: [PATCH] test(opcua-client): replace fixed delays in the ConditionRefresh wire test with ordering barriers (lmxopcua#764) The 1500 ms pre-control and 500 ms trailing sleeps are gone. ConditionRefresh completes server-side before SubscribeAlarmsAsync returns, so refresh events queue ahead of the control Acknowledge; a trailing message-change barrier event is projected and awaited (bounded 10 s poll) so any stray event must arrive before it. The assertion is now the exact sequence [Acknowledge, Unspecified], no weaker than before. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV --- .../OpcUaClientDriverWireTests.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverWireTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverWireTests.cs index 054674da5..cf41ec863 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverWireTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests/OpcUaClientDriverWireTests.cs @@ -310,19 +310,27 @@ public sealed class OpcUaClientDriverWireTests var before = Seen().Length; await drv.UnsubscribeAlarmsAsync(first, ct); await drv.SubscribeAlarmsAsync([AlarmRef], ct); - await Task.Delay(1500, ct); // let the second refresh's events land before the control transition + // No sleep: ConditionRefresh has completed server-side when SubscribeAlarmsAsync returns, so its events + // are already queued on the subscription AHEAD of anything projected below. A re-delivery would + // therefore surface before the control events and be caught by the exact-sequence assertion. // Positive control: an upstream acknowledge arrives as exactly one Acknowledge, after which nothing else. rig.Project(native: true, new AlarmConditionSnapshot(Active: true, Acknowledged: true, Confirmed: true, Enabled: true, AlarmShelvingKind.Unshelved, 800, "already hot")); await RecordedEvents.WaitUntilAsync(() => Seen().Any(e => e.Kind == AlarmTransitionKind.Acknowledge)); - await Task.Delay(500, ct); + // Trailing barrier instead of a quiet period: a further, distinguishable upstream change (new message) is + // projected after the Acknowledge and awaited. Events reach the driver in queue order, so anything stray + // that was going to follow the Acknowledge must arrive before this marker and is caught below. + rig.Project(native: true, new AlarmConditionSnapshot(Active: true, Acknowledged: true, Confirmed: true, + Enabled: true, AlarmShelvingKind.Unshelved, 800, "barrier")); + await RecordedEvents.WaitUntilAsync(() => Seen().Any(e => e.Message == "barrier")); // #728: record each event's full shape on failure, not only its Kind — the extra Unspecified that failed CI // could be a changed message/severity, a non-condition event or a quality-only annotation, and the Kind alone // cannot tell them apart. var after = Seen().Skip(before).ToArray(); - after.Select(e => e.Kind).ShouldBe([AlarmTransitionKind.Acknowledge], - "the second refresh re-delivered an unchanged state and must have emitted nothing; events after the " + + after.Select(e => e.Kind).ShouldBe([AlarmTransitionKind.Acknowledge, AlarmTransitionKind.Unspecified], + "the second refresh re-delivered an unchanged state and must have emitted nothing (only the Acknowledge " + + "and the trailing barrier's message change are expected); events after the " + "resubscribe: " + string.Join(" | ", after.Select(Describe))); }