The daemon's supervisor and engine change (forgectrl: controller death as a signal, the homing runner and the kernel before a respawn, the enclosure check before every spawn, the fail tiers ending the controller). This commit carries the catalog tests that hold them. forgetest/forgetest/suite/motion.py: - motion.deadman gains a last phase: SIGKILL of the controller during $H (the web-service homing, run only with cloud mode enabled, the homing mode set and put back by the test). The homing runner must be gone before the respawn, the kernel idle when the new controller starts, no halt needed, and the pulse device held by the daemon and one controller. The head ends wherever the homing was. - motion.respawn-gate (kind operator, the lid through the fixture where one is wired): the controller killed with the lid open. The supervisor safes (latch locked), reports waiting with why naming the lid, starts nothing while the lid stays open, and comes back verified when it closes, without a second motion probe. forgetest/forgetest/suite/laser.py: - laser.armed-kill reads the latch and the kernel state from sysfs every few milliseconds after the SIGKILL: the latch must lock within 300 ms of the kill and the kernel leave running within a second (a death is a signal to the supervisor, not a poll). The emission bound stays at 2.5 s: the witness counts a window. forgetest/forgetest/suite/cooling.py: - cooling.fail-tier-stop (kind operator, one press, no emission): the crash watch's thresholds at their lowest make the head's own move trip the abort generator inside an armed, dark (S0) job. The engine logs the crash signal, the supervisor logs the stop and starts a new controller, the latch is locked, the kernel idle, no emission, the thresholds put back. The watch exists only inside the armed window, so the press is the arm, not a fire. Proof. Host: the forgetest unit tests. Bench reference: motion.deadman passed with the new phase (the runner gone in 0.9 s, the kernel idle at the respawn, holders forgectrl and grblHAL_glowforge), motion.respawn-gate passed (waiting 1.3 s after the kill with "the lid is open", running and verified the moment the lid closed, no probe line), cooling.fail-tier-stop passed (the signal at +0.27 s, the controller pid 21823 to 22273, latch locked, emission 0).
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> 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, /data/forgefirm/panel.token |
forgectrl client (HTTP; the token authorizes writes from the board) |
FORGECTRL_TLS_URL |
https://127.0.0.1 |
forgectrl over HTTPS (self-signed, unverified), for the login test |
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.