The catalog is for what needs the machine or an operator. Each test below asserted software rules that a host test in CI already proves, read assertion by assertion next to that host test before it left; the record of that reading is kept with the audit. Safety rules (each approved for deletion by the owner on that reading): - laser.disarm-in-hold: laser_lifecycle_test test_resume_after_grace. - laser.arm-wait-lid: laser_arm_test E-G, laser_lifecycle_test test_lid_open_in_wait. - laser.port-dark: ctlport_test test_port_dark (port jogs, a long jog and its cancel, four injected words, the panel ops: no FIRE tick under an open window with M3 modal). - motion.respawn-gate: super_test A and D. - motion.interlock-cancel-home: the lifecycle loop cases, laser_arm_test G. - motion.lid-policy-hold: laser_lifecycle_test test_lid_policy_hold. - cloud.lid-during-button-wait, cloud.dark-print, cloud.verdict-refuse, cloud.paused-lid-cancel: test_machine_lid_button.py. - cooling.fan-duty-readback: cool_fanwrite_test. - cooling.intake-one-fan: cool_intake_test A-E. - exthost.hold-pause-tier: holdkeep_test, run_test.py, cool_flow_test P, holds_test. Other rules: - homing.cloud-offsets: manual_home_test [own-offsets], test_gfhome_motion; the real session stays in cloud.mode-switch. - exthost.core-range: version_test, install_test. - exthost.operator-destinations: run_test.py, netrules_test, install_test, extpkg_test. - exthost.lifecycle: run_test.py's ext.shutdown read before the stop. - exthost.mcode: the null-sink mcode_test, forgectrl mcode_test, sdk_test, install_test. - motion.soft-limits: z_envelope_test's homed cases, which now assert no step before the alarm, and envelope_test. - cooling_intake.py, extcore.py, extdest.py, extlife.py and extmcode.py go whole; homeoff.py keeps only the camera-home helpers that cloud.mode-switch, setup.check-envelope and their meta-tests use. - The helpers only the removed tests used go with them. - exthost.armed-freeze still runs the cloud suite's dark-print helper, whose latch checks still run; its description says so. It now requires cooling.floor-and-warm-up, the dark print's own prerequisite, in place of the removed cloud.dark-print. - HOST_TESTED: forgectrl src/holds.* (holds_test) and src/mcode.* (mcode_test), whose only catalog home left with these tests. - The meta-tests that replayed the removed tests go: test_cooling_intake_suite.py, the cloud replay cases and their fixture. The one that catches a false PASS stays and runs the dark print as armed-freeze does: a latch open at the button fails. - Text that named a removed test (exthost.motion-job's description and comment, test_baseline's docstring) no longer does. Proof: the coverage lint on a manifest from the recipe pins reports 0 uncovered paths and 0 host-tested row problems over 111 tests (its one failure is setup.check-lens's src/lensfind.* entry, which the forgectrl pin bump clears); pyflakes adds no finding; the gate self-check refuses an empty artifact cleanly; the forgetest host suite passes under Linux (547 tests); the catalog loads with 111 tests after armed-freeze's description edit. Acceptance: the catalog hash moves, so the next image opens a new campaign. Every remaining test in laser.py, motion.py, cloud.py, cooling.py, exthost.py, homeoff.py and bedsize.py moves its fingerprint (their modules' shared text changed); exthost.armed-freeze moves by its requires.
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.