test(runtime): operator restart re-subscribes the respawned child (archreview 03/S16, U6)
Seen RED against a T3-reverted build: all 4 failed, Created[1].SubscribeCount == 0 on the headline case. 20x isolation run green after the fix was restored.
This commit is contained in:
@@ -537,3 +537,61 @@ added: `IReadOnlyDictionary<string, string>? tagConfigByTag = null`. The seeder
|
||||
alarm-bearing raw tag that `Operator_restart_re_subscribes_alarm_refs_too` (a test the plan itself
|
||||
specifies) requires. It is trailing and optional, so it is the same source-compatible shape as
|
||||
`enabled`.
|
||||
|
||||
### T5 — `DriverHostActorRestartTests` (operator-restart falsification, 03/U6's first half)
|
||||
|
||||
**RED observed against a T3-reverted build** (the `SendDesiredSubscriptions(child, …)` call in
|
||||
`SpawnChild` commented out, everything else on the branch): **all 4 FAILED, 0 passed**, each at the
|
||||
bar the plan predicts —
|
||||
|
||||
```
|
||||
Operator_restart_re_subscribes_the_respawned_child [FAIL]
|
||||
factory.Created[1].SubscribeCount should be greater than or equal to 1 but was 0
|
||||
Operator_restart_re_subscribes_alarm_refs_too [FAIL]
|
||||
factory.Created[1].SubscribeAlarmsCount should be greater than or equal to 1 but was 0
|
||||
Restart_of_a_driver_with_an_empty_value_set_still_sends_the_set [FAIL]
|
||||
respawned.SubscribeAlarmsCount should be greater than or equal to 1 but was 0
|
||||
A_child_spawned_by_a_fresh_deploy_ends_with_the_NEW_refs [FAIL]
|
||||
factory.Created[2].LastSubscribedRefs should be ["Plant/Modbus/dev1/temp"] but was null
|
||||
```
|
||||
|
||||
The call was then restored; **4/4 pass**, and the class was run **20× in isolation, 20/20 green** (#500
|
||||
discipline for this assembly).
|
||||
|
||||
**Two deviations, both found by running the tests rather than by reading:**
|
||||
|
||||
1. ⚠️ **The plan's claim that the transient spawn-time set "is never acted on" is FALSE, and the
|
||||
production comment that repeated it has been corrected.** The plan reasons that the child is still
|
||||
`Connecting` when the stale set arrives, so it is only stored. But the child runs **concurrently**
|
||||
with the rest of `HandleArtifactForApply` — `Context.ActorOf` returns and the child starts
|
||||
processing while the host is still building `refsByDriver` — so a driver whose `InitializeAsync`
|
||||
completes instantly reaches Connected and subscribes the stale set *before* the bulk push arrives.
|
||||
Measured directly: `SubscribeCount == 2` on the respawned child, first with revision A's ref and
|
||||
then with B's. What Akka's per-pair ordering does guarantee is **convergence**, and
|
||||
`HandleSubscribeAsync` drops the prior subscription before establishing the new one, so the cost is
|
||||
one extra subscribe round-trip on a *respawning* deploy — a real device driver, whose initialise
|
||||
opens a socket, rarely wins that race. Accepted rather than "fixed": withholding the spawn-time send
|
||||
is the 03/S16 defect, and the operator-Restart and supervision paths have no bulk push coming at
|
||||
all. The `SpawnChild` comment now states this instead of the plan's claim, and the test asserts
|
||||
convergence (the plan's own stated acceptance — "the child's live subscription carries the NEW
|
||||
refs") rather than pinning the race.
|
||||
2. **`A_child_spawned_by_a_fresh_deploy_ends_with_the_NEW_refs` had to be re-shaped.** The plan
|
||||
describes revision B as one "which respawns nothing but re-pushes". It respawns: the raw tags are
|
||||
folded into the merged `DriverConfig`, so a tag-set edit changes the `DriverInstanceSpec` and trips
|
||||
the #516 config respawn. That is the *stronger* arrangement for this test's subject (a brand-new
|
||||
child spawned while the authority still holds A), so the test keeps the same name and asserts the
|
||||
respawned child converges on B — plus a subsequent operator Restart, which pins that the authority
|
||||
is repopulated **whole** per apply rather than accumulated.
|
||||
3. **`Restart_of_a_driver_with_no_authored_tags_sends_an_empty_set_not_nothing` was renamed
|
||||
`Restart_of_a_driver_with_an_empty_value_set_still_sends_the_set`, with a real positive control.**
|
||||
The seeder derives `DriverInstances` from the raw-tag rows, so a driver with *zero* tags is not
|
||||
expressible, and — more importantly — an all-empty set has **no driver-side observable at all**
|
||||
(the child stores it and, having no handle, does nothing), which would have made the test vacuous:
|
||||
"it did not subscribe" passes equally against a build that sends the respawned child nothing, i.e.
|
||||
against the defect itself. The subject is therefore a driver whose only raw tag is alarm-bearing:
|
||||
its value half is genuinely empty while its alarm half is not, and **both halves ride in one
|
||||
message** — so `SubscribeAlarmsCount >= 1` proves the message ARRIVED and `SubscribeCount == 0`
|
||||
then proves the empty value half was honoured. The falsification run above confirms the control
|
||||
works: with T3 reverted this test fails on the *positive control*, not on the absence.
|
||||
**Not covered, and worth a reviewer's eye:** the `_desiredByDriver` union's `_children.Keys` arm
|
||||
(a running child absent from BOTH ref maps) is not reachable through this seeder and is untested.
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
{
|
||||
"id": "T5",
|
||||
"subject": "DriverHostActorRestartTests: operator restart re-subscribes the respawned child (falsifiable; 03/S16, U6)",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"blockedBy": [
|
||||
"T3",
|
||||
"T4"
|
||||
|
||||
Reference in New Issue
Block a user