Files
forgefirm/forgetest/tests/test_kernel_suite.py
T
ScottW514 b3efab9c46 forgetest: the latch-unlock gate waits for the safety chain to release
The gate that refuses a latch unlock while the chain may hold HV_ENABLE
up (charge_pump_alive or a pulse engine not idle) ran at the start of
phases B, U and K3 of kernel.fire-line, within a second of the previous
phase's run. A run feeds the charge-pump watchdog every 200 ms and the
one-shot holds ALIVE for 0.45 s after the last feed, so the gate read
alive=1 and refused: the first bench run of the gate (forgefirm
64f552fc; the laser_pgood gate before it was vacuous) failed phase B on
image 20260902144848.

wait_hv_off() polls the chain for up to 3 s before it refuses, logs the
release when it was not immediate and records every wait in the
evidence (hv_release_s). require_hv_off and check_hv_off use it. The
bench scripts that copy the gate (fire_test.py per phase,
gate_a_kernel_drills.py K3 after K2) get the same wait.

Bench: kernel.fire-line PASS on 20260902144848 with the chain released
after 0.41 s at each of the three phase boundaries. Host:
tests/test_kernel_suite.py covers release inside the window, a chain
held past it, and a chain already off.

Catalog consequence: the kernel.* implementation hashes move (the suite
file changed); the kernel set re-ran and passed.
2026-09-02 15:39:24 -04:00

68 lines
1.8 KiB
Python

"""wait_hv_off against a scripted charge-pump readback: a chain that
releases inside the window passes, a chain held past it is the refusal,
and a chain already off costs no wait."""
import time
import unittest
from forgetest.suite import kernel
class Ctx:
def __init__(self):
self.evidence = {}
self.lines = []
def sleep(self, s):
time.sleep(s)
def log(self, fmt, *a):
self.lines.append(fmt % a)
def scripted(alive_for_s):
t0 = time.time()
def rd(path):
if path == "cnc/charge_pump_alive":
return "1" if time.time() - t0 < alive_for_s else "0"
if path == "cnc/state":
return "idle"
return None
return rd
class WaitHvOffTests(unittest.TestCase):
def setUp(self):
self._rd = kernel.rd
def tearDown(self):
kernel.rd = self._rd
def test_release_inside_the_window_passes(self):
kernel.rd = scripted(0.3)
ctx = Ctx()
t0 = time.time()
self.assertIsNone(kernel.wait_hv_off(ctx, timeout_s=2.0))
self.assertGreaterEqual(time.time() - t0, 0.25)
self.assertTrue(any("released" in l for l in ctx.lines))
self.assertGreaterEqual(ctx.evidence["hv_release_s"][0], 0.25)
def test_chain_held_past_the_window_is_the_refusal(self):
kernel.rd = scripted(99)
ctx = Ctx()
why = kernel.wait_hv_off(ctx, timeout_s=0.3)
self.assertIn("charge_pump_alive=1", why)
self.assertTrue(any("still held" in l for l in ctx.lines))
def test_chain_already_off_costs_no_wait(self):
kernel.rd = scripted(0)
ctx = Ctx()
t0 = time.time()
self.assertIsNone(kernel.wait_hv_off(ctx, timeout_s=2.0))
self.assertLess(time.time() - t0, 0.1)
self.assertEqual(ctx.lines, [])
if __name__ == "__main__":
unittest.main()