forgetest: the hand-back reads an engine hold again past the engine's next tick

The cooling engine publishes its state once a tick (1 Hz). A /cool/status
read inside the tick after a run ended still shows the run: while a
diagnostic owns the hardware every tick publishes phase "diag" with the
hold set, and a fail tier's hold stands until the tick that ends its
session. The baseline took one read, and by its rule an arm or a hold is
the run's doing, so a test that finished inside that second failed its
hand-back on a hold the engine's next tick cleared.

Seen on the bench reference twice. cooling.aa-offset-calibrate in campaign
c-20260919215024-c402: the diagnostic reported done at 22:07:14 with its
offset measured (15.7 counts, spread 0.7), and the hand-back at 22:07:15
read "cool=diag/armed=False/hold=True ... -> waited" and failed the run;
the test had passed on five images before, the last one earlier the same
day. cooling.fail-tier-stop in c-20260919202934-3d3a, the same way on the
crash fault's hold (0ceb4ab made that test wait for its own fault; this is
the general case).

The cooling check moves into Baseline._cool_side. An arm or a hold is read
again for up to COOL_PUBLISH_S (2.5 s: two ticks and a margin) before it is
called the run's doing. One the next tick cleared is logged as the engine's
last word on the run and judged on what the engine reads then: idle is
clean, a cooldown phase is waited out as the engine's own post-job work. A
hold a run did leave stands for a job that is never coming back, so it is
still there after the tick and is recorded, stood down and failed exactly
as before; a hold that clears only later is still a leftover ("waited").

Proven: tests/test_baseline.py CoolPublishTests - a diagnostic's hold the
next tick clears, a fail tier's hold clearing into the smoke phase, a hold
that outlives the tick (recorded, machine stood down), a hold that clears
only later (recorded as waited), an idle engine and a silent daemon; the
baseline, queue, operator, mode, responsiveness and server host tests
pass under Linux. baseline.py is not a suite module, so no test's
fingerprint moves with it.
This commit is contained in:
ScottW514
2026-09-19 18:16:04 -04:00
parent dc7170d0d1
commit f306c9983f
2 changed files with 128 additions and 28 deletions
+75
View File
@@ -437,6 +437,81 @@ class TransientNotLeftoverTests(BaselineTests):
self.assertEqual(items[0].found, "1014")
class CoolPublishTests(unittest.TestCase):
"""The cooling engine publishes once a tick. A status read inside the
tick after a run ended is the engine's last word on the run, not the
machine as the run left it. Found on the bench reference: a diagnostic
that finished clean (cooling.aa-offset-calibrate) and a fail tier that
did its work (cooling.fail-tier-stop) both failed their hand-back on a
hold the engine's next tick cleared."""
IDLE = {"phase": "idle", "armed": False, "hold": False}
def setUp(self):
self.lines = []
def bl(self, statuses):
"""A baseline whose /cool/status answers play in order (the last
one repeats) and whose waits count reads, not seconds: three for
the engine's next tick, as the real wait makes, and enough for the
cooldown to let a slow script run out."""
b = baseline.Baseline(self.lines.append)
seq = list(statuses)
b.fc_get = lambda path: (200, dict(seq.pop(0) if len(seq) > 1 else seq[0]))
def wait(what, pred, timeout):
for i in range(3 if what == "cool publish" else 50):
if pred():
return float(i)
return None
b._wait = wait
b.stood_down = []
b.stand_down = b.stood_down.append
return b
def test_a_diagnostic_hold_the_next_tick_clears_is_not_a_leftover(self):
left = []
b = self.bl([{"phase": "diag", "armed": False, "hold": True}, self.IDLE])
b._cool_side(left)
self.assertEqual(left, [])
self.assertTrue(any("last word on the run" in ln for ln in self.lines), self.lines)
def test_a_fail_tier_hold_clears_into_the_engines_own_post_job_phase(self):
left = []
b = self.bl([{"phase": "run", "armed": False, "hold": True},
{"phase": "smoke", "armed": False, "hold": False},
{"phase": "smoke", "armed": False, "hold": False}, self.IDLE])
b._cool_side(left)
self.assertEqual(left, [])
self.assertTrue(any("not a leftover" in ln for ln in self.lines), self.lines)
def test_a_hold_that_outlives_the_tick_is_the_runs_doing(self):
left = []
b = self.bl([{"phase": "run", "armed": True, "hold": True}])
b._cool_side(left)
self.assertEqual([x.item for x in left], ["cool"])
self.assertEqual(left[0].found, "run/armed=True/hold=True")
self.assertTrue(left[0].action.startswith("failed: still"), left[0].action)
self.assertEqual(len(b.stood_down), 1)
def test_a_hold_that_clears_only_later_is_still_recorded(self):
left = []
held = {"phase": "run", "armed": False, "hold": True}
b = self.bl([held] * 10 + [self.IDLE])
b._cool_side(left)
self.assertEqual([x.item for x in left], ["cool"])
self.assertEqual(left[0].action, "waited")
self.assertEqual(b.stood_down, [])
def test_an_idle_engine_and_a_silent_daemon_leave_nothing(self):
left = []
self.bl([self.IDLE])._cool_side(left)
b = baseline.Baseline(self.lines.append)
b.fc_get = lambda path: (None, None)
b._cool_side(left)
self.assertEqual(left, [])
class CloudOwnedSysfsTests(unittest.TestCase):
"""In cloud mode the cloud client configures the machine from the
pulse header it is playing, the lens included: z_mode comes from