Files
forgefirm/forgetest
ScottW514 d5369630d9 forgetest: motion.deadman recovers the hung controller with a reset, then the unlock
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.
2026-09-02 15:55:08 -04:00
..

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.