Six checks in five cooling tests read forgectrl's log tail once for the line the engine writes when it acts: the TEC-on line (cooling.tec-drive), the off gate's line and the run-end temperature line (cooling.gate-off), the warm-up release and the floor's off line (cooling.floor-and-warm-up), the critical line's off line (cooling.critical-tier), and the two put-back lines (cooling.fan-duty-readback). The engine writes to the device first and logs after, and the line reaches the file through rsyslog a moment later still: on the bench reference cooling.tec-drive read thermal/tec_on = 1 and then the tail a few milliseconds before "TEC on: coolant 26.3 C over 24.5 C, airflow up" landed, and failed. A tail read also takes a line an earlier run left behind for this run's. Each check now marks forgectrl's log before the action that makes the line (the settings write, the M8, the duty written behind the engine's back) and waits up to 5 s for the line among those written after the mark. Every line was checked against where cool.c writes it: gates_apply for an off gate, the run's end for the temperatures, the warm-up release, fans_verify for a put-back, the TEC policy for the TEC. The helpers are motion's log offset pair, imported inside each test's function as the fire-watch tests already do; the tests that poll the tail already (_tail_wait) are unchanged. Proof: tests/test_cooling_suite.py's fake engine writes its lines to a scratch forgectrl log as well as to the fake tail, and a new case puts an earlier run's off-gate line in the log with the engine writing none this run: it fails as it must, and against the checks before this it passes. 25 cooling cases pass; forgetest 500 OK. On the bench reference, image 20260925183749 with this file mounted: cooling.tec-drive, cooling.gate-off, cooling.floor-and-warm-up, cooling.critical-tier and cooling.fan-duty-readback PASS, each baseline clean. Acceptance: the five tests are the change; their fingerprints move and no other test's does.
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.