From 1a75f61ebe52540aceb5c8452fb8864b57196ed8 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Fri, 7 Aug 2026 05:49:34 -0400 Subject: [PATCH] test(GWC-26): deflake ApplyReconcileBroadcastsAcknowledgeDelta MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Seeding the cache with a live Raise transition raced the first reconcile: when the reconcile snapshot populated the cache first, the still-buffered live Raise was applied — and broadcast — after the test's feed subscriber had registered, so the exactly-one-transition assertion saw two. Seed through a reconcile pass instead (forced by a provider-mode probe, as the acked step already did) so no live transition is ever in flight. Verified: 5 consecutive full alarm-monitor runs green, and the test still fails (timeout) with the ApplyReconcile acked-delta branch removed. --- .../GatewayAlarmMonitorAttachOrderTests.cs | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs index 1b99cc2..48ad14d 100644 --- a/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs +++ b/src/ZB.MOM.WW.MxGateway.Tests/Alarms/GatewayAlarmMonitorAttachOrderTests.cs @@ -110,15 +110,19 @@ public sealed class GatewayAlarmMonitorAttachOrderTests await monitor.StartAsync(cts.Token); await sessions.WaitForSubscribeStartAsync(WaitTimeout); - // Seed the cache with an active (unacked) alarm and keep the reconcile snapshot in - // agreement, so a periodic reconcile pass cannot clear it out from under the test. + // Seed the cache with an active (unacked) alarm through a reconcile rather than a live + // transition: a buffered live transition could still be in flight when the cache first + // shows the alarm, and would then broadcast to the reader below and pollute the + // exactly-one-transition assertion. A provider-mode event forces the reconcile + // immediately, so the test never waits on the periodic timer. sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.Active)); - sessions.EmitEvent(Transition(1, AlarmTransitionKind.Raise)); + sessions.EmitEvent(ProviderModeProbe(1)); await WaitUntilAsync( - () => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference), + () => monitor.CurrentAlarms.Any(alarm => alarm.AlarmFullReference == AlarmReference + && alarm.CurrentState == AlarmConditionState.Active), WaitTimeout); - // Subscribe AFTER the raise: this reader's snapshot carries the alarm, so every + // Subscribe AFTER the seed: this reader's snapshot carries the alarm, so every // transition it observes from here on is a reconcile-derived broadcast. List received = []; TaskCompletionSource snapshotComplete = new(TaskCreationOptions.RunContinuationsAsynchronously); @@ -129,17 +133,7 @@ public sealed class GatewayAlarmMonitorAttachOrderTests // The worker now reports the same alarm acknowledged. A provider-mode event forces an // immediate reconcile pass so the test does not wait on the periodic timer. sessions.SetReconcileSnapshot(Snapshot(AlarmConditionState.ActiveAcked)); - sessions.EmitEvent(new MxEvent - { - Family = MxEventFamily.OnAlarmProviderModeChanged, - WorkerSequence = 2, - OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent - { - Mode = AlarmProviderMode.Alarmmgr, - Reason = "probe", - At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), - }, - }); + sessions.EmitEvent(ProviderModeProbe(2)); AlarmFeedMessage acknowledge = await WaitForAsync( received, @@ -238,6 +232,20 @@ public sealed class GatewayAlarmMonitorAttachOrderTests }, }; + // A no-op provider-mode event. The monitor forces an immediate reconcile after every one, + // which is how these tests drive a reconcile pass without waiting on the periodic timer. + private static MxEvent ProviderModeProbe(ulong sequence) => new() + { + Family = MxEventFamily.OnAlarmProviderModeChanged, + WorkerSequence = sequence, + OnAlarmProviderModeChanged = new OnAlarmProviderModeChangedEvent + { + Mode = AlarmProviderMode.Alarmmgr, + Reason = "probe", + At = Timestamp.FromDateTimeOffset(DateTimeOffset.UtcNow), + }, + }; + private static ActiveAlarmSnapshot Snapshot(AlarmConditionState state) => new() { AlarmFullReference = AlarmReference,