test(dcl): stabilize flaky DCL002 crash/restart subscription-preservation with condition-based wait (#234)

This commit is contained in:
Joseph Doherty
2026-06-19 00:33:48 -04:00
parent ddafc5c811
commit 7674b37a24
@@ -76,7 +76,7 @@ public class DataConnectionManagerActorTests : TestKit
} }
[Fact] [Fact]
public async Task DCL002_ConnectionActorCrash_PreservesSubscriptionState() public void DCL002_ConnectionActorCrash_PreservesSubscriptionState()
{ {
// Regression test for DataConnectionLayer-002. The supervisor used // Regression test for DataConnectionLayer-002. The supervisor used
// Directive.Restart, which discards the connection actor's in-memory // Directive.Restart, which discards the connection actor's in-memory
@@ -103,7 +103,12 @@ public class DataConnectionManagerActorTests : TestKit
new DataConnectionManagerActor(_mockFactory, _options, _mockHealthCollector))); new DataConnectionManagerActor(_mockFactory, _options, _mockHealthCollector)));
manager.Tell(new CreateConnectionCommand("conn1", "OpcUa", new Dictionary<string, string>(), null, 3)); manager.Tell(new CreateConnectionCommand("conn1", "OpcUa", new Dictionary<string, string>(), null, 3));
await Task.Delay(300); // connection actor reaches Connected // Wait until the connection actor has called ConnectAsync (i.e. reached Connected)
// rather than sleeping a fixed 300 ms that can expire before the async connect
// completes under CPU contention.
AwaitCondition(
() => mockAdapter.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "ConnectAsync"),
TimeSpan.FromSeconds(5));
// Register a subscription. // Register a subscription.
manager.Tell(new SubscribeTagsRequest("c1", "inst1", "conn1", ["tag1"], DateTimeOffset.UtcNow)); manager.Tell(new SubscribeTagsRequest("c1", "inst1", "conn1", ["tag1"], DateTimeOffset.UtcNow));
@@ -111,7 +116,13 @@ public class DataConnectionManagerActorTests : TestKit
// Crash the connection actor via a synchronously-throwing write. // Crash the connection actor via a synchronously-throwing write.
manager.Tell(new WriteTagRequest("c2", "conn1", "tag1", 42, DateTimeOffset.UtcNow)); manager.Tell(new WriteTagRequest("c2", "conn1", "tag1", 42, DateTimeOffset.UtcNow));
await Task.Delay(300); // supervisor handles the failure // Wait until WriteAsync was invoked, proving the WriteTagRequest was fully
// processed by the connection actor (and the supervisor's Resume directive
// applied) before we query health. A fixed sleep can expire before the
// actor processes the message under CPU load.
AwaitCondition(
() => mockAdapter.ReceivedCalls().Any(c => c.GetMethodInfo().Name == "WriteAsync"),
TimeSpan.FromSeconds(5));
// After the crash the subscription state must survive: the health report // After the crash the subscription state must survive: the health report
// still shows the subscribed/resolved tag. With Restart it would be 0. // still shows the subscribed/resolved tag. With Restart it would be 0.