fix(test): give the three absence assertions a positive control (#501)
`AwaitAssert` returns on its FIRST success, so an assertion that is already true at t=0 passes instantly and spends none of its duration. All three sites asserted an absence against a collection that starts empty, so none of them ever gave the handler the time their comments claimed. Replaced with mailbox-ordering positive controls rather than sleeps: - `RouteNativeAlarmAck_unknown_node_is_dropped` — follow the unmapped ack with a MAPPED one. The host forwards via `Tell` and the child handles `RouteAlarmAck` with `ReceiveAsync` (which suspends its own mailbox), so once the control reaches the driver the unmapped one has provably been handled. - `RouteNativeAlarmAck_on_non_primary_is_dropped` — the role itself is the control: return the node to Primary and re-send. Comments (`gated` / `control`) are the discriminator, so a leaked ack is visible rather than merged into a count. - `PeerProbeSupervisorTests.Single_node_snapshot_spawns_no_children` — a FOURTH site the issue did not list. It says the other `ChildCount.ShouldBe(0)` waits are each preceded by a `ShouldBe(1)`; that is true of three of the four, but not this one, which asserts the initial state. Now followed by a two-node snapshot: the count reaching 1 proves the single-node snapshot was processed, and a local-node child would have made it 2. The issue warned these may turn red, which would be a real defect surfacing. They did not — the routing is correct. Proven by breaking it deliberately instead: disabling the Primary gate reddens the Secondary test, and routing unmapped ids to an arbitrary mapping reddens the unknown-node test. Under the old form both breaks passed.
This commit is contained in:
+31
-5
@@ -86,8 +86,19 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest
|
||||
|
||||
actor.Tell(new DriverHostActor.RouteNativeAlarmAck("Plant/gw/dev1/does-not-exist", "cmt", "alice"));
|
||||
|
||||
// Give the (fire-and-forget) handler time to run; the unmapped node must produce no ack.
|
||||
AwaitAssert(() => recorder.Acks.ShouldBeEmpty(), duration: TimeSpan.FromMilliseconds(800));
|
||||
// POSITIVE CONTROL, not a sleep (#501). `Acks` is empty at t=0, so any assertion made before the
|
||||
// host has processed the message above passes without testing anything — and AwaitAssert returns on
|
||||
// its FIRST success, so it spends none of its duration and is the wrong tool for an absence.
|
||||
//
|
||||
// Instead send a MAPPED ack afterwards. The host processes its mailbox in order and forwards to the
|
||||
// child via Tell, and the child handles RouteAlarmAck with ReceiveAsync (which suspends its own
|
||||
// mailbox), so by the time this one reaches the driver the unmapped one has provably already been
|
||||
// handled. Whatever `Acks` holds then is the complete answer.
|
||||
actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "control", "alice"));
|
||||
|
||||
AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout);
|
||||
recorder.Acks.Count.ShouldBe(1,
|
||||
"only the control ack may reach the driver — the unmapped condition NodeId must have been dropped");
|
||||
}
|
||||
|
||||
/// <summary>On a SECONDARY node the ack is gated off (same primary gate as the inbound write path): the
|
||||
@@ -110,10 +121,25 @@ public sealed class DriverHostActorNativeAlarmAckRoutingTests : RuntimeActorTest
|
||||
},
|
||||
CorrelationId.NewId()));
|
||||
|
||||
actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "cmt", "alice"));
|
||||
actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "gated", "alice"));
|
||||
|
||||
// No ack reached the driver — the gate short-circuited before the inverse-map lookup.
|
||||
AwaitAssert(() => recorder.Acks.ShouldBeEmpty(), duration: TimeSpan.FromMilliseconds(800));
|
||||
// POSITIVE CONTROL (#501) — same reasoning as the unmapped-node test, with the role itself as the
|
||||
// control: return the node to PRIMARY and re-send the SAME mapped ack. Mailbox ordering means that
|
||||
// once this one reaches the driver, the Secondary-gated one has already been processed. The
|
||||
// discriminator is the comment, so a leaked ack is visible rather than merged into the count.
|
||||
actor.Tell(new RedundancyStateChanged(
|
||||
new[]
|
||||
{
|
||||
new NodeRedundancyState(TestNode, RedundancyRole.Primary,
|
||||
IsClusterLeader: true, IsDriverPrimary: true, AsOfUtc: DateTime.UtcNow),
|
||||
},
|
||||
CorrelationId.NewId()));
|
||||
actor.Tell(new DriverHostActor.RouteNativeAlarmAck(AlarmRawPath, "control", "alice"));
|
||||
|
||||
AwaitAssert(() => recorder.Acks.ShouldContain(a => a.Comment == "control"), duration: Timeout);
|
||||
recorder.Acks.ShouldNotContain(a => a.Comment == "gated",
|
||||
"the Primary gate must have dropped the ack sent while this node was Secondary");
|
||||
recorder.Acks.Count.ShouldBe(1);
|
||||
}
|
||||
|
||||
/// <summary>Spawns the host with the recording alarm-driver factory, dispatches the deployment, and waits
|
||||
|
||||
Reference in New Issue
Block a user