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
This commit is contained in:
Joseph Doherty
2026-10-22 13:47:34 -04:00
parent 3e60a925dc
commit 0d367590db
4 changed files with 23 additions and 3 deletions
@@ -0,0 +1,17 @@
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests;
/// <summary>
/// Classes that assert on process-wide telemetry (an <c>ActivityListener</c> on the Galaxy
/// ActivitySource, a <c>MeterListener</c> 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).
/// </summary>
[CollectionDefinition(Name, DisableParallelization = true)]
public sealed class ProcessWideTelemetryCollection
{
/// <summary>The collection name.</summary>
public const string Name = "Process-wide Galaxy telemetry";
}
@@ -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);
@@ -14,6 +14,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
/// <see cref="ActivityListener"/> rather than asserting on internal state, so the
/// tests double as documentation of the listener-side contract.
/// </summary>
[Collection(ProcessWideTelemetryCollection.Name)]
public sealed class GalaxyTelemetryTests
{
/// <summary>Subscribes an ActivityListener for the test, captures each spawned activity.</summary>
@@ -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
/// <see cref="IMxWriteOps"/> seam (the SDK session types are sealed + internal-ctor and cannot be faked).
/// </summary>
[Collection(ProcessWideTelemetryCollection.Name)]
public sealed class GatewayGalaxyDataWriterFailClosedTests
{
private static GatewayGalaxyDataWriter NewWriter()