forgetest: presence is proved once per test, not once per ready gate

The ready gate lives inside the arm-and-fire helper, and the kill drill
calls that helper twice, once for the expected stop and once for the
SIGKILL. So the presence gate asked the operator for a second press part
way through a test they had already proved themselves present for, with
the actuator standing by holding the presses. It is the only test in the
catalog with two ready gates.

The second gate now returns at once. Its setup line still goes up,
because the second half may want the scrap moved, but there is no press
to make.
This commit is contained in:
ScottW514
2026-09-03 18:08:16 -04:00
parent 2f330715f3
commit d6f648b539
2 changed files with 41 additions and 0 deletions
+11
View File
@@ -293,6 +293,17 @@ class Context:
self.log("READY (fixture performs the step): %s", text) self.log("READY (fixture performs the step): %s", text)
return return
# Presence is proved once per test, not once per gate. A test with
# two armed halves (the kill drill stops the controller and does it
# again) reaches this twice, and asking a second time is friction
# with nothing behind it: the operator proved presence a moment ago
# and the actuator is already making the presses. The setup line
# still goes up, because the second half may want the scrap moved.
if self.run.fixture_takeover:
self.notice(text)
self.log("READY: presence already proved this test; the bench has the presses")
return
fixture = getattr(self.runner, "fixture", None) if self.runner is not None else None fixture = getattr(self.runner, "fixture", None) if self.runner is not None else None
if fixture is not None and fixture.covers("button"): if fixture is not None and fixture.covers("button"):
self.notice(text + " Then press the button on the machine to start.") self.notice(text + " Then press the button on the machine to start.")
+30
View File
@@ -536,6 +536,36 @@ class RoutingTests(unittest.TestCase):
self.assertEqual(rec[0]["by"], "operator") self.assertEqual(rec[0]["by"], "operator")
self.assertTrue(any("press the button on the machine" in ln.lower() for ln in run.lines)) self.assertTrue(any("press the button on the machine" in ln.lower() for ln in run.lines))
def test_presence_is_proved_once_per_test_not_once_per_gate(self):
"""A test with two armed halves reaches the ready gate twice. The
second one must not ask for another press: the operator proved
presence a moment earlier and the actuator has the presses. The
setup line still goes up, because the second half may want the
scrap moved."""
run = Run("test", "r.live", "r.live")
ctx = Context(run, self.runner, self.reg["r.live"])
self.runner.probe_fixture(force=True)
asked = []
run.ask = lambda q, o: asked.append(q)
self.press_button(False)
threading.Timer(0.3, self.press_button, args=(True,)).start()
threading.Timer(0.8, self.press_button, args=(False,)).start()
ctx.ready("First half.")
self.assertTrue(run.fixture_takeover)
presses = [r for r in run.evidence["actions"] if r["state"] == "presence"]
self.assertEqual(len(presses), 1)
# the second gate: returns at once, no new press, notice still shown
seen = []
run.set_notice = lambda text: seen.append(text)
t0 = time.time()
ctx.ready("Second half. Move the scrap.")
self.assertLess(time.time() - t0, 1.0)
self.assertEqual(asked, [])
self.assertEqual(len([r for r in run.evidence["actions"] if r["state"] == "presence"]), 1)
self.assertIn("Second half. Move the scrap.", seen)
self.assertTrue(any("presence already proved" in ln for ln in run.lines))
def test_the_presence_press_hands_the_arm_press_to_the_actuator(self): def test_the_presence_press_hands_the_arm_press_to_the_actuator(self):
"""The bench's standing opt-in is not needed once the operator has """The bench's standing opt-in is not needed once the operator has
proved presence: that press is what the opt-in existed to proved presence: that press is what the opt-in existed to