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.
This commit is contained in:
ScottW514
2026-10-12 15:21:06 -04:00
parent dfd32d5fd1
commit ed52e752ee
11 changed files with 1386 additions and 912 deletions
+76 -22
View File
@@ -19,7 +19,7 @@ from forgetest import baseline, catalog
from forgetest.runner import Context, Failed, Run
from forgetest.suite import setup
IDS = ("setup.gate-blocks-controllers", "setup.override-until-reboot",
IDS = ("setup.gate-blocks-controllers",
"setup.advisories-rehash", "setup.account-login", "setup.https-only-writes",
"setup.ssh-until-reboot", "setup.cloud-disabled-surface",
"setup.factory-return", "setup.machine-name", "setup.first-run-flow",
@@ -42,6 +42,8 @@ class RegistrationTests(unittest.TestCase):
self.assertEqual(kinds[tid], "auto", tid)
self.assertNotIn("setup.root-ssh-refused", self.reg) # folded into ssh-until-reboot
self.assertNotIn("setup.wizard-first-run", self.reg) # split into the flow and the page
self.assertNotIn("setup.override-until-reboot", self.reg) # the gate has nothing to lift
self.assertNotIn("setup.sheet", self.reg) # the two burns, each on its own scrap
def test_the_record_swapping_tests_are_takeovers(self):
for tid in ("setup.gate-blocks-controllers", "setup.advisories-rehash",
@@ -61,13 +63,36 @@ class RegistrationTests(unittest.TestCase):
self.assertIn("setup.check-sensors", self.reg["setup.mirror"].requires)
def test_flag_of_reads_the_status_document(self):
w = {"required": ["airflow", {"id": "laser.floor", "reason": "the tube was replaced"}],
"recommended": [{"id": "laser.focus", "reason": "the tray was replaced"}]}
self.assertEqual(setup.flag_of(w, "required", "laser.floor"), "the tube was replaced")
self.assertEqual(setup.flag_of(w, "recommended", "laser.focus"), "the tray was replaced")
self.assertIsNone(setup.flag_of(w, "required", "airflow")) # a plain id is the table, not a flag
self.assertIsNone(setup.flag_of(w, "required", "laser.focus"))
self.assertIsNone(setup.flag_of({}, "recommended", "x"))
w = {"recommended": [{"id": "laser.floor", "reason": "the tube was replaced"},
{"id": "laser.focus", "reason": "the tray was replaced"},
{"id": "airflow", "reason": "not run yet"}, "stray"]}
self.assertEqual(setup.flag_of(w, "laser.floor"), "the tube was replaced")
self.assertEqual(setup.flag_of(w, "laser.focus"), "the tray was replaced")
self.assertEqual(setup.flag_of(w, "airflow"), "not run yet")
self.assertIsNone(setup.flag_of(w, "stray")) # only an entry with an id counts
self.assertIsNone(setup.flag_of(w, "cameras"))
self.assertIsNone(setup.flag_of({}, "x"))
self.assertEqual(setup.recommended(w), {"laser.floor": "the tube was replaced",
"laser.focus": "the tray was replaced",
"airflow": "not run yet"})
def test_the_gate_parts_and_the_optional_steps_are_never_recommended(self):
self.assertFalse(set(setup.RECOMMENDED_STEPS) & set(setup.NEVER_RECOMMENDED))
self.assertFalse(set(setup.RECOMMENDED_STEPS) & set(setup.REQUIRED_STEPS))
for wid in ("advisories", "account", "airflow.intakes", "laser.floor", "motion.envelope"):
self.assertIn(wid, setup.NEVER_RECOMMENDED)
for wid in ("lens.travel", "laser.focus", "airflow", "switches"):
self.assertIn(wid, setup.RECOMMENDED_STEPS)
self.assertEqual(setup.FORM_WIZARDS, ("advisories", "account", "preferences", "machine", "cloud"))
# in the order the gate's reason names them
self.assertEqual(setup.REQUIRED_STEPS, ("preferences", "machine", "cloud"))
def test_the_gate_test_starts_from_an_open_gate_and_the_intake_tests_from_a_count(self):
reg = self.reg
self.assertIs(reg["setup.gate-blocks-controllers"].precheck, setup.gate_is_open)
from forgetest.suite import setup_dark
for tid in ("setup.check-intakes", "setup.check-airflow"):
self.assertIs(reg[tid].precheck, setup_dark.intakes_counted, tid)
def test_the_first_run_flow_runs_unattended_with_the_fixture_and_the_page_walk_does_not(self):
flow = self.reg["setup.first-run-flow"]
@@ -89,14 +114,14 @@ class RegistrationTests(unittest.TestCase):
for want in (("forgectrl", "src/ui/wizard.*"), ("forgectrl", "src/ui/md.js"), ("forgectrl", "src/tls.*")):
self.assertIn(want, page, want)
def test_the_only_attended_tests_need_a_workstation_or_the_sheet(self):
# With the bench actuator up, three setup tests keep a
# person: the page walk, the print from the Glowforge app, and
# the sheet. Everything else runs from the queue.
def test_the_only_attended_tests_need_a_workstation_or_a_scrap(self):
# With the bench actuator up, four setup tests keep a person: the
# page walk, the print from the Glowforge app, and the two burns,
# each on a fresh scrap. Everything else runs from the queue.
attended = sorted(tid for tid, t in self.reg.items() if tid.startswith("setup.")
and t.kind != "auto" and not t.fixture_runnable(("button", "lid", "interlock")))
self.assertEqual(attended, ["setup.cloud-header-capture", "setup.first-run-page",
"setup.sheet"])
"setup.floor", "setup.focus"])
def test_the_factory_return_never_runs_the_return(self):
import inspect
@@ -152,13 +177,43 @@ class RecordTests(unittest.TestCase):
def test_without_wizards_keeps_consent_and_account(self):
rec = setup.complete_record(self.STATUS)
rec["flags"] = {"machine": {"level": "required", "reason": "x"}}
rec["flags"] = {"machine": {"reason": "x"}}
out = setup.record_without_wizards(rec)
self.assertEqual(out["wizards"], {})
self.assertEqual(out["flags"], {})
self.assertEqual(out["advisories"], rec["advisories"])
self.assertEqual(out["account"], rec["account"])
self.assertEqual(rec["flags"], {"machine": {"level": "required", "reason": "x"}}) # a copy
self.assertEqual(rec["flags"], {"machine": {"reason": "x"}}) # a copy
def test_with_required_steps_keeps_them_and_nothing_else(self):
rec = setup.complete_record(self.STATUS)
rec["wizards"]["machine"]["result"] = {"model": "basic"}
rec["wizards"]["switches"] = {"version": 1}
rec["flags"] = {"machine": {"reason": "the head was replaced"}}
out = setup.record_with_required_steps(rec)
self.assertEqual(sorted(out["wizards"]), ["cloud", "machine", "preferences"])
self.assertEqual(out["wizards"]["machine"]["result"], {"model": "basic"}) # kept from the record
self.assertEqual(out["wizards"]["preferences"]["version"], 1) # written at version 1
self.assertEqual(out["flags"], {})
self.assertEqual(out["account"], rec["account"])
self.assertIn("switches", rec["wizards"]) # a copy
def test_without_consent_keeps_the_account_and_the_wizards(self):
rec = setup.complete_record(self.STATUS)
out = setup.record_without_consent(rec)
self.assertEqual(out["advisories"], {})
self.assertNotIn("acceptance", out)
self.assertEqual(out["account"], rec["account"])
self.assertEqual(out["wizards"], rec["wizards"])
self.assertIn("pressed_at", rec["acceptance"]) # a copy
def test_without_account_keeps_the_consent(self):
rec = setup.complete_record(self.STATUS)
out = setup.record_without_account(rec)
self.assertNotIn("account", out)
self.assertEqual(out["advisories"], rec["advisories"])
self.assertEqual(out["acceptance"], rec["acceptance"])
self.assertEqual(rec["account"]["name"], "bench") # a copy
def test_write_file_and_remove(self):
tmp = tempfile.mkdtemp(prefix="forgetest-comm-")
@@ -180,7 +235,6 @@ class RecordTests(unittest.TestCase):
try:
self.assertEqual(setup.record_path(), "/tmp/ffdata/setup.json")
self.assertEqual(setup.users_path(), "/tmp/ffdata/users")
self.assertEqual(setup.override_path(), "/tmp/ffrun/setup-override")
self.assertEqual(setup.ssh_flag_path(), "/tmp/ffrun/ssh-enabled")
finally:
os.environ.pop("FORGECTRL_DATA_DIR", None)
@@ -266,7 +320,7 @@ class GatedSettleTests(unittest.TestCase):
def test_gated_returns_at_once(self):
self.fake.state["mode"] = {"mode": "grbl", "controller": "gated", "pid": 0, "motion": "unverified",
"gated": True, "why": "setup required: machine"}
"gated": True, "why": "the intake fans are not counted"}
lines = []
body = baseline.Baseline(lines.append).wait_settled(timeout=8)
self.assertEqual(body["controller"], "gated")
@@ -275,14 +329,14 @@ class GatedSettleTests(unittest.TestCase):
class FirstRunSeedTests(unittest.TestCase):
"""The record the first-run tests install: a first run behind the
checks and the sheet."""
checks and the burns."""
STATUS = {"wizards": [{"id": "advisories", "version": 1, "class": "form"},
{"id": "account", "version": 1, "class": "form"},
{"id": "cloud", "version": 1, "class": "form"},
{"id": "switches", "version": 1, "class": "dark"},
{"id": "motion", "version": 2, "class": "dark"},
{"id": "sheet.frame", "version": 1, "class": "live"}]}
{"id": "laser.focus", "version": 1, "class": "live"}]}
def test_no_consent_no_account_not_complete_and_every_check_done(self):
base = {"created": "2026-09-01T00:00:00Z", "completed": "2026-09-02T00:00:00Z",
@@ -296,14 +350,14 @@ class FirstRunSeedTests(unittest.TestCase):
self.assertEqual(rec["advisories"], {})
self.assertEqual(rec["created"], "2026-09-01T00:00:00Z")
self.assertEqual(rec["flags"], {"flow_thin": True})
self.assertEqual(sorted(rec["wizards"]), ["motion", "sheet.frame", "switches"])
self.assertEqual(sorted(rec["wizards"]), ["laser.focus", "motion", "switches"])
self.assertEqual(rec["wizards"]["switches"]["result"], {"lid": True}) # kept from the base
self.assertEqual(rec["wizards"]["motion"]["version"], 2) # written at the catalog version
def test_without_a_base_record(self):
rec = setup.first_run_record(self.STATUS, None)
self.assertEqual(sorted(rec["wizards"]), ["motion", "sheet.frame", "switches"])
self.assertEqual(rec["wizards"]["sheet.frame"]["version"], 1)
self.assertEqual(sorted(rec["wizards"]), ["laser.focus", "motion", "switches"])
self.assertEqual(rec["wizards"]["laser.focus"]["version"], 1)
class SshdPolicyTests(unittest.TestCase):