Files
forgefirm/forgetest
ScottW514 675fc11cb1 forgetest: events.button-telemetry and exthost.lifecycle
Two new tests, each in its own module so that no other test's
fingerprint moves.

events.button-telemetry (suite/evmore.py): a stream opened at
forgectrl's read-only listener, from a loopback source of its own, gets
a telemetry.tick within 14 s with exactly its fields (phase, verdict,
fire_ok, down_c, up_c, state, lid). With the machine idle and nothing
waiting for the button, one press (the fixture's, or the operator's)
arrives as pressed true and then false, in that order. That a press the
machine is waiting for is no event, and the update and setup edges, are
events_test's.

exthost.lifecycle (suite/extlife.py): a package that follows its feed is
running when extensions are turned off. It reads ext.shutdown with the
host's reason and writes it to its data directory before its process
ends, the host stops it a second later, and nothing is frozen in that
second. Its covers name forgeext's run, machine (the armed question
asked during the grace), evfeed, and api.

Proof: on the bench reference, with forgectrl a9c45fc and forgeext
dbb00e5 bind-mounted, both PASS. exthost.lifecycle FAILed on the host
before its grace fix (the service frozen for want of an armed reading)
and PASSed after it. forgetest's unit tests pass (451), and the coverage
lint passes with --enforce.
2026-09-23 00:39:13 -04:00
..
2026-09-18 12:14:22 -04:00
2026-09-18 12:14:22 -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> 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.