mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-16 01:29:55 -07:00
c177aabefa375dc5ebba5c976a295d5eaef98a8f
122
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
01f59914e8 |
Catalog: forgectrl.panel-serves, setup.ssh-until-reboot, setup.first-run-flow and logs.routing leave
Each leaves for a host proof that forgectrl's build.yml runs at the pinned 22a2cfc, and that run passed (38728407038): - forgectrl.panel-serves: tests/host_routes.sh serves the panel page bundled, with the theme and the save bar and the policy frame-src 'none', and answers /status, /cam/status and the dose-curve reads. Rows: src/ui.h. - setup.ssh-until-reboot: tests/host_routes.sh, GET and POST /system/ssh with the flag in the run directory and sshd's init a stand-in told start and stop; enable=2 refused. Rows: src/paths.h. - setup.first-run-flow: tests/host_setup.sh, the first run in the page's order: the four documents accepted at their hashes, the press step breathing teal on a scratch LED tree, the press taken as the acceptance, the account and its cookie, the gate's reason after each form step, the run completed. Rows: src/advisories.*, src/button.*, src/led.*, src/hooks.h and the four first-run documents, one row each (docs/advisories/extensions.md stays with exthost.service); src/sheetid.* to tests/sheetid_test.c, the golden ids, because host_setup.sh does not read the sheet id. - logs.routing: tests/fflog_e2e.sh, the image's rsyslog.conf and the rendered rules, each logger in its own file at its level, a stray tag in system/, the kern facility in kernel/. Rows: src/fflog.*. logs.py held logs.routing alone and goes with its import. The five camera tests no longer require forgectrl.panel-serves. test_setup_suite.py keeps setup.account-login and setup.machine-name in its ids; the cases that described setup.first-run-flow (takeover, fixture, covers) go, with the cases of the helpers that left with the two tests (breathes_teal, first_run_record, sshd_policy). test_manifest's behavioral-path case runs the report with no host-tested rows: the real table now names the first-run documents, and the case is about the allowlist. exthost.motion-job stays. It is the only catalog test that grants motion.job and posts /v0/motion/job, the path that reads machine_host_token() on the default token file, and it stays the bench proof of that path until an image carries forgeext's token-file change. src/jobpost.* stays in its covers, with no row. Fingerprints: every test in forgectrl.py, setup.py and camera.py moves with the module text. The next image cannot inherit a campaign. Proof: the coverage lint over the tree manifest from the pins finds 0 uncovered paths and 0 host-tested row problems (49 tests); pyflakes on the touched files is clean; forgetest's unit suite under Linux passes. |
||
|
|
0d63f8740e |
pack-licenses.sh: the image's license step refuses a manifest without the project's recipes; the layer's interfaces file starts no DHCPv6 client
The host proofs of image.license-bundle's pack half and of image.network-boot's interfaces check. No catalog test is removed here; suite/image.py is unchanged. pack-licenses.sh, next to forgefirm-image.bb, is the license step. pack_licenses calls it with the common-licenses directory and the output tar path (forgefirm-image-dev.bb requires the release recipe, so both images run the one call), and do_rootfs's file-checksums name it. Before it writes the tar or removes the tree, it exits non-zero with a message on stderr when the directory is missing, when the manifest is missing, or when the manifest lacks any of the newline-terminated lines "RECIPE NAME: forgectrl", "RECIPE NAME: grblhal-glowforge" and "RECIPE NAME: kernel-module-glowforge". On success it packs as before: tar --sort=name --mtime=@0 --owner=0 --group=0 --numeric-owner, gzip -9 -n, the member common-licenses/license.manifest, and the loose tree removed once the bundle holds the manifest. test_image.py's PackLicensesTests drives that script on a fixture: a missing directory, a missing manifest, and a manifest missing each one of the three lines are refused with a message on stderr and no tar, the tree left; a manifest naming all three packs, the tar holds common-licenses/license.manifest, and the tree is gone. NetworkBootTests reads the BSP layer's interfaces file from the meta-openglow checkout beside forgefirm's (forgetest-ci checks out its scarthgap branch there) and finds no line that starts a DHCPv6 client. A missing file fails the case. meta-forgefirm's content hash moved (forgefirm-image.bb and the new script are layer content, and the platform identity of forgefirm-image-manifest.bbclass leaves out only *.md, *-pin.inc and forgefirm-release.inc): the next image cannot inherit a campaign. Proof: python3 -B -m unittest discover -s tests -p test_image.py on Linux (Ubuntu 24.04, GNU tar 1.35, meta-openglow at f012d8e): 14 OK, the five new cases among them; pyflakes names no undefined name. |
||
|
|
4c6ca8d701 |
test_setup_suite: only registered tests named
The file named setup.mirror (gone at |
||
|
|
f9c298a347 |
Catalog: eleven tests leave for their host proofs
Each left after its host cases passed (forgectrl 1dc85f6) and with the coverage lint clean at the pins, its only failure the known setup.check-lens entry on src/lensfind.*: - forgectrl.tokens: host_routes.sh's scoped tokens (a route refused to a token not given it, neither token in the list or the store), with tokens_test and auth_scoped_test. - update.product-gate: host_update.sh's product gate on the upload, with fwproduct_test. - setup.first-run-page: tests/wizard_walk.py. - setup.check-sensors, setup.check-intakes, setup.check-cameras, setup.check-flow-verify, setup.check-envelope, setup.floor: host_setup.sh's checks and the floor burn, with wizcalc_test. envelope_test.py's cases are unchanged. setup.floor's emission stays with setup.focus and laser.emission-witness. - motion.job: jobpost_test, with jobrun_test and lease_test; jobpost.* is covered by exthost.motion-job, which stays. - exthost.motion-jog: host_routes.sh's credential case. Two HOST_TESTED rows, each a binary forgectrl's build.yml runs at the pin c274f19 that proves the path the lint named: src/fwproduct.* to fwproduct_test, src/grblport.* to grblport_test. exthost.motion-job now requires motion.jog-roundtrip in place of motion.job. bedsize.py held setup.check-envelope alone and goes; forgectrl.py loses _words_of and its hw import, motion.py the job runner's helpers, setup_burns.py FLOOR_KEY and FLOOR_KEYS. forgetest's own tests: test_setup_dark.py loses the sensors and intakes judges and the departed ids; test_setup_burns.py loses the floor's cases, and the guards burn() and preview_ok() hold for the focus burn (the program's tail and reach, the thickness in inches and the one refused, a prompt not answered, the witnesses) run on setup.focus. Stay: forgectrl.auth (the fuse view's button refusal on the real button); forgectrl.settings-bounds (the lid lamp: cam.c writes /sys/glowforge/pic/lid_led, not under GF_SYSFS_ROOT); forgectrl.panel-serves (src/ui.h, which no binary at the pin proves; Z inside the lens reach is setup.check-lens's); logs.routing; setup.account-login (the account's render and its home, image content); setup.ssh-until-reboot (src/paths.h); setup.machine-name (udhcpc -x hostname and no mDNS responder, image content); setup.first-run-flow (src/button.*, led.*, advisories.*, hooks.h and the four advisory documents); exthost.motion-job (jobpost.*, which no binary at the pin proves). Proof: the coverage lint, 55 tests; pyflakes; test_setup_burns, test_setup_dark, test_takeover_position and test_image (54 OK). Shared text moved the fingerprints of exthost.armed-freeze, exthost.platform, exthost.service, forgectrl.auth, forgectrl.panel-serves, forgectrl.settings-bounds, laser.armed-kill, laser.emission-witness, laser.pause-resume-lid-cancel, motion.cancel-abort, motion.deadman, motion.jog-roundtrip, motion.liveness-probe, motion.microstep-modes, motion.pacing, motion.release, motion.step-timing-under-load, setup.account-login, setup.check-airflow, setup.check-lens, setup.check-motion, setup.check-switches, setup.cloud-header-capture, setup.first-run-flow, setup.focus, setup.machine-name, setup.ssh-until-reboot and update.slots-and-signature; exthost.motion-job's own text moved with its requires. |
||
|
|
754c239e08 |
test_image: the hostname and the banner scripts, run over a scratch tree
MachineNameTests runs the layer's own forgefirm-hostname and forgefirm-banner in private mount, UTS and network namespaces: /run a fresh tmpfs, a scratch directory over /sys/class/net, /etc a read-only overlay with the scratch hostname and issue over the host's (so each script makes its copy under /run/forgefirm and writes through its own bind mount, as at boot). unshare -Urmun where user namespaces are open, sudo -n unshare -mun otherwise; skipped only where neither makes a namespace. - the name: wlan0's last four hex digits (forgefirm-12ab with eth0 beside it), eth0's with no wlan0, eth0's with a zero wlan0 MAC; the live name and /etc/hostname agree. - the banner: the image's text, then the heading and one URL per global address of wlan0 (a veth carrying 192.0.2.5, 2001:db8::5 and fe80::5): https://192.0.2.5/ and https://[2001:db8::5]/, the link-local address left out, no marker line, no .local name. Proof: test_image.py under Linux by both namespace routes (9 tests OK), and on Windows with the two skipped. Mutants of scratch copies of the scripts, each failing: eth0 read before wlan0, a zero MAC taken, the file written without the script's bind mount, a marker line in the banner, a .local URL, the link-local address kept. setup.machine-name stays: the DHCP client's -x hostname and no mDNS responder on the image are image content. Nothing under meta-forgefirm changed. |
||
|
|
6594f57362 |
Catalog: motion.port-jog and motion.tray leave; two leftovers of the audit
Two catalog tests leave for the host proofs that carry them; the catalog goes from 80 to 78 (78 @test in forgetest/forgetest/suite/). - motion.port-jog. forgectrl's tests/host_routes.sh (7b2f629 on e69d967) runs the controller port's routes against the port's stand-in: a jog refused without the token, a jog that is no number 400 with nothing on the port, a numeric jog on the port as G91 G21, cancel, release and energize each one line, GET /motion/state passing the port's state on with state and sender, GET /motion/sender 200. The port beside a sender, its polls, the cancel, the client first and the bound are grblHAL's ctlport_test.py and forgectrl's grblport_test; motion.release keeps the physical currents. The helpers before it (_port_state, _port_idle, _words) stay: bedsize, laser and motion.job read them. - motion.tray. host_routes.sh holds POST /motion/tray (tray=in on the port, tray=up 400) and the panel page's switch (id="traybtn", trayToggle, /motion/tray); grblHAL's tray_test.py the mode, its offset, its persistence and its frame; forgectrl's tray_test the daemon's side. suite/tray.py held only it: the module and its import go. The coverage lint then named forgectrl src/tray.c and src/tray.h, and nothing else; grblhal-glowforge's src/glowforge_tray.* stays covered by another catalog test, so it gets no row. One HOST_TESTED row takes forgectrl's, to forgectrl:tests/tray_test.c on forgectrl:.github/workflows/build.yml, which the pin c274f19 runs (./build/tray_test). Two of the audit's leftovers, in the same change: - test_manifest.py's ImplementationHashTests sat after the __main__ guard, so running the file directly skipped it. It is above the guard now, byte for byte: python3 -m unittest test_manifest.py and python3 test_manifest.py (from tests/, where its helpers import resolves) each run its 7 cases, and 34 in all; before, the direct run ran none of them. - laser_lifecycle_test.py's test_lid_open_in_wait said an alarm is raised; the docstring now says what the test asserts: the lid-open cancel is reported and no alarm is raised. The assertions are unchanged and the harness was not run. The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 78 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports nothing on motion.py, manifest.py, test_manifest.py or laser_lifecycle_test.py. The full forgetest suite was not run. Fingerprints, measured against the parent: 17 tests move with motion.py's shared text: the 11 left in motion.py, and homing.manual, laser's four and setup.check-envelope, whose modules read it. The pending pin bumps from forgectrl c274f19 and forgeext 2d0391e already owe all 17; this deletion adds none to the owed list. The two leftovers move no fingerprint. releases/ is unchanged. |
||
|
|
b447ae77f1 |
Catalog: the LAN redirect, the release check, and the update job's lease leave
Three catalog tests leave for the host proofs that carry them; the catalog goes from 95 to 92. - setup.https-only-writes, for forgectrl's tests/host_lan.sh (13273a0): from a LAN peer in a network namespace, reads served over HTTPS and HTTP, three writes over HTTP each a 302 to the same path on HTTPS that acts on nothing, the same write served from loopback, and the token writing over HTTPS with no account. - update.release-check and update.job-locks, together, for forgectrl's tests/host_update.sh (acc3dcf), against a release server of its own: GET /update/release's keys, POST /update/check with a v<semver> tag, a size above 0, notes and new; the dismissal refused for a version that is not a tag, taken for the tag, cleared by an empty one; and a probe download that holds the machine (a settings write and a G21 job each 409 in the job's name, update:download of kind system the holder), ends refused by the signature check leaving no file, and lets the same settings write through after. update.release-check was the last test in suite/update.py; _VERSION_RX and the re import, which only it used, go with it. update.job-locks was the only test in suite/updlock.py: the module and its import in suite/__init__.py go. setup.https-only-writes was the only test of setup.py's "HTTPS boundary" section. tests/test_setup_suite.py drops its id. update.product-gate stays: fwup is on neither this host's PATH nor CI's, so host_update.sh does not prove the five uploads, and no HOST_TESTED row is added for src/fwproduct.*. No other HOST_TESTED row either. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 92 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports only the unused local plain in update.slots-and-signature, which HEAD already has. The full forgetest suite was not run. Fingerprints: setup.py's and update.py's shared text move, so 33 tests move: the 6 left in setup.py, the 25 in the modules that read it, and update.slots-and-signature and update.product-gate. The pending pin bump from c274f19 already owes 31 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
098cfdf193 |
Catalog: setup.record-export leaves
setup.record-export leaves for forgectrl's tests/host_guard.sh (84779c8 on d21e200): the record as JSON with the token (schema 1, the sheet id GET /wiz reports, no password hash, a plain read not an attachment), the download an attachment of the record named with that id, the printable page text/html with no script, the sheet id, "setup record" and an <h3> per completed wizard, both routes 403 without a token or a login, and the sanitized export carrying system/setup.json with the same id and wizards, no $6$, and no panel token anywhere in the archive. The catalog goes from 97 to 96. One HOST_TESTED row: forgectrl src/recordhtml.*, the one path no other catalog test covers, to tests/recordhtml_test.c (no script, the sheet id, the step headings, the escaping), which forgectrl's build.yml runs at the pinned revision. src/setup.* and src/wiz.* stay on the setup family, src/logs.* on exthost.service, forgectrl.lease and the two log tests, and src/main.c, src/ui/index.html, src/ui/panel.js and src/ui/wizard.html on the tests that already name them. tests/host_guard.sh is not named: the pinned workflow does not run it. tests/test_setup_suite.py drops the id, the record-export hardware assertion, and the export lines of the lifecycle covers test (the covers set and its two assertions); the mirror's assertions stay. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 96 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes is clean on setup.py and test_setup_suite.py. The full forgetest suite was not run. Fingerprints: the deletion moves suite/setup.py's shared text, so 33 tests move: the 8 left in setup.py and the 25 in the modules that read it (setup_dark, setup_burns, bedsize, exthost, extcall, extcat, extsender, extwizard). The pending pin bump from c274f19 already owes 31 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ is unchanged. |
||
|
|
1e28eb92cd |
Catalog: setup.factory-return leaves
setup.factory-return leaves for forgectrl's tests/host_guard.sh (d21e200): POST /restore/factory-return with the token and no confirm is 400 and names confirm, confirm=0 is 400, GET /update/status shows no job running, GET /slots lists the archives (run only where /usr/sbin/ffboot is not on the host), and GET /setup carries the confirmed call; confirm=1 is never sent. The catalog goes from 98 to 97. It was the only test in the "the operator" section of suite/setup.py, and called no helper of its own. tests/test_setup_suite.py drops its id and test_the_factory_return_never_runs_the_return. src/update.* stays covered by the update tests, src/ui/wizard.js by the wizard and mirror tests, and src/main.c by the tests that already name it. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 97 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes is clean on both files. The full forgetest suite was not run. No HOST_TESTED row: the pinned workflow does not run host_guard.sh. Fingerprints: the deletion changes suite/setup.py's shared text, so the 34 tests that read it move again: the 9 left in setup.py, setup_dark 9, setup_burns 2, bedsize 1, exthost 9, extcall, extcat, extsender and extwizard 1 each. The pending pin bump from c274f19 already owes 32 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ is unchanged. |
||
|
|
71556b2480 |
Catalog: the setup gate, the advisories, the extensions' consent and cloud-off leave
Four catalog tests leave for the host proofs that carry them; the catalog goes from 103 to 99. - setup.gate-blocks-controllers, setup.advisories-rehash, setup.extensions-consent and setup.cloud-disabled-surface. forgectrl's tests/host_setup.sh (91d30f1) runs the four HTTP proofs against the built daemon with the controller up: each reason the gate holds on and the live fan-count close and reopen; the first-run advisories, their hashes, and an acceptance that takes the press back; the extensions' advisory, its five refusals, and the consent; and every cloud selection refused while cloud mode is off, POST /mode included. builtin_test.c holds the cloud sweep, sha256_test.c the digest, and settings_dir_test.c the search bit the consent adds to the data directory. - gate_is_open and _GATE_COVERS go with the gate test, ADVISORY_DOCS and the hashlib import with the rehash; nothing else calls them. The record builders, REQUIRED_STEPS, SAFETY_PHRASE, Restore and request stay. - The requires that named them: _FIRST_RUN_REQUIRES held only the two deleted ids and goes, so setup.first-run-flow requires nothing from it and setup.first-run-page requires setup.first-run-flow alone; exthost.service keeps exthost.platform. - Coverage: HOST_TESTED rows for forgectrl src/sha256.* (tests/sha256_test.c) and src/builtin.* (tests/builtin_test.c), both run by forgectrl's build.yml at the pinned revision. docs/advisories/** stays behavioral: setup.first-run-flow, which accepts them, covers safety-and-risk.md, licenses.md, privacy.md and cloud-service.md, and exthost.service, which turns extensions on over it, covers extensions.md. - tests/test_setup_suite.py drops the four ids from the id list and the takeover list, the gate test's precheck assertion (the method now names the intake prechecks only), and CloudDisabledSurfaceTests, with the imports only that class used; the docstring no longer describes it. test_setup_suite.py passes (32 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 99 tests; its one finding is setup.check-lens's empty src/lensfind.* entry, which the forgectrl pin bump clears. pyflakes is clean on the four files. The full forgetest suite was not run. Fingerprints: deleting the four functions changes suite/setup.py's shared text, so 35 of the 99 tests' implementation hashes move: every remaining test in setup.py (10), and the modules that import it at module level, setup_dark (9), setup_burns (2), bedsize (1), exthost (9), extcall (1), extsender (1), extwizard (1), and extcat (1, through its import of exthost). The next forgectrl pin bump already owes 33 of them; exthost.platform and setup.machine-name are the two it adds. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
7172bba92b |
Catalog: the report channel, the event stream and what-changed leave
Three catalog tests leave for the host proofs that now carry them; the catalog goes from 106 to 103. - cooling.report-channel. forgectrl's tests/host_routes.sh (e69d967) runs the channel against a live daemon: a run report with the controller's secret writes exhaust 65535; no secret, a wrong secret and a short secret are 403 and the duty file is unchanged; an accepted idle while the ring still reads running writes 32768; the secret dies with the controller. cool_report_test.py and super_test case L own the secret, and cooling.fans-quiet-after-motion owns the live M8/M9 return to the idle duty. The forged Host: localhost was the same secret refusal. - events.stream. events_test.c owns the hello, the replacement, and motors.released; host_routes.sh owns the live cap (three peers held, a fourth 503, GET /settings 200 beside them, closing one admitting the next). forgectrl.lease and exthost.events still cover src/events.*, and _EventStream stays for forgectrl.lease. - setup.what-changed. setup_test holds tray -> laser.focus and fan -> airflow under the image's table beside tube -> laser.floor; host_routes.sh holds /wiz/changed (403, 400) and that every step the menu names is one GET /wiz lists. src/setup.* and src/wiz.* stay covered by the other setup tests, and help.js, panel.js and index.html by camera.key-read. preserved() and flag_of() go with it; recommended() stays. tests/test_setup_suite.py drops setup.what-changed from the id list, the takeover list and the covers lookup, and its flag_of case keeps only the recommended() assertion. test_setup_suite.py passes (36 tests). The coverage lint on the tree manifest finds no uncovered path across the 103 tests; its one finding is setup.check-lens's empty src/lensfind.* entry, which the forgectrl pin bump clears. pyflakes on the four files reports only the unused t0 in cooling.py that HEAD already has. The full forgetest suite was not run. Fingerprints: the cuts change the shared text of suite/cooling.py, suite/forgectrl.py and suite/setup.py, so 56 of the 103 tests' implementation hashes move (cooling 12, setup 26, forgectrl 5, and exthost 13 through its import of suite/forgectrl.py). The next forgectrl pin bump already owes 54 of them; exthost.platform and setup.machine-name are the two it adds. releases/ is unchanged. |
||
|
|
520d62ac05 |
forgetest tests: the confirmed trims of the tool's own unit tests
The cases that tested a mock, a timer, a wording or a table, or that
replayed a judgment a host test already holds, leave forgetest/tests.
What catches a false PASS, keeps the bench safe, or guards the gate
stays.
- test_baseline: the fake sysfs tree is a mixin with no test_ methods,
so ExtensionFreeTests and BaselineModeTests no longer re-run
BaselineTests' 25 cases (133 runs, now 45). CoolPublishTests, the
lamp-fade cases and TransientNotLeftoverTests go; PositionDeadbandTests
keeps a reading past the band and an unreadable reading, both never
excused. Both latch-relock cases stay.
- test_fixture: MdnsTests, the three timing cases, the probe's log
wording, and nine prompt-routing variants go. A live test never runs
unattended, the arm-press opt-in, the short-press edge, the presence
press handing over the arm press, and the arm notice dropping at the
press stay (35 cases, now 21).
- test_cloud_suite: three false-PASS guards stay: the latch open at a
dark print's button, a lid stop that is not edge-driven, the kernel
refusing the resume. The guards for the latch unlocking in the hold
and a paused print resuming left with their catalog tests in
|
||
|
|
21f53e4e83 |
forgetest: laser.verdict-cut leaves the catalog; the stream rules hold its claim
laser.verdict-cut held, on the machine, that a stale cooling verdict
mid-cut holds the job dark under the open window with the latch
untouched, and that the clean verdict resumes it lit with no press.
Every part of that is a rule the host now proves:
- dark while held: laser_stream_test rules 24 and 27, check_hold_dark on
every verdict hold's stationary stretch, and check_fire_gaps (
|
||
|
|
6a434bbf77 |
forgetest: nineteen tests a host test already proves leave the catalog
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. |
||
|
|
7246707df5 |
forgetest: the gate test leaves the step lists to forgectrl's setup_test
setup.gate-blocks-controllers asserted on the machine which steps the image's table requires (GET /wiz's missing list) and recommends (each not run yet, never the gate's parts or the optional steps). Both are the setup table's decisions over a record, software with no hardware in them, and forgectrl's setup_test now proves both on the image's own table: the required steps in order (10b), the missing list emptying when they are done (4b), and the recommended steps in order, each not run yet (10b). The catalog keeps what needs the machine: the supervisor holding and spawning the controller, the process, and the Grbl port. - The two missing-list checks and the recommended-list check leave the test's body; its description says where they are proven. - RECOMMENDED_STEPS and NEVER_RECOMMENDED go with them (no test read them any more), and the meta-test case that only compared them. Proof: the forgetest host suite passes under Linux (test_setup_suite with the case removed among it); the coverage lint is unchanged. Acceptance: every setup.py test (the constants were the module's shared text), setup.gate-blocks-controllers among them. |
||
|
|
ed1322518b |
forgetest: the lint takes python -m unittest given a test's file or module
forgectrl's CI runs its dev-server mock test as `python3 -B -m unittest -v tests/test_devserver_mock.py`, and the workflow judge counted python -m unittest only as `unittest discover`: a HOST_TESTED row for that test was refused as not run. _unittest_names: an argument to python -m unittest (not discover) runs the test when it is the test's file (resolved against the step's working-directory) or a dotted name whose module is that file (tests.test_x, tests.test_x.Case.test_one); a -k pattern's value and an argument with a shell variable are not names. Proof: host tests: the CI's own command line, a module, a module with a class and a method; not a module beside it, nor a -k pattern naming it. The manifest tests (34) pass, and the forgetest host suite passes under Linux. Against forgectrl's pinned workflow a row for tests/test_devserver_mock.py is accepted; every earlier verdict on the real workflows is unchanged. Acceptance: none. forgetest is dev-only and in no fingerprint; the lint runs in CI, not in a campaign. |
||
|
|
a9c794be34 |
forgetest: a path a host test proves needs no catalog test
The coverage lint failed CI on any behavioral path no catalog test covered, so every software change pulled a new or wider acceptance test in with it, though the catalog is for what needs the machine or an operator. A path whose behavior a host test in CI proves now has a row in HOST_TESTED (manifest.py): the component, a glob, the host test, and the CI workflow that runs it, the last two as "<repository>:<path>" - a file of a component in the manifest (its tests/ and .github/ files are listed there), or "forgefirm:" and a path in this repository (the null-sink harnesses, which grblhal-glowforge's workflow runs). - coverage_report counts a host-tested path as covered. Fingerprints are unchanged: a path no catalog test covers is in no fingerprint, so a change there makes no acceptance run necessary. - host_row_problems reports a row whose glob selects no behavioral file; a host test or workflow that does not exist (a component file missing from the manifest, a "forgefirm:" file missing from the checkout, the dev-only forgetest, no repository named); a workflow that should be readable and is not; one that does not check out the test's repository; and one with no step that runs the test. - The workflow is read at the pinned revision: read_source takes a component's blob from the bare repo manifest-from-tree.py fetched for its source URL (the checkout's .manifest-cache, or --sources), a "forgefirm:" file from the checkout. With no sources (an image) it is not read and the lint says so; with sources, a read that fails is a problem, never a pass. - workflow_steps reads the workflow's steps by indentation (uses, run, working-directory, with), comments dropped, whole lines and from " #"; a quoted scalar loses its quotes, a quote inside a command stays. checkout_path finds where the test's repository is checked out (its own: the checkout naming no repository; another: the checkout of <owner>/<name>, the name from the component's source URL). - workflow_runs looks at what a step's run commands execute, split into simple commands (;, &&, ||, |, newlines, then/do/else; a backslash continuation joined), with sudo and its flags, env, time, exec and variable assignments passed over, each path resolved against the step's working-directory and compared with the test's workspace path: the command itself (a script run directly, a C test's built binary <dir>/<stem>); the script an interpreter runs (python, bash, sh) or a for loop's list, by path or a glob matching the whole path segment by segment; unittest discover starting in the test's own directory with a pattern its name matches (test*.py unless -p); pytest given the test or its own directory, for a name it collects. An argument to anything else (echo, cat, a build target), a step's name, a comment, a trigger's paths filter, or a path that only ends like the test's does not count. - The lint prints the row problems and fails --enforce on them, as on an uncovered path or an empty covers entry; --json carries them as host_tested, with workflows_read (false when a workflow that should have been read was not). It reads HOST_TESTED when it runs. - The table starts empty: rows come with the tests that leave the catalog. Proof: host tests: a host-tested row takes its paths out of the report and moves no fingerprint; good rows report nothing; each kind of bad row is reported with its reason; a workflow that runs one harness and names another only in its paths filter, a comment line and an inline comment runs the first and not the second; workflow_runs on a built binary, sudo and a binary, a script, a harness under another checkout, pytest on a directory, a for-loop glob and unittest discover under a step's working-directory, and not under another one; and not on a paths entry, an inline comment, a glob or a discover that the path only ends like (tools/tests/..., scripts/bench/tests/...), a longer name, another file, a step's name, or a commented line in a block; not on echo with the stem or the binary's path, a build target, cat of a script, unittest discover from a directory above the test or with a pattern its name misses, or pytest on a directory above it; on discover with -p '*_test.py', pytest given the file, a script run directly and by bash, and sudo -E with assignments continued onto the next line; checkout_path for its own and another repository and none; repo_name from a source URL; a component's workflow read from a bare repo's blob, judged, not judged without the sources, and a blob that cannot be read reported; the command line with a workflow it cannot read reports workflows_read false and the read failure, and fails --enforce. The manifest tests (34) pass on Windows and under Linux; the forgetest host suite passes under Linux. Against the recipe pins and their fetched workflows: rows for forgectrl's tray_test, forgeext's holdkeep_test, sandbox_test (under sudo) and run_test.py (sudo, env and assignments over three lines), the laser_stream_test harness (grblhal-glowforge's checkout of forgefirm), gfhardware's test_machine_lid_button (a for-loop glob under its working-directory), gfutilities' test_frame (pytest on tests) and forgetest's own test_manifest (unittest discover under forgefirm/forgetest) pass; rows for forgectrl's fflog_e2e.sh, the bench script fire_test.py and kas/source-bundle.yml (only in forgetest-ci's paths filter) are refused. The lint on that manifest reports 0 uncovered paths and 0 host-tested row problems. Acceptance: none. forgetest is dev-only and in no fingerprint; the lint runs in CI, not in a campaign. |
||
|
|
1191389a2b |
forgetest: no press is ever held; the arm notice goes down at the press
- The presence press at the Ready gate polled forgectrl's /status over HTTPS every 0.25 s plus the request's own time, so a short press fell between two reads and the operator held the button for seconds. It is read as an edge from the switches' input events (hw.ButtonEdges, no grab), opened before the notice goes up: a press of any length counts. Events read and not yet looked at are kept, so a short press whose release arrives in the same read gives the release to the next wait. With no switches device (the host harnesses) the status is polled at 50 ms. - An operator's arm notice (arm_press, press_now) stood until the test's next step, over a press already taken. A watcher takes it down the moment the controller puts the button out at the press; a notice put up meanwhile is left alone. Proof: host tests: a press and release written back to back on a FIFO standing in for the switches' device pass the Ready gate in well under a second with the status never showing the button down (the first version dropped the release with the press and waited 10 s: the test caught it); the arm notice stays while the button is dark or lit and goes down when it goes out, and a notice put up meanwhile stays. The fixture (35), setup (87), manifest (26) and operator (22) host tests pass. Acceptance: these are the harness's own paths; every live and operator test with a press runs through them. |
||
|
|
4c5dde2549 |
forgetest: the burns, the lens and the intake checks follow forgectrl's review fixes
- baseline: the lid lamp fades to a new level (the kernel's smooth trigger), so the hand-back reads it until it holds the wanted level twice in a row, for up to 3 s, before it is a leftover. motion.deadman failed its hand-back reading 232 against 236 0.05 s after the supervisor re-applied 236 to a respawned controller. - setup.check-lens and setup.focus: grblHAL reads the lens window and the edge only at its start, and their restore came after the wizard had restarted the controller on the test's values. Each test now waits for the step to end, puts the settings back, and restarts the controller (stop, start, running and verified with a new pid) on every exit path; cloud mode restarts nothing. - setup.focus and setup.floor: the served program ends with G0 Z<edge>, G0 X0 Y0, G92.1, M2; setup.floor requires setup.focus and checks the scrap's thickness against the lens reach; the burns cover lenshome. - setup.check-intakes: the two assertions that could not fail are gone; an intake-count prompt fails the test unanswered (the bench reference's intakes both turn). The precheck's refusal names no setting. - setup.check-airflow: the machine's count rules (intake 2 turning writes 2; silent on a one-fan machine, the count is not written). Shared text: setup_dark.py's precheck string (all setup_dark tests, setup_burns, setup.check-envelope), setup_burns.py's imports, covers, docstring and preview check, cooling_intake.py's docstring; every test they move covers paths forgectrl changed in the same work. Proof: the forgetest host suite passes (548), with new host tests for the lamp's fade, the restarts on pass, fail and abort, the floor's Focus refusal and reach, the tail without G92.1, the intakes prompt, and the airflow count rules; pyflakes is clean on the changed modules. Acceptance: these are the tests. |
||
|
|
005ff8008b |
forgetest: the coolant flow calibration is a required step; the gate's missing list
forgectrl requires cooling.flow after preferences, machine, and cloud, and GET /wiz lists the required steps the gate waits for as missing. - REQUIRED_STEPS is the form steps plus cooling.flow, in the gate's order; REQUIRED_FORM_STEPS are the form steps. cooling.flow leaves RECOMMENDED_STEPS. - setup.gate-blocks-controllers expects "setup required: preferences, machine, cloud, cooling.flow" on a record with no step done, with all four named in missing, and an empty missing list once they are done and the gate is open. - setup.first-run-flow walks the form steps alone: its seed keeps the checks done, the calibration among them, so the gate's reason narrows from the three form steps to open. Proof: the setup host tests (74) and the manifest tests pass; the host test of record_with_required_steps keeps the four. Acceptance: these are the tests. |
||
|
|
748d28686d |
forgetest: the setup burns need two witnesses and record the beam sensor
setup.focus and setup.floor follow forgectrl: a burn counts on the tube current and the kernel's LASER_ON samples, and its result records the beam sensor's rise and beam_sensor_seen, never requiring a rise (a head may give no output from the sensor). The check reads that both are recorded and logs them; the descriptions name the two witnesses. Proof: the setup host tests (74) and the manifest tests pass, two of them new: a burn whose sensor gave no output passes, and a result that does not record the sensor fails, each with the settings put back. Acceptance: these are the tests. |
||
|
|
ed52e752ee |
forgetest: the setup required up to the intake fans; the burns replace the sheet
The catalog follows forgectrl's setup (openglow-org/forgefirm#4): the gate is the advisories with the press, the account, the required steps (preferences, machine, cloud), and the intake fan count; every other step is recommended; the setup sheet is retired for two self-contained burns and a dark lens travel check; the override file is gone. - setup.gate-blocks-controllers installs four records: without consent, without the account, and without the three required steps the controller is gated with the reason naming the cause; with them done the gate opens, the controller runs, and the other steps are recommended as not run. Clearing cool_intake_fans closes the gate live with its reason, and putting it back as found reopens it. - setup.override-until-reboot is deleted with its helpers, and forgetest.init no longer writes the override file at boot. - setup.first-run-flow follows the reason from the three required steps to open; setup.first-run-page walks the choice screen after them; setup.what-changed reads the one-level map with the gate unmoved. - New setup.check-intakes (the count by the tachs equals the machine's, the setting put back as found) and setup.check-lens (the window written equals the stops found or the fallback, and the settings and /status hold it; both keys restored). A precheck refuses the intake and airflow checks while cool_intake_fans is unset, so neither can leave it unset in the middle of a campaign. - setup_burns.py replaces setup_sheet.py: setup.focus and setup.floor answer the gantry stops, the home, the scrap and the thickness, and keep the preview, emission and window checks; motion.tray starts the focus burn for its tray-out refusal. Proof: the forgetest host suite (529 before the required-steps edits, and the full suite after them) and the manifest load pass; pyflakes reports no undefined names. The coverage lint is clean against forgectrl's working tree; against the current pin, setup.check-lens's src/lensfind.* glob selects no file until the forgectrl pin moves to the commit that adds it. Acceptance: these are the tests themselves; the campaign runs them with cool_intake_fans set on the bench. |
||
|
|
35221de791 |
forgetest: every machine action a test takes is declared
The queues route a test by its kind and its declared actions: an auto test runs unattended with or without the bench fixture, and an operator test whose actions the fixture covers runs unattended only while it does. exthost.events opens and closes the lid through Context.act but was declared auto with no actions, so the unattended queue ran it with no fixture on the network, and it waited 180 s for a person who was not there: the fx29 campaign (image 20261010213702) ended on "lid open was not seen on the machine within 180 s" with forgefixture.local silent to mDNS and 172.16.1.171 silent to ping. - exthost.events: operator, actions lid, with the operator's steps. - events.button-telemetry: the same defect with the button; operator, actions button, with the operator's steps. - setup.check-switches: declares interlock beside lid and button; the check works the interlock loop on a Pro. test_fixture's new CatalogTests case reads every catalog test's source and fails a test that calls Context.act on a channel it does not declare, or that is auto and calls it at all. It found these three and no other. Fingerprints: the three decorators are each test's own text, so these three move and no other (diffed across the change, 128 tests). Proof: the whole host suite passes (509 tests) and pyflakes finds no undefined name. RoutingTests already prove the routing these declarations feed: an operator test the fixture covers moves to the unattended queue, one it does not stays attended, and with no fixture the routing is the old one. Acceptance: the change is the three tests' declarations; with the fixture up each runs unattended through it as before, and with the fixture down each stays in the attended list instead of stranding the unattended queue. |
||
|
|
16a8ae9ba3 |
forgetest: cooling.intake-one-fan, and setup.check-airflow reads the intake fan count
forgectrl c274f19 adds cool_intake_fans: at 1, a machine built with one
intake fan, the second intake is published not fitted and never judged,
and the setup's airflow check writes the count it finds.
cooling.intake-one-fan, in a suite module of its own, proves both halves
on a machine with two intake fans with the setting alone. With
cool_intake_fans 1 and an 8 s grace, a session at the cut fan profile
must publish intake_2 as not fitted with no floor while it reads a real
speed, run OK, and judge intake_1 ok at the intake floor; with the intake
floor at its legal maximum it must trip AIRFLOW (hold, fire blocked) with
intake_1 TRIPPED and intake_2 still not fitted, and the fault must end
with the session; restored, every fan reads ok at its floor. It starts
only from a machine with both intakes judged, requires
cooling.fan-gate-trips, and puts the count, the intake floor and the
grace back as found on every exit path.
setup.check-airflow now reads the count from the result: 2 needs all
four fans at least 1000 rpm, 1 needs intake 2 reported {"fitted": false}
and the other three ok; either way the setting must read what the check
found, and the count is put back with the five floors.
Fingerprints: the new test has a module of its own, so no cooling test
moves; the setup.check-airflow edit is inside its function. Diffed across
the change: 127 -> 128 tests, one added, setup.check-airflow the only
one moved.
Proof: the touched host tests pass (test_cooling_intake_suite 4,
test_setup_dark 10, test_cooling_suite 25) and pyflakes is clean.
test_cooling_intake_suite replays the test against a scripted engine,
and two broken engines fail it with the settings put back: one that
ignores the setting, and one that takes intake 1 out with intake 2.
test_setup_dark's new AirflowCheckTests take a two-fan and a one-fan
result, and fail a count the setting does not match and a result with
no count. On the bench reference, image 20260928194539 (dev) with these
files and forgectrl c274f19 bind-mounted, cooling.fan-gate-trips,
cooling.intake-one-fan and setup.check-airflow pass with a clean
baseline before and after each (forgectrl's commit has the readings).
|
||
|
|
8dc7c0bc56 |
forgetest: the stale boot reference judged at a held uptime, on both sides of the limit
test_stale_preconfig_reference_is_retaken_on_a_fresh_boot read the host's
uptime after boot_reference() had read it to decide, and expected the
decision its own reading implied. On a CI runner the uptime crossed
BOOT_MAX_AGE_S (600 s) between the two readings: boot_reference retook
the reference and the test expected the stale one (forgetest-ci on
|
||
|
|
0eb0a16452 |
forgetest: forgeext's packages/ exemption goes with the packages
OpenGlow's packages left forgeext for repositories of their own, so the
non-behavioral entry ("forgeext", "packages/**") names nothing. The
manifest test checks the shared package workflow's kit action as
non-behavioral in its place (".github/**" covers it). The manifest tests
pass (26); manifest.py is in no fingerprint.
|
||
|
|
d2307a899e |
forgetest: the cooling tests judge the log line their own run wrote
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. |
||
|
|
800863fb09 |
forgetest: the cloud tests wait out the service's thinking
wait_quiet took the machine as quiet after 8 s without the start of a motion, a park, a run or a lens homing. Between a lid image and its next move the service is working on the image and the log is silent, and on the bench reference the re-hunt's moves came 9.6 s apart: cloud.mode-switch switched back to GRBL mode in the middle of the re-hunt, twice, the first time canceling a motion at 988 of its 1002 steps. Every line of a service action now counts as activity: the requests, the image uploads, the action ends, the runs, the parks, the motions and the lens homing. The quiet is 30 s. Over 1901 motion, lid-image and hunt requests in the bench reference's logs, half came within 1.6 s of the line before, 99 percent within 12.8 s, and two after more than 30 s (32.8 and 62.2 s). return_head goes: cloud.mode-switch hands its cloud stretch back through the cloud client's log (homeoff.cloud_mode_return), and nothing else called it. RETURN_MAX_MM moves to homeoff, its one user. Proof: tests/test_cloud_suite.py's new case lands a motion, a lid image 0.8 s later and the next move 1.6 s after the motion, with the quiet at 1 s: the quiet comes after the move. With the old activity marks it is declared after the image, before the move. forgetest 499 OK. Acceptance: cloud.mode-switch gates the change on a machine, its re-hunt waited out before the switch back. The fingerprints of every test in suite/cloud.py and of every module that imports from it move: 29 tests, the cloud tests, events.button-telemetry, the exthost tests, homing.cloud-offsets and setup.check-envelope. |
||
|
|
cfef6a1b84 |
forgetest: the camera-home tests hand the head back where they found it
homing.cloud-offsets, setup.check-envelope and cloud.mode-switch let the
service move the head, and ended with it where the service left it: at
the camera home, or, in cloud.mode-switch, under the camera, where the
service's re-hunt had taken it. Each told the baseline the counters had
been re-zeroed at the starting position, so the hand-back saw nothing to
do. No counter reading can say where the head was found: every service
motion zeroes the counters at its start, and the home and every
controller start zero them again.
suite/homeoff.py now holds the helpers for a test that lets the service
move the head:
- session_travel sums a client's own record of each motion's end ("end
positions (x, y, z)", the counters the motion zeroed at its start, in
x8 steps, the one mode a service motion runs at). A motion with no end
on record, or a log rotated under the run, leaves the travel unknown.
- camera_home_return drops the camera home, jogs the head back by the
session's travel plus what the counters read since the home, starts
the controller once more so the counters read zero where the head
began, and tells the baseline. cloud_mode_return does the same for a
stay in cloud mode, from the cloud client's log.
- A travel that cannot be known fails the run and moves nothing. A
hand-back that fails while the test is already failing is logged, and
the test's own failure is the one reported.
- judge_whole_motions fails a homing motion that stopped short: the
position declared after it is false.
cloud.mode-switch imports the helpers inside its function, so no other
cloud test's fingerprint moves. Its cloud stretch no longer goes through
return_head, which read 0/0 after the controller's start and left the
head under the camera.
Proof: tests/test_camera_home_return.py, 15 cases on the machine's own
log lines (a session stopped short, a whole three-motion one, a cut log,
a refused motion, homed and unhomed counters, a restart since the home,
the first failure winning). tests/test_cloud_suite.py's mode-switch fakes
now zero the counters and remove the anchor at a controller start, write
the anchor at the home, and move the counters on a jog: 10 cases, a
failed hand-back that must not hide the test's failure among them.
forgetest 498 OK. On the bench reference, with the driver and the runner
fixes: homing.cloud-offsets, setup.check-envelope and cloud.mode-switch
PASS, each ending with the head where it was found; cloud.mode-switch
jogged its cloud stretch 246.06/139.01 mm back with the counters across
the jog agreeing, and every baseline was clean.
Acceptance: the three tests are the change; their fingerprints move and
no other test's does.
|
||
|
|
ef2390f7d4 |
forgetest: a takeover hands back the cloud client it found
In cloud mode forgectrl's start at the end of a takeover starts a cloud
client, and what that client is comes only from gfcloud's one-start
markers, which the client that starts reads and takes down. The takeover
started it bare: the online client, with the service's connect-time
hunt. Seen on the bench with exthost.armed-freeze, which runs its print
under the offline service and puts the setup record back under a
takeover: the machine came back online in cloud mode, the hunt homed the
head at 14:21:01, the run's result was written at 14:21:02, and the
service sent a head move at 14:21:08, after the run had ended, where the
next test's switch to GRBL mode would have cut it off. It also broke the
harness's own rule that every cloud client start it makes comes up
without the hunt.
The takeover now reads, on entry, whether the running cloud client is
the offline service: the client itself holds the listening socket at
/run/gfcloud-offline.sock (hw.listens_on reads /proc/net/unix and the
process's own descriptors, so a socket file another process left behind
does not count). forgectrl's start is made under the no-hunt marker in
cloud mode, and under the offline marker as well when the offline
service was found. After the start the client that came up must listen
on the offline socket within 60 s; a miss is recorded on the run's
baseline capture and the post pass turns it into a leftover ("cloud
client at the takeover end", not restored: a cloud test starts the
client it needs), which fails the run. A marker no client read (the
gate, standby, a fault) is taken down, so a later start never comes up
under it. The judge never breaks the exit path.
Proof: forgetest's unit tests pass on the host (481 OK). The new
tests/test_takeover_client.py drives a real enter and exit against the
fake forgectrl, a fake /proc, and an init script that plays gfcloud's
start: the offline service comes back offline, an online client after
it fails the run, a start that never happened takes the markers down,
an online client comes back without the hunt, a stale socket file is not
the offline service, GRBL mode starts under no marker. Negative
controls: without the markers four of them fail, without the judge
three. Bench drill on the bench reference with this package from /tmp:
GRBL mode, the offline service started as enter_offline starts it (pid
4834), a takeover; the client forgectrl brought back (pid 4898) read
both markers, logged "OFFLINE service", listened on the socket, and in
the 10 s after had no web session, no hunt, and no motion. The bench was
then handed back to GRBL mode as found.
Acceptance: runner.py, baseline.py and hw.py are in no test's
fingerprint, so no result moves; the catalog test that exercises the
change is exthost.armed-freeze, whose takeover now fails the run unless
the offline service comes back.
|
||
|
|
b7dd23d539 |
forgetest: a takeover judges where the head stands
forgectrl's start at the end of a takeover is a controller start, which
re-zeroes the step counters wherever the head then stands. After it a
head left out reads as home, so the baseline's position check after the
run could not see a head a test left out before its takeover.
setup.check-envelope did exactly that on 2026-09-23 and passed (fixed by
|
||
|
|
308a033146 |
laser.verdict-cut judges the hold by the gated output; laser.disarm-in-hold presses at the arm
Both tests failed for the operator on the bench reference on 2026-09-24, image 20260923232513, from the harness and not the machine. laser.verdict-cut (00:10:52) needed the kernel's sampled LASER_ON count to read 0 within 2.5 s of the hold. That count latches once a second over the second before, so it reads 0 only once a whole window has closed inside the hold, up to 2 s in, and this hold lasted 1.37 s. Every sample of the hold read bit 0 of interlock_circuit as 1: the gated LASER_ON, active low, dark. The dark judge now reads the gated output itself, cnc/laser_on (1 = on), about 3300 times a second on the bench reference, yielding the CPU between reads (the stream threads run SCHED_FIFO). Its witness is the same reads over the second before the pause, which must see the cut lit (10 reads or more, none failed). The hold is dark when every read that falls wholly inside it, from 0.3 s after the first Hold:0 to the last Hold row, reads off, none failed, and there are at least 500. The 0.3 s is the pause tier's first deceleration, lit on purpose, still playing out of the driver's 200 ms queue and its 10 ms lead when the controller reports Hold:0. The daemon's pause is 3.0 s, as the code already had and the description now says. dark_span() is pure, kept inside the test's function, and tests/test_laser_verdict.py runs it through the function's code object over synthetic trails (8 cases: a dark hold, the lit deceleration inside the drain, emission after it caught, a burst the resume ends, failed reads counted, the drain counted from the first Hold:0, no Hold:0, and a hold too short for the drain). laser.disarm-in-hold (00:06:23, 180 s) pressed the button through ctx.act at once, before the job reached its arm wait, so the press was lost and the move never started. It now uses ctx.arm_press(), which presses when the button lights, and waits for Run. Proof: forgetest's unit tests pass on the host (474 OK, 4 skipped). Only the fingerprints of laser.verdict-cut and laser.disarm-in-hold move. Acceptance: the change is the two catalog tests themselves; both are attended (laser emission, the operator present) and a campaign runs both again. |
||
|
|
dc9bede034 |
forgetest: the first /state parses each suite module once
The first GET /state after forgetest starts hashes every test's implementation, and it took from 81 s to more than 12 minutes on the bench reference. Two causes: - sibling_imports() read and parsed a test's module and every sibling it imports, transitively, once per test: 280 ast.parse calls for 26 suite modules. module_parts() kept its own parse, sibling_imports() kept none. - The page gives up on a poll after 20 s and polls again; the server thread goes on computing. Each new poll started the same cold work beside the first, all of it on the one CPU, so the longer the first took the more copies ran. That is the spread between 81 s and 12 minutes. Each module's text and tree are now read and parsed once and kept, as are its direct sibling imports, and the implementation hash is filled by one thread at a time: a poll that arrives while it is computed waits for it instead of repeating it. catalog.forget(path) drops what is kept about a file, for the unit tests that edit their modules. The hashes do not move: every test's implementation hash and domain fingerprint on the image manifest of 20260923220034 is byte-identical before and after, so no result is invalidated. On the host the cold computation went from 7.00 s to 0.29 s (280 parses to 25). On the bench reference (the file bind-mounted on image 20260923220034, the page open) the first /state answered in 8 to 12 s after a restart, where the same restart earlier in the evening had not answered after 7 minutes. tests/test_responsiveness.py pins both: each suite module parsed at most once while every test's implementation hash is computed, and three threads reading one test's hash compute it once. With the old behavior put back by a patch both fail (280 parses for 26 modules; the hash computed 3 times). forgetest's unit tests 454 OK. forgetest is the dev-only harness, outside the catalog's coverage; its catalog consequence is none, since no fingerprint moves. |
||
|
|
4e47b7fbf1 |
forgetest: exthost.catalog, and the coverage lint no longer allows an advisory away
exthost.catalog (suite/extcat.py, its own module): GET /ext/catalog answers the index the host keeps and the one address it is fetched from. On a scratch root under /tmp, with a throwaway key standing in for the OpenGlow extension key, the machine's own forgeext keeps an index signed with it, and the author key it names for one id makes a package of that id read as community and endorsed, where before it was unverified; the same key on another id counts for nothing. On the machine's own root that index is refused in words, a package handed over as an index is refused by the product gate, and the index kept is left as it was. The relay refuses an id with no such form (400) and one the kept index does not list (404, or 409 with none kept) before anything is fetched, and a refresh from the fixed address keeps OpenGlow's index when one is published there and is 502 in curl's words when none is, the kept index left as it was. Nothing is left in the staging directory. The coverage lint had a gap: coverage_report() let the allowlist's docs/** and **/*.md take out a path the BEHAVIORAL list keeps in every fingerprint, so the four first-run advisory documents were covered by no test and the lint passed. A change to the privacy advisory would have invalidated nothing. A behavioral path is now never allowed away, and setup.advisories-rehash, which accepts every first-run document at its current hash, covers the four. Proof: on the bench reference, with forgectrl 848ccc1 and forgeext a64b933 bind-mounted and the privacy document accepted again at its new hash with the fixture's press, exthost.catalog PASS (the refresh was 502: GitHub answered 404, nothing is published at the address yet), and setup.advisories-rehash PASS with the rest of the campaign. The lint's new unit test reports the uncovered advisory, and the old reading (the override ignored) reports nothing for it, which is the gap. forgetest's unit tests pass (452), and the coverage lint passes with --enforce. |
||
|
|
71b44598eb |
test_cloud_suite: the quiet wait's deadline is no longer the test
The cloud suite's host tests replay a print's log from threads, and the suite's quiet wait gave up after 3 s (QUIET_TIMEOUT_S in setUp). On a loaded host the last replayed lines landed after that, and a test failed with "still running service moves after 3 s" instead of its own finding: about one run of the module in five, a different test each time, alone as well as under the full suite. The deadline is now 15 s. It is a deadline and not a wait: a quiet machine is seen at once, so a passing run is no slower. The one test that waits the deadline out on purpose (a motion that never goes idle) sets 3 s for itself; tearDown puts the module's value back. Proof: before, test_cloud_suite failed 2 of 10 runs by itself, every failure the quiet deadline (test_lid_during_button_wait_on_the_bench_excerpt, test_pause_resume_fails_without_the_retraced_restart, test_mode_switch_fails_when_gfhome_never_saw_the_head_move, test_pause_resume_fails_when_the_kernel_refuses_the_resume). After, 10 of 10. forgetest's whole unit suite: 451 tests OK, 4 skipped, in 492 s against 491 s before. A host-test change: no catalog consequence. |
||
|
|
b7e7c0fb2c |
Manifest: the advisories are behavior, and forgeext's kit and packages are not
forgectrl embeds its advisory documents in the binary, serves them, and records the operator's consent to one by its hash, so an edited advisory is a changed consent. They are Markdown under docs/, which the non-behavioral list takes out of every fingerprint, and that left setup.extensions-consent's covers entry for docs/advisories/extensions.md selecting nothing: the enforced coverage lint fails on it (exit 1 on the dev image's manifest of 20260922225653), and an edited advisory moved no fingerprint at all. A BEHAVIORAL list now keeps forgectrl's docs/advisories/** in, ahead of the non-behavioral one. forgeext's recipe installs the binary and its init script and nothing else, so packages/ (the official packages, which carry their own acceptance artifact), sdk/ (the author's kit), template/ and tools/ are non-behavioral for the image: without that, an edit to the alignment page or the kit would make every exthost test stale on the next image. Proof: test_manifest passes its 25 cases, the new ones among them; the enforced coverage lint on the dev image's manifest of 20260922225653 exits 0 with no empty entry and nothing uncovered, where it exited 1 before. The whole forgetest suite ran its 451 tests; one, test_cloud_suite's test_pause_resume_passes_on_the_machines_lines, errored under the suite's load and passes 16 runs of 16 alone, at HEAD and on this tree alike: it replays a print against timed hooks. |
||
|
|
eb0a1e7838 |
exthost.panel-install: the key goes in through the panel, and the box holds the button
The test added the owner's key by copying the file. It now adds it the way an operator does: POST /ext/key with the machine's button held. Without the button it is 409 and no file lands; a name with a space, a name that is a path, and no key at all are 400; a key that is no key is 409 from the host; with the button held it is added and listed with its id. Removed again, the same archive reads unverified, and removing a key that is not there is 409. The bench actuator's press was 200 ms, the firmware's default, and a request that must reach the machine while the button is down often missed it: the unverified install took three presses on one run and all ten on another. ctx.act() now takes `ms`, the fixture client passes it to the box (the firmware clamps it into 20 to 500), and the two button-held steps ask for 500. Both landed on the first or second press afterward. Proven. The unit suite: 451 tests, 0 undefined names (both stub fixtures take the new argument, and the fixture test pins that the box is asked for the longest press). On the bench reference, image 20260921161446 with the cross-built forgectrl and extension host mounted over the image's: exthost.panel-install PASS, exthost.package-routes PASS, exthost.hold-pause-tier PASS. Against the image's own daemons the install test FAILS, as it should. Acceptance. exthost.panel-install covers forgectrl's src/extpkg.*, src/main.c, src/auth.* and forgeext's src/main.c, src/install.*, src/pkg.*; the runner and the fixture client are harness, outside the suite and outside every fingerprint. |
||
|
|
1a306d5d8d |
forgetest: a campaign's machine is extension-free
An extension package is software the image does not carry, and a result taken beside one is not a result about the image. Two places hold the line. The baseline (_ext_side, in every pre and post pass): a package under the tests' own prefix (org.forgetest.) and an owner key named forgetest-*.pub are what a test made and left behind; they are removed (forgeext remove, the key's file), recorded as restored, and the host stops the service on its next turn. A process that still runs under a pool account after that belongs to the operator's own packages: it is recorded as unrestorable and never touched. An installed package that does not run is nobody's leftover. hw.pool_pids() reads each process's Uid line; hw.ext_packages() lists the package directory. image.health (5b): the extension host is one process (/usr/bin/forgeext run), its start link sorts after forgectrl's and its kill link before it, no package is installed, and nothing runs under a pool account. Proven. test_baseline.py, ExtensionFreeTests, over a stand-in forgeext and a fake package tree: an installed package that does not run leaves nothing; a test's package and key are removed and the operator's package and key stay; a removal that fails says so in the host's own words; running extensions are reported and left alone. The unit suite passes (451 tests, 0 undefined names). The link order image.health asks for is the one the built root filesystems of image 20260921014201 have: S90forgectrl before S91forgeext, K09forgeext before K90forgectrl. Acceptance. image.health gains forgeext's init/** in its covers map; it runs first in every campaign and is the on-image proof of 5b. The baseline is harness, outside the suite and outside every fingerprint. |
||
|
|
619414ed2b |
cloud suite: a print turned away before the button fails at once, with the reason
The cloud client turns a print away before the button wait for four reasons of its own (machine._safe_to_move): the lid or the interlock, a machine that is not idle, the coolant above its start ceiling (THERMAL.max_start_temp), a coolant sensor that reads nothing. It logs the reason and finishes the print ':cancelled' within a millisecond. The four tests that wait for the button looked only for the wait, so on the bench reference, with the loop at 27.4 C against the ceiling of 27, cloud.dark-print sat out its 120 s and said "the print never reached the button wait". wait_button_wait replaces the four waits: it ends on the print's finish line as well, and fails with the client's own lines, for example "the client turned the print away before the button wait: INFO machine:_safe_to_move machine temp is too high, temp: 27.4 (... finished with event ":cancelled")". The ceiling is the client's and stays where it is: the remedy for a warm loop is airflow, a run session for a minute or two. Proven. test_a_print_turned_away_before_the_button_fails_at_once_with_the_reason replays the bench reference's lines and fails in under 30 s with that reason; with the early exit disabled it fails after the full 120 s with the old words. Each reason is a line the pinned cloud library can log, so the phrase check passes. The unit suite passes (422) with no undefined name. On the bench reference, image 20260920211625 with this cloud.py over the image's, cloud.dark-print passes through the new wait with the loop at 25.8 C. Acceptance. cloud.dark-print, cloud.verdict-refuse, cloud.lid-during-button-wait, and every test that starts an offline print go through the new wait. The helper is module text, so every cloud.py test's fingerprint moves; no product behavior changes. |
||
|
|
19ce4d78d2 |
forgetest: the built-in extensions in setup.cloud-disabled-surface
forgectrl gains a table of built-in extensions, with cloud mode as entry one, GET /extensions to serve it, and POST /settings asking it which selections point at the cloud, how to refuse them, and what they fall back to. It also gains an example client under examples/, and its test tokens lose the names of clients nobody is building. setup.cloud-disabled-surface is the gate for "nothing points at the cloud while it is off", so it now holds the list to the settings three times: as found (enabled as cloud_enabled says, the two roles with their providers and fallbacks, each active exactly when its setting selects it), with the cloud off (not enabled, no role active), and as restored. The two refusals are held to the table's words. Its covers name src/builtin.*. The helper lives inside the test's own function, so no other test of the module changes its fingerprint. The unit test's fake daemon serves the route the way builtin.c does, and gains a case with two lists that lie (enabled after the cloud went off; a role that stays active), each of which must fail the test. forgectrl.tokens: the jog token is "forgetest jogger". manifest: forgectrl's examples/** joins the non-behavioral paths. They are clients that run on another computer: outside the image, outside every fingerprint, and outside the coverage lint. Proven. The unit suite passes (421) with no undefined name; the two lying lists fail the test as they should. On the bench reference, forgectrl's registry daemon hot-deployed over image 20260920152153, on a machine with cloud mode on: setup.cloud-disabled-surface PASS through the whole path (off, the sweep, the refusals, the restore), and forgectrl.tokens PASS with the renamed token. Acceptance. setup.cloud-disabled-surface is the gate for forgectrl's built-in table through the settings route. |
||
|
|
888b47abe8 |
forgetest: the hand-back never moves the head across a lost counter frame
Seen on the bench reference, twice in one session: at the end of a passing run the hand-back jogged the head 30 mm into the back-left stop blocks, from a head that had not moved. The baseline compares the kernel's step counters at the end of a run with the start and jogs the head back by the difference. The GRBL controller zeroes those counters at every start (the lens's startup reference) and at every home (home_completed()), and rewrites its anchor, /run/grblhal.homed, each time. Across either event the difference between two counter readings is not a distance the head traveled. It stayed hidden because the counters normally read zero between tests. homing.manual broke that: a manual home zeroes the counters 30 mm out from where the test began, so after the test's own correct return they read -6400, and the next test that restarts the controller (setup.check-flow-verify, then motion.release) ended at 0, "expected -6400", and was "returned" by 30 mm. An operator who jogs the head from a Grbl client and then starts any takeover test from the page would have met the same thing, by whatever distance they had jogged. The baseline's capture() now records the counters' frame, the anchor's inode and mtime. If the frame changed during the run and the test did not vouch for the new one, the hand-back logs that the two readings share no frame, and moves nothing. ctx.counters_rezeroed() is how a test vouches: it now sets rezero_declared beside the position it expects. The start_reads argument it briefly took is gone, since it made the baseline accept counters that nothing after it could live with. homing.manual restarts the controller once more after returning the head, so it ends with the counters at zero where it began, and declares that. events.stream waits for its three places. A stream an earlier test closed keeps its place until the daemon's next write to it (its keep-alive), as documented, so run straight after forgectrl.lease the third stream drew 503. The test now opens the three once they can be opened, and says so in its log. Proven. test_baseline gains test_a_lost_counter_frame_never_moves_the_head: counters at -6400, a new anchor, counters at 0: no jog, no leftover, and the log says why; the same counters with the frame intact are still a displaced head; a declared re-zero is held to the position it declared. With the frame check unable to see the change (the first cut of the test reused an inode inside one clock tick) the case fails with ['position'], which is the old behavior. The unit suite passes. On the bench reference, arranged so a failure would move the head away from the stop: the head jogged to +60 mm (counters 12800), motion.release run, PASS, "the controller re-zeroed its counters during the run ... the head is not moved", and nothing moved. forgectrl.lease then events.stream: two logged waits, PASS. homing.manual then setup.check-flow-verify, the sequence that drove the head into the stop: both PASS with a clean hand-back. |
||
|
|
f306c9983f |
forgetest: the hand-back reads an engine hold again past the engine's next tick
The cooling engine publishes its state once a tick (1 Hz). A /cool/status
read inside the tick after a run ended still shows the run: while a
diagnostic owns the hardware every tick publishes phase "diag" with the
hold set, and a fail tier's hold stands until the tick that ends its
session. The baseline took one read, and by its rule an arm or a hold is
the run's doing, so a test that finished inside that second failed its
hand-back on a hold the engine's next tick cleared.
Seen on the bench reference twice. cooling.aa-offset-calibrate in campaign
c-20260919215024-c402: the diagnostic reported done at 22:07:14 with its
offset measured (15.7 counts, spread 0.7), and the hand-back at 22:07:15
read "cool=diag/armed=False/hold=True ... -> waited" and failed the run;
the test had passed on five images before, the last one earlier the same
day. cooling.fail-tier-stop in c-20260919202934-3d3a, the same way on the
crash fault's hold (
|
||
|
|
dc7170d0d1 |
forgetest: cooling.flow-under-load finds the verdict by offset, not by a tail count
The test counted "heater rise" in the last 300 lines of the forgectrl log before the job, then waited for the count to grow. A tail of fixed length cannot show that: the new verdict line comes in at the bottom as an old one leaves at the top, the count does not move, and a check that verified reads as one that never judged. Seen on the bench reference in campaign c-20260919210959-8780: the engine logged "coolant flow verified (heater rise 11.5 C, dT 9.5 C; laser 1.6 off 13.1)" at 21:37:46, 70 s into the job and inside the wait, and the test failed at 21:39:21 with "the engine published no flow verdict within 120 s". The 21:17:27 verdict of an earlier test sat near the top of the 300-line tail when the test began. Replayed against that log, the old method reads 4 before the job and 4 with the new verdict in the tail; the search from the byte offset finds the 21:37:46 line. The test now takes the log's byte offset before the job (_log_offset, as the fail-tier and liveness tests do) and searches what the file gained since with the verdict expression, stopping only on a line that matches. _log_since reads a file that is now shorter than the offset from its start: a rotation under the test leaves only newer lines. The /logs/tail helper and its line count are gone with their one user. Proven: tests/test_cooling_suite.py FlowVerdictLogTests - the text after the offset alone, the real verdict line through the expression, an old verdict before the offset not taken for the new one, a rotated log read whole, a missing log read as nothing; the cooling host tests pass under Linux, 24 tests. No component source changed, so no pin moves. |
||
|
|
d482e76402 |
installer: a download that resumes and retries, and an install log
A field install failed on "firmware download failed", and worked after a reboot. The download was one bare curl -fL: no retry, no resume, no bound on a stalled transfer, and nothing on the machine recorded what had gone wrong. The download. download_fw makes up to five tries, 5, 15, 30 and 60 seconds apart. Each try resumes the partial file (curl -C -) and is bounded: 20 s to connect, and a transfer below 1 KB/s for 30 s ends the try. The file is written as forgefirm.fw.part and takes its name only when curl finished; the signature check that follows is what vouches for its content. A full disk (curl 23) and a release that is not there (HTTP 404) end the tries at once, because waiting cannot fix them. A partial file the server will not resume (curl 33 or 36, HTTP 416) starts over. The loop is the installer's own rather than curl --retry: the factory curl on the bench reference is 7.69.1, whose --retry does not count a resolver failure or a dropped transfer as retryable, and older factory builds carry older curls. The owner sees the reason in words with each retry, and the final failure says that a re-run goes straight to the download, because the archives are kept. The log. Every run appends to /data/log/forgefirm/install/install.log, in the log tree's own line format (UTC, program "install"): the installer's md5 (which revision ran), the factory version and the slots, the owner's answers, each archive, each download try with curl's exit code, the HTTP code and the reason, the machine's clock at each try (a wrong clock breaks TLS), and after a failed try the address, the default route, the resolver and whether github.com resolves; then the signature and identity checks, the write, the boot selection, and the reason for any failure through die(). The log is appended across runs, so the run that failed is still there after the run that worked. Logging never fails the install. forgectrl's log export carries the directory (forgectrl 0dae758). Proven: tests/test_installer.py runs the installer's own functions under sh against a scripted curl - a clean download, a resolver failure and a dropped transfer that resume to the full file, the tries running out, 404 and a full disk ending them at once, a stale partial file starting over, the TLS reason naming the clock, every log line in the tree format, die() leaving its reason, and an unwritable log not failing the run. The whole host suite, 409 tests, passes under Linux and the coverage lint is clean. Bench: the same functions under the factory firmware's own shell (busybox 1.31.1 ash, the factory slot of the bench reference in a chroot) resumed, retried, ran out of tries and logged exactly as under sh. Acceptance: logs.tree-tail-export now plants a probe file in the install directory and requires it back in the export bundle, its line intact and its MAC and IPv4 address redacted, and requires an install log in the bundle when the machine has one. The installer itself is not on the image: the install page fetches it from master, so it is live with this push. |
||
|
|
8232c8c9fe |
forgetest: image.network-boot - the boot does not wait on the network
A client that waits in ifup's foreground on a server's answer holds the whole machine, because init starts the rest of the boot - sshd, forgectrl, the console login - only after S01networking returns. A field machine sat there forever on a router that refused DHCPv6 (the record is in the meta-openglow commit that drops the DHCPv6 client). Nothing in the catalog looked at the network boot path; this test does. It asserts: wlan0 is in ifupdown's state file and no ifup is running; the console getty is up; udhcpc runs with -b (it leaves ifup after three unanswered discovers) and has been reparented to init; no DHCPv6 client is named in /etc/network/interfaces, running, or on the image, and its hook script is gone; IPv6 on wlan0 is the kernel's own - enabled, router advertisements accepted, a link-local address up. A global address is evidence only: a network whose router advertisement offers no SLAAC prefix gives none. What a hostile server does to a client is a bench drill, not a test. covers is empty by design, as with setup.machine-name: the interfaces file is layer content, in the platform identity of every fingerprint, so a change there already makes every test necessary again. Proven: the host tests for the parsers and the registration (tests/test_image.py), and the whole host suite, 398 tests, under Linux. The test's logic, run read-only on the bench reference against an image that carries the client, fails exactly the four DHCPv6 checks and passes every other one. The test fails on any image built from a meta-openglow that still carries the client, so the kas lock moves to the layer head that drops it. |
||
|
|
cd4c176a87 |
Finish the x32 xy_microsteps default in the baseline test and the stream harness
The x32 default landed in forgetest/baseline.py and the driver, but two callers still judged the machine at x8 and both failed on the host. forgetest/tests/test_baseline.py: setUp seeded the fake machine from the x8 FIXED_SYSFS literals while enforce() compares against fixed_sysfs() of the resolved mode, so x_mode, y_mode, step_freq and ramp_rate read as deviations on a clean machine - 23 failures across BaselineTests and TransientNotLeftoverTests. It seeds from fixed_sysfs() now, and the tick expectations come from it (DEFAULT_TICK) rather than a typed 28160. The xy_mode_of and ref_xy_mode unset/invalid cases expect 32, with an explicit "8" case added that had no coverage. Two reference_preconfig dumps taken on an x8 machine carry xy_microsteps = 8, because the markers are read at the reference's own mode. wait_configured wrote the static CONFIGURED_MARKERS where the function watches configured_markers() of the mode in force, and the held-controller jog typed 221 steps for "4.144 mm", which is 1.036 mm at x32; both derive from the mode now. 69 tests, all pass. scripts/bench/laser_stream_test.py: STEPS_PER_MM was the x8 53.333, so the X-peak check failed at 2133 steps against an expected 533. The whole harness now derives from XY_MICROSTEPS_BASE/DEFAULT the way glowforge.h and baseline.py do, which uncovered five more x8-only expectations behind the first: the machine tick, the fire-gap limit (it grows as sqrt(k), not k - a finer mode shortens the accel interval by sqrt(k) while speeding the tick by k), the rung split in fire_spans, the density period and minimum burst (laser_pulse_ticks is in x8 ticks and the stream scales it, so the config keeps the x8 numbers and the measured lengths scale), and the decel/hold budgets. Run against the null-sink build: all stream emission rules hold. xy_mode_test.py's docstring still described the no-key case as x8 while its own assertions had moved to x32. No behavior change and no acceptance-catalog consequence: these are test expectations and a bench harness, not image component sources. The coverage lint is unchanged at 0 uncovered paths. |
||
|
|
f9f4de31c1 | Fixed artifact exporter | ||
|
|
0eb764bf75 | Added SPDX | ||
|
|
db6015dc81 |
forgetest: cloud.mode-switch opens the lid behind the controller's start, ahead of the hunt
The supervisor holds every controller spawn until the enclosure is closed (forgectrl 0.1.25), and the test opened the lid before it asked for the cloud controller: POST /mode answered "waiting, the lid is open" and the controller never came up. The round trip now switches with the lid closed, polls /mode five times a second, and opens the lid the moment the controller is running. The client requests its connect-time hunt a few seconds after its start, right behind its session, so the hunt still finds the lid open. The order is recorded and judged: the hunt's request line must not be in the client's log when the lid reads open (hunt_before_lid_open), and the test refuses to start with the lid open. The catalog text tells the operator to open the lid at once, with a hand ready on it. Proof. Host: test_cloud_suite drives the round trip with the hunt landing only once the lid reads open, as on the bench, plus the lost race (the hunt requested before the lid opened fails the test with "before the lid was open") and the start with the lid open refused; 8 mode-switch cases green. Bench reference (dev image 20260915001814, forgectrl 0.1.25): cloud.mode-switch PASS in 118 s, the lid open 4 s before the client requested its hunt, no refusal before the hunt's end, the lens homed, the exhaust row unjudged, 5 service motions after the lid closed, $H under gfhome homed in 48.4 s with 9 motion windows. No catalog consequence beyond the test itself: its covers map is unchanged. |
||
|
|
f080d5d9cb |
Catalog: the cloud client's latch at the run, and a hold that never clears
The cloud client changes (python3-gfhardware: the latch unlocks at the run and nowhere earlier, the warm-up is supervised, a live feed must land and finish, fire needs a power byte first, the homing runner always stops). This commit carries the catalog tests that hold the part the bench can see. forgetest/forgetest/suite/cloud.py: - cloud.dark-print (new, kind operator, one press, dark by construction) replaces cloud.verdict-hold. The print arms on the press, waits for the engine's acknowledgment and runs: the laser latch is locked at the button and through the wait, unlocked only for the run (immediately before it starts), locked again when the job ends, and the print completes. The engine's own warm-up release stays proven by cooling.floor-and-warm-up. - cloud.verdict-refuse (new, kind operator, one press, dark): the start gate far above the coolant and cloud_hold_max_s at its minimum keep the armed print under the warm-up past the bound. The client waits with the latch locked (sampled every two seconds), cancels at the bound with its own log line, never runs, closes the armed window, and the print ends ':cancelled'. The settings are restored. - cloud.verdict-hold is retired: its release rode the loop heater at the flow-check duty against a gate one degree above the coolant, inside the upstream reading's noise band, and its run hovered for minutes with the engine's "warm-up stalled" line in the log. The two tests above prove the client's contract without the thermal race. - cloud.oversize-stream already reads cnc/streaming back at both ends of the run, which is the readback the client now insists on. A forced streaming write failure has no seam on the board (the write goes to sysfs as root) and stays a host test. tests/test_cloud_suite.py follows: excerpts and failure cases for the two new tests in place of the retired one's. Proof. Host: the forgetest unit tests. Bench reference: cloud.dark-print passed (locked at the button, unlocked in the run, locked after, ':completed'), cloud.verdict-refuse passed (held 60 s, 31 latch samples all locked, ':cancelled', settings restored); motion.deadman's kill during $H ended the runner on SIGTERM alone; and the retired cloud.verdict-hold passed once more on the new client before it went (the latch locked through an 8 minute warm-up hold, the release ran the print to completion). |