The hang case resumed the controller and sent $X alone. The stream fault raises Alarm 17 (motor fault), which the core treats as a critical event: $X is refused with error:79 until a soft reset, and the reset is what the stream takes as the operator's acknowledgment of the fault (grblHAL 38b450e: the kernel stopped and re-armed, the stale ring cleared, the producer armed again). The drill's first bench run on image 20260902144848 therefore ended in Alarm. The drill now records $X before the reset and requires the error:79 refusal, sends the soft reset, unlocks, requires Idle, requires the ring back at its idle free count (the stale bytes of the interrupted move are gone), and then jogs. The final assertion read the state off the report dict returned by wait_idle as a string; it never ran before because the TIMEOUT test short-circuited it. Bench: motion.deadman PASS on 20260902144848 (kill respawn 1.2 s, hang to underrun 0.21 s, $X -> ALARM:17 error:79, reset + $X -> Idle, ring 33521664 of 33521664, jog Jog -> Idle, restart retook supervision). Catalog consequence: the motion.* implementation hashes move (the suite file changed) and the set re-ran and passed. The failed first run had closed the campaign, so the always-required core ran again in the new one, as the campaign rules require.
forgetest - the ForgeFIRM release acceptance tool
The daemon behind http://<machine>:8090/ on the dev image: runs the
acceptance catalog against the machine, keeps the append-only result log,
decides which results still apply to the image that is running, exports
the release artifact scripts/release.sh gates on, and serves the bench
diagnostics page. The contract - catalog, campaigns, fingerprints,
inheritance, the gate, the coverage rule - is
the Acceptance page of the documentation site.
Run the host tests
cd forgetest
python3 -m unittest discover -s tests -v
Run the daemon on a workstation (against a mock or a manifest file)
FORGETEST_DATA=/tmp/ft FORGETEST_MANIFEST=../tree-manifest.json \
FORGECTRL_URL=http://<machine>:8080 python3 -m forgetest --port 8090
scripts/manifest-from-tree.py produces tree-manifest.json from the recipe
pins; the coverage lint is python3 -m forgetest.coverage --manifest ....
Environment
| Variable | Default | Purpose |
|---|---|---|
FORGETEST_DATA |
/data/forgetest |
results.jsonl, bench.jsonl, token, export/ |
FORGETEST_MANIFEST |
/etc/forgefirm-manifest.json |
the image manifest |
FORGETEST_PORT, FORGETEST_HOST |
8090, 0.0.0.0 | listener |
FORGETEST_BENCH_DIR |
/usr/share/forgetest/bench |
the installed bench scripts |
FORGETEST_BENCH_DATA |
<FORGETEST_DATA>/bench |
passed to bench tools: where they keep their data files (with GF_HOST=127.0.0.1 and the panel token in GF_TOKEN) |
FORGETEST_MARKER |
/run/forgetest.active |
takeover marker |
FORGECTRL_URL, FORGECTRL_TOKEN_FILE |
http://127.0.0.1:8080, /data/forgefirm/panel.token |
forgectrl client |
GF_SYSFS_ROOT |
/sys/glowforge/ |
kernel module sysfs |
GRBL_HOST, GRBL_PORT |
127.0.0.1, 23 | Grbl TCP |
Adding a test
Register it in the subsystem module under forgetest/suite/ with
@test(...): id subsystem.name, kind, hardware, mode (the controller
mode the test needs; the runner switches to it first), covers,
requires, always, steps. The body gets a Context (log, check, fail,
prompt, confirm, instruct, sleep, evidence, forgectrl, sysfs,
grbl, takeover). Return normally for PASS, raise runner.Failed for
FAIL. Then run the unit tests and the coverage lint.