From 0d367590dbae5c60697d4250945f4ff730163794 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 22 Oct 2026 13:47:34 -0400 Subject: [PATCH] test(galaxy): isolate process-wide telemetry tests; bounded stream-cancel wait GalaxyTelemetryTests (ActivityListener) and GatewayGalaxyDataWriterFailClosedTests (MeterListener, filtered only by meter and instrument name) observe every Galaxy span or measurement in the process. They now run in a collection with parallelization disabled (F-FU6), because a parallel driver test, or its background probe seed, polluted their counts. The stream-cancel assertion in GalaxyDriverShutdownTests goes back to a bounded wait: the instantaneous IsCompleted check introduced in round 1 was racy. Refs #735 Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT --- .../ProcessWideTelemetryCollection.cs | 17 +++++++++++++++++ .../Runtime/GalaxyDriverShutdownTests.cs | 7 ++++--- .../Runtime/GalaxyTelemetryTests.cs | 1 + .../GatewayGalaxyDataWriterFailClosedTests.cs | 1 + 4 files changed, 23 insertions(+), 3 deletions(-) create mode 100644 tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ProcessWideTelemetryCollection.cs diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ProcessWideTelemetryCollection.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ProcessWideTelemetryCollection.cs new file mode 100644 index 00000000..9d2f16fe --- /dev/null +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/ProcessWideTelemetryCollection.cs @@ -0,0 +1,17 @@ +using Xunit; + +namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests; + +/// +/// Classes that assert on process-wide telemetry (an ActivityListener on the Galaxy +/// ActivitySource, a MeterListener on the Galaxy meter) run with parallelization disabled: +/// those listeners see every Galaxy span / measurement in the process, so any parallel class that +/// drives a traced subscriber or the data writer — including a driver's background probe seed that +/// outlives its test — pollutes their counts (F-FU6, observed flaky in lane F fix round 2). +/// +[CollectionDefinition(Name, DisableParallelization = true)] +public sealed class ProcessWideTelemetryCollection +{ + /// The collection name. + public const string Name = "Process-wide Galaxy telemetry"; +} diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyDriverShutdownTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyDriverShutdownTests.cs index 10ef4ff3..cdb2ec69 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyDriverShutdownTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyDriverShutdownTests.cs @@ -179,9 +179,10 @@ public sealed class GalaxyDriverShutdownTests await driver.ShutdownAsync(CancellationToken.None); - // No wait: the pump's Cancel runs the stream's registration synchronously and ShutdownAsync - // awaited the pump's dispose, so the signal is either already set or the stream was never cancelled. - sub.StreamCancelled.Task.IsCompleted.ShouldBeTrue("ShutdownAsync must cancel the StreamEvents stream"); + // Bounded wait, not an instantaneous IsCompleted check: the pump's dispose does not promise the + // enumerator's cancellation registration has run by the time it returns (observed flaky). + (await Task.WhenAny(sub.StreamCancelled.Task, Task.Delay(WaitMs))).ShouldBe(sub.StreamCancelled.Task, + "ShutdownAsync must cancel the StreamEvents stream"); driver.HasEventPumpForTests.ShouldBeFalse(); driver.TrackedSubscriptionCountForTests.ShouldBe(0, "handles died with the session; the host re-subscribes after re-init"); driver.GetHealth().State.ShouldBe(DriverState.Unknown); diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyTelemetryTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyTelemetryTests.cs index bfef6df2..6b311f9f 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyTelemetryTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GalaxyTelemetryTests.cs @@ -14,6 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime; /// rather than asserting on internal state, so the /// tests double as documentation of the listener-side contract. /// +[Collection(ProcessWideTelemetryCollection.Name)] public sealed class GalaxyTelemetryTests { /// Subscribes an ActivityListener for the test, captures each spawned activity. diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyDataWriterFailClosedTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyDataWriterFailClosedTests.cs index 67a01d77..83d41dea 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyDataWriterFailClosedTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests/Runtime/GatewayGalaxyDataWriterFailClosedTests.cs @@ -13,6 +13,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime; /// of leaving a phantom-Good node. These tests drive the real write pipeline through the internal /// seam (the SDK session types are sealed + internal-ctor and cannot be faked). /// +[Collection(ProcessWideTelemetryCollection.Name)] public sealed class GatewayGalaxyDataWriterFailClosedTests { private static GatewayGalaxyDataWriter NewWriter()