From c23dedd89a31bc988b3133e9cb26e4a694ac280b Mon Sep 17 00:00:00 2001 From: ScottW514 Date: Mon, 17 Aug 2026 08:43:33 -0400 Subject: [PATCH] laser.armed-kill: wait for the restarted controller's Grbl port before the second burn /mode reporting the controller running means the supervisor has spawned it, not that the listener is bound. Connecting straight away would fail the test on that race - after an arm press and a burn - instead of on the behavior it is about. --- forgetest/forgetest/suite/laser.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/forgetest/forgetest/suite/laser.py b/forgetest/forgetest/suite/laser.py index 726b027..dece819 100644 --- a/forgetest/forgetest/suite/laser.py +++ b/forgetest/forgetest/suite/laser.py @@ -98,6 +98,22 @@ def arm_and_fire(ctx, g, room="40 mm +X and +Y", job=None, timeout=240): raise Failed("no emission seen within %d s (arm refused, or no button press)" % timeout) +def wait_grbl_port(ctx, timeout=30): + """The controller's Grbl listener is accepting again. /mode reporting the + process running is not the same thing: the supervisor has spawned it, but + the socket may not be bound yet, and a bare connect would fail the test on + a race rather than on the behavior it is about.""" + end = time.time() + timeout + while time.time() < end: + ctx.checkpoint() + try: + with ctx.grbl(): + return True + except OSError: + time.sleep(1) + return False + + def kill_trail(ctx, t0, seconds=5.0): """Sample emission / kernel state / armed for `seconds` after a kill.""" trail = [] @@ -383,6 +399,7 @@ def armed_kill(ctx): ctx.check(st == 200 and isinstance(m0, dict) and m0.get("controller") == "running", "controller not running before the kill: %s", m0) pid = m0.get("pid") + ctx.check(wait_grbl_port(ctx), "the restarted controller never accepted a Grbl connection") with ctx.grbl() as g, LiveJob(ctx, g): prepare(ctx, g) smp = arm_and_fire(ctx, g)