The read-back judge of the soft-reset check compared MPos X in steps
with the kernel's X count to one step, and the Y and Z judges allowed
one step of MPos motion. One step is the smallest miss the read-back
can make: the core's kill step for the pulse start that never came,
which a position kept as the core counted it carries. MPos is
reported to 0.001 mm, so the difference of two readings carries up to
0.21 step at x32 (0.001 mm at $100 = 213.333; 0.05 step at x8), and
a one-step miss lands between 0.79 and 1.21 steps: on either side of
a one-step bound, at random. The bound is now half a step for X, Y
and Z: a correct read-back lands within 0.21 step, a one-step miss at
0.79 or more. The comment and the description say so.
With the committed judges the parent of the read-back work fails the
check on four counts, not two: the kernel is still running 1.0 s after
the ^X (the parent writes no cnc/stop), the tail rate is 100% of the
tick (the run ends at speed), the position is not read back (MPos X
one step past the kernel), and the homed mask goes 4 -> 0.
Proof: a compile and pyflakes 4.0.3 give no message on
forgetest/suite/motion.py, with no undefined name in forgetest or
tests. forgetest unit tests, python3 -B -m unittest discover -s tests
-p test_<name>.py, in a private PID namespace: test_manifest.py 39
OK, test_queue.py 15 OK, test_campaign.py 13 OK. Coverage lint on the
tree manifest from scripts/manifest-from-tree.py (python3 -B -m
forgetest.coverage --manifest ../tree-manifest.json --enforce):
0 uncovered, 0 empty, 0 host-tested row problems, 43 tests. Host dry
runs of the whole test against the null-sink build of the
grblHAL-glowforge default branch head ("serial_test: the scan's exempt
arguments each match once in the sources"), configured as its CI
configures it (-DGFMCODE_WAIT_S=5.0), with the head accelerometer,
the kernel's state and bytes-played counter, and forgectrl stood in
for and the step counters taken from the shipped stream: with a model
kernel that ramps from the controller's cnc/stop write, the test
passes, MPos X 3489.06 steps against 3489, tail 23% of the tick, mask
4 kept, drift 0.000 mm; with a model kernel that ramps as cnc.c does
(the tick held 10 ms, then 5000 Hz off every 10 ms to the 900 Hz
floor), the test passes, MPos X 3509.97 steps against 3510, tail 32%.
Against the null-sink build of the parent of "Stream engine: a soft
reset during motion gets the kernel's ramp, and the position is read
back from the kernel's counters" (the commit "Test hooks: honored only
in a null-sink run, through one gate"), under the ramp model, the
read-back judge fails 5 runs in 5, MPos X one step past the kernel
each time (1.06, 1.06, 1.09, 0.99 and 1.10 steps); with the one-step
judge the same parent passed the read-back 3 runs in 5 (misses of
1.09, 0.95, 0.90, 0.98 and 1.09 steps). Every parent run also fails
on the kernel still running 1.0 s after the ^X, the tail at 100%, and
the mask 4 -> 0. The real counters and the machine's timing are read
on the machine only.
Acceptance: the change is inside the test's own function and its
decorator, so the shared text of suite/motion.py does not change. The
fingerprint of motion.jog-roundtrip moves, and no other test's: of
the implementation hashes of the 43 catalog tests, before and after,
only that one differs. The check runs on the bench in the next
campaign as part of motion.jog-roundtrip.
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.