From ce73dbb052adf2245ed8046c0d02e789863f830d Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Fri, 16 Oct 2026 23:00:54 -0400 Subject: [PATCH] forgetest: a check run_check stops following at its timeout is aborted before the test fails run_check judged its timeout with ctx.check, which raises, ahead of the abort meant for that case, so the abort never ran: a check still running at the end of its follow was left running behind the FAIL. For setup.focus that is a live burn wizard. Its put-back waits 120 s for the burn to end and then skips the controller restart, and forgectrl's own bound on a live run (1500 s) outlasts the test's 900 s follow. The same driver serves the setup.check-* tests, setup.cloud-header-capture and motion.release. The abort now goes out before the timeout is judged, and the unreachable return after it is gone. The change only ends a check the test started itself: it adds no energy and no motion. Proof: tests/test_setup_dark.py, the Unit tests step of forgetest-ci.yml (python -B -m unittest discover -s tests), 21 tests OK on Windows with python 3.14 and on Ubuntu 24.04 with python 3.12: test_a_check_that_does_not_end_in_time_is_aborted_before_the_test_fails (a lens.travel check that never ends, followed for 0.2 s: Failed, and /wiz/lens.travel/abort posted). test_setup_burns.py (24) and test_setup_suite.py (15) pass. Negative control on a scratch copy: the timeout judged before the abort, as it was, fails the case (only /wiz/lens.travel/start posted). setup.focus run whole against test_setup_burns.py's scripted daemon with a burn that never ends (the follow cut to 0.5 s, the press prompt held open): before, Failed with no abort, the wizard still running and the restart skipped ("the burn is still ending"); after, Failed with laser.focus aborted, the wizard stopped, and the controller stopped and started (200, 200). pyflakes reports no undefined name. Acceptance: the shared text of setup_dark.py moves the fingerprints of its five tests (setup.check-switches, setup.check-airflow, setup.check-motion, setup.check-lens, setup.cloud-header-capture), of setup.focus (setup_burns.py imports it at its top level) and of motion.release (its own function imports run_check); the other 36 do not move (source_sha compared before and after). Against the campaign this series already needs, that adds no run: 850498f moved all 43 once. --- forgetest/forgetest/suite/setup_dark.py | 5 +++-- forgetest/tests/test_setup_dark.py | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/forgetest/forgetest/suite/setup_dark.py b/forgetest/forgetest/suite/setup_dark.py index 42c6f3d..12a9d33 100644 --- a/forgetest/forgetest/suite/setup_dark.py +++ b/forgetest/forgetest/suite/setup_dark.py @@ -132,10 +132,11 @@ def run_check(ctx, wid, on_prompt, timeout_s, on_poll=None): raise ev["elapsed_s"] = round(time.time() - t0, 1) ev["phases"] = phases[-12:] - ctx.check(last is not None, "the %s check did not end within %d s", wid, timeout_s) if last is None: + # A check this test stops following is aborted before the test + # fails: no check runs on behind the FAIL. fc.post("/wiz/%s/abort" % wid) - return {} + ctx.check(last is not None, "the %s check did not end within %d s", wid, timeout_s) ev["log"] = last.get("log", [])[-12:] ev["error"] = last.get("error") ev["result"] = last.get("result") diff --git a/forgetest/tests/test_setup_dark.py b/forgetest/tests/test_setup_dark.py index af70beb..e9bbabe 100644 --- a/forgetest/tests/test_setup_dark.py +++ b/forgetest/tests/test_setup_dark.py @@ -408,6 +408,26 @@ class LensCheckTests(ScriptedCheck): self.assertIn(("/wiz/lens.travel/abort", {}), self.fake.posts) self.assert_put_back() + def test_a_check_that_does_not_end_in_time_is_aborted_before_the_test_fails(self): + # The run never ends: the test stops following it at its timeout, + # aborts it, and fails, so no check runs on behind the FAIL. + from forgetest.runner import Failed + from forgetest.suite import setup_dark + real = self.fake.on_get + + def on_get(path, q): + if path == "/wiz/dark": + return 200, dict(self.dark) # running until aborted + return real(path, q) + self.fake.on_get = on_get + t = catalog.load_suite()["setup.check-lens"] + ctx = Context(Run("test", t.id, t.title), None, t) + with self.assertRaises(Failed) as cm: + setup_dark.run_check(ctx, self.WID, lambda p: None, 0.2) + self.assertIn("did not end within", str(cm.exception)) + self.assertIn(("/wiz/lens.travel/abort", {}), self.fake.posts) + self.assertFalse(self.dark["running"]) + def test_a_controller_that_does_not_restart_fails_the_hand_back(self): # The start refused: the controller stays stopped on the stale # window's heels, and the run says so rather than pass.