mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-15 17:19:55 -07:00
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.
This commit is contained in:
@@ -3,16 +3,15 @@
|
||||
# https://community.openglow.org
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""The setup's two burns in the catalog: two live tests on the button with
|
||||
a scrap at hand, their covers naming the live runner, the renderer, the
|
||||
sender, the page, and the driver's laser module; and each burn against a
|
||||
"""The setup's focus burn in the catalog: a live test on the button with
|
||||
a scrap at hand, its covers naming the live runner, the renderer, the
|
||||
sender, the page, and the driver's laser module; and the burn against a
|
||||
scripted daemon: the preview and the program read, the start, the
|
||||
prompts answered in order (the gantry stops, the head at home, the scrap,
|
||||
its thickness in the machine's units, the arm, the machine's own press
|
||||
prompt, the pick), the witnesses judged, the record read at version 1,
|
||||
the setting the burn wrote put back as found, and for the focus burn the
|
||||
controller started again on the edge and the window put back. The floor
|
||||
starts only with Focus on the record and a scrap the lens reaches."""
|
||||
the setting the burn wrote put back as found, and the controller started
|
||||
again on the edge and the window put back."""
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
@@ -23,7 +22,7 @@ from forgetest import catalog # noqa: E402
|
||||
from forgetest.runner import Aborted, Context, Failed, Run # noqa: E402
|
||||
from forgetest.suite import setup_burns, setup_dark # noqa: E402
|
||||
|
||||
LIVE = ["laser.focus", "laser.floor"]
|
||||
LIVE = ["laser.focus"]
|
||||
EMISSION = {"hv_max": 610, "laser_on_samples": 900, "thermopile_delta": 600, "beam_sensor_seen": True,
|
||||
"lit_s": 30.0}
|
||||
SPM = 2.922 # the lens screw, half-steps per mm ($102)
|
||||
@@ -55,7 +54,7 @@ def ladder_z(edge, below, above):
|
||||
return [round(edge + round(above - (above + below) * i / 11) / SPM, 2) for i in range(12)]
|
||||
|
||||
|
||||
# Each burn as the daemon runs it: its prompts in order (id, kind,
|
||||
# The burn as the daemon runs it: its prompts in order (id, kind,
|
||||
# options) and the result it ends with. The press prompt is the
|
||||
# machine's own: nothing answers it, the discharge closes it. The focus
|
||||
# burn's window and ladder are the ones the settings held at its start.
|
||||
@@ -69,14 +68,10 @@ SCRIPT = {
|
||||
{"z_passes": [100, 101], "emission": EMISSION, "pick": 11, "picked_lines": [11, 11],
|
||||
"pick_half_steps": -8, "edge_z_mm": 6.96, "steps_per_mm": SPM, "max_height_mm": 13.81,
|
||||
"focus_range_mm": {"min": 2.17, "max": 13.81}, "summary": "Line 11."}),
|
||||
"laser.floor": (SETUP + [("floor-arm", "continue", ["Continue"]), PRESS,
|
||||
("floor-pick", "choice", [str(2 * i) for i in range(1, 13)] + ["None"])],
|
||||
{"z_passes": [100, 101], "emission": EMISSION, "faintest_density": 8.0,
|
||||
"floor_density": 10.0, "summary": "The floor is 10."}),
|
||||
}
|
||||
# What each burn writes when it ends.
|
||||
WRITES = {"laser.focus": {"lens_hall_edge_z_mm": "6.96"}, "laser.floor": {"laser_floor_density": "10"}}
|
||||
# The machine as the burns find it: its own edge, the window the Lens
|
||||
# What the burn writes when it ends.
|
||||
WRITES = {"laser.focus": {"lens_hall_edge_z_mm": "6.96"}}
|
||||
# The machine as the burn finds it: its own edge, the window the Lens
|
||||
# travel check wrote, its floor.
|
||||
FOUND = {"lens_hall_edge_z_mm": "4.10", "lens_stop_below_steps": "14", "lens_stop_above_steps": "20",
|
||||
"laser_floor_density": "12", "ui_units": "metric"}
|
||||
@@ -89,27 +84,23 @@ class RegistrationTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.reg = catalog.load_suite()
|
||||
|
||||
def test_two_live_tests_on_the_button_with_a_scrap_at_hand(self):
|
||||
for tid in ("setup.focus", "setup.floor"):
|
||||
t = self.reg[tid]
|
||||
self.assertEqual(t.kind, "live", tid)
|
||||
self.assertEqual(t.mode, "grbl", tid)
|
||||
self.assertEqual(t.hardware, "takeover", tid)
|
||||
self.assertEqual(list(t.actions), ["button"], tid)
|
||||
self.assertEqual(list(t.hands), ["scrap"], tid)
|
||||
self.assertFalse(t.fixture_runnable(("button", "lid", "interlock")), tid)
|
||||
self.assertIsNone(getattr(t, "precheck", None), tid)
|
||||
self.assertEqual(list(self.reg["setup.focus"].requires),
|
||||
def test_a_live_test_on_the_button_with_a_scrap_at_hand(self):
|
||||
t = self.reg["setup.focus"]
|
||||
self.assertEqual(t.kind, "live")
|
||||
self.assertEqual(t.mode, "grbl")
|
||||
self.assertEqual(t.hardware, "takeover")
|
||||
self.assertEqual(list(t.actions), ["button"])
|
||||
self.assertEqual(list(t.hands), ["scrap"])
|
||||
self.assertFalse(t.fixture_runnable(("button", "lid", "interlock")))
|
||||
self.assertIsNone(getattr(t, "precheck", None))
|
||||
self.assertEqual(list(t.requires),
|
||||
["forgectrl.auth", "setup.check-motion", "laser.emission-witness", "setup.check-lens"])
|
||||
# the floor burns at the focus Focus measured, and refuses without it
|
||||
self.assertEqual(list(self.reg["setup.floor"].requires),
|
||||
["forgectrl.auth", "setup.check-motion", "laser.emission-witness", "setup.focus"])
|
||||
|
||||
def test_the_sheet_is_gone(self):
|
||||
self.assertFalse([i for i in self.reg if i.startswith("setup.sheet")])
|
||||
|
||||
def test_covers_the_live_runner_the_renderer_the_sender_the_page_and_the_driver(self):
|
||||
for tid in ("setup.focus", "setup.floor"):
|
||||
for tid in ("setup.focus",):
|
||||
covers = set(self.reg[tid].covers)
|
||||
for want in (("forgectrl", "src/wizlive.*"), ("forgectrl", "src/sheet.*"),
|
||||
("forgectrl", "src/jobstream.*"), ("forgectrl", "src/curverec.*"),
|
||||
@@ -120,7 +111,7 @@ class RegistrationTests(unittest.TestCase):
|
||||
self.assertIn(want, covers, "%s lacks %s" % (tid, want))
|
||||
|
||||
def test_the_steps_ask_for_a_scrap_in_the_corner_and_one_press(self):
|
||||
for tid, size in (("setup.focus", "78 x 68 mm"), ("setup.floor", "86 x 68 mm")):
|
||||
for tid, size in (("setup.focus", "78 x 68 mm"),):
|
||||
text = " ".join(self.reg[tid].steps)
|
||||
self.assertIn(size, text, tid)
|
||||
self.assertIn("back-left corner", text, tid)
|
||||
@@ -130,10 +121,9 @@ class RegistrationTests(unittest.TestCase):
|
||||
|
||||
class ScriptedDaemon:
|
||||
"""The wizard runner as the suite sees it: /wiz/<id>/start opens the
|
||||
first prompt (the floor ends at once, with the wizard's error, when the
|
||||
record carries no Focus), each answer opens the next (a thickness that
|
||||
is not a plain number, out of range in the machine's units, or, for
|
||||
the floor, outside the lens reach opens it again), the press prompt
|
||||
first prompt, each answer opens the next (a thickness that is not a
|
||||
plain number or out of range in the machine's units opens it again),
|
||||
the press prompt
|
||||
closes on the poll after the one that served it, the last answer ends
|
||||
the burn with its result and its writes, /wiz/dark reports it, GET /wiz
|
||||
carries the versions (the record carries Focus already, as the bench's
|
||||
@@ -234,11 +224,6 @@ class ScriptedDaemon:
|
||||
return 404, {"error": "no such wizard"}
|
||||
self.order.append(wid)
|
||||
self.settings_at_start[wid] = dict(s)
|
||||
if wid == "laser.floor" and not self.versions.get("laser.focus"):
|
||||
# the floor burns at the focus Focus measured: no prompt, no motion
|
||||
self.dark.update({"id": wid, "running": False, "phase": "", "prompt": None, "result": None,
|
||||
"error": "run Focus first: the floor burns at the focus it measures"})
|
||||
return 200, {"started": True, "id": wid}
|
||||
s.update(self.start_writes.get(wid, {}))
|
||||
self.lens_at_start = lens_block(s)
|
||||
self.queue = list(SCRIPT[wid][0])
|
||||
@@ -259,9 +244,7 @@ class ScriptedDaemon:
|
||||
mm = float(value.strip()) * (25.4 if imperial else 1.0)
|
||||
except ValueError:
|
||||
mm = -1 # "1/8" or "3 mm": not a plain number
|
||||
lens = self.lens_at_start
|
||||
if not 0 < mm <= 30 or (wid == "laser.floor"
|
||||
and not lens["reach_min"] <= mm <= lens["reach_max"]):
|
||||
if not 0 < mm <= 30:
|
||||
self.open_next() # the same prompt again, a new sequence number
|
||||
return 200, {"ok": True}
|
||||
self.thickness = mm
|
||||
@@ -370,66 +353,12 @@ class BurnRunTests(unittest.TestCase):
|
||||
self.assert_restarted_on_found({"lens_hall_edge_z_mm": "4.10", "lens_stop_below_steps": None,
|
||||
"lens_stop_above_steps": None})
|
||||
|
||||
def test_the_floor_burn(self):
|
||||
run = self.run_test("setup.floor")
|
||||
d = self.daemon
|
||||
self.assertEqual(d.order, ["laser.floor"])
|
||||
self.assertEqual(d.answers["laser.floor"], ["Yes", HOME, "Continue", "3.2", "Continue", "8"])
|
||||
self.assertEqual(d.previews, ["laser.floor"])
|
||||
self.assertEqual(run.evidence["result"]["floor_density"], 10.0)
|
||||
self.assertEqual(run.evidence["settings_before"], {"laser_floor_density": "12"})
|
||||
self.assertEqual(run.evidence["focus_on_record"], 1)
|
||||
self.assertEqual(run.evidence["program_tail"]["laser.floor"], ["G0 Z4.10", "G0 X0 Y0", "G92.1", "M2"])
|
||||
self.assert_found()
|
||||
# the controller loads the floor at every arm: nothing to restart
|
||||
self.assertEqual(d.lever_posts(), [])
|
||||
|
||||
def test_the_floor_needs_focus_on_the_record(self):
|
||||
# The floor burns at the focus Focus measured, and the wizard
|
||||
# refuses without it: the test says so before the scrap is asked
|
||||
# for, and nothing starts.
|
||||
self.daemon.versions = {}
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.assertIn("the record carries no Focus", str(cm.exception))
|
||||
self.assertEqual(self.daemon.order, [])
|
||||
self.assertEqual(self.daemon.previews, [])
|
||||
self.assert_found()
|
||||
|
||||
def test_the_floors_own_refusal_without_focus_ends_the_run(self):
|
||||
# Past the test's own reading (a record that lost Focus in
|
||||
# between): the wizard's refusal ends the run with its word, before
|
||||
# any prompt.
|
||||
real = self.daemon.on_get
|
||||
|
||||
def on_get(path, q):
|
||||
if path == "/wiz" and not self.daemon.order:
|
||||
return 200, {"versions": {"laser.focus": 1}, "recommended": [], "completed": True}
|
||||
return real(path, q)
|
||||
self.fake.on_get = on_get
|
||||
self.daemon.versions = {}
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.assertIn("run Focus first", str(cm.exception))
|
||||
self.assertEqual(self.daemon.order, ["laser.floor"])
|
||||
self.assertEqual(self.daemon.answers, {})
|
||||
self.assert_found()
|
||||
|
||||
def test_a_scrap_the_lens_does_not_reach_fails_before_the_floor(self):
|
||||
# An edge high enough that the lens cannot focus 3.2 mm above the
|
||||
# tray: the floor would refuse the thickness, so nothing starts.
|
||||
self.fake.state["settings"]["lens_hall_edge_z_mm"] = "9.50"
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.assertIn("leaves out the 3.2 mm scrap", str(cm.exception))
|
||||
self.assertEqual(self.daemon.order, [])
|
||||
|
||||
def test_a_program_that_keeps_its_g92_origin_fails_before_the_burn(self):
|
||||
# The tail clears the G92 origin, which the controller would
|
||||
# otherwise carry to its next start under a sender's own offsets.
|
||||
self.daemon.tail = ["G0 X0 Y0", "M2"]
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assertIn("not the tail", str(cm.exception))
|
||||
self.assertIn("G92.1", str(cm.exception))
|
||||
self.assertEqual(self.daemon.order, [])
|
||||
@@ -439,8 +368,8 @@ class BurnRunTests(unittest.TestCase):
|
||||
# The number prompts read ui_units: on an imperial machine the
|
||||
# thickness is 0.125 in and the record carries 3.175 mm.
|
||||
self.fake.state["settings"]["ui_units"] = "imperial"
|
||||
run = self.run_test("setup.floor")
|
||||
self.assertEqual(self.daemon.answers["laser.floor"][3], "0.125")
|
||||
run = self.run_test("setup.focus")
|
||||
self.assertEqual(self.daemon.answers["laser.focus"][3], "0.125")
|
||||
self.assertEqual(run.evidence["thickness_answer"], "0.125")
|
||||
self.assertEqual(run.evidence["result"]["thickness_mm"], 3.175)
|
||||
|
||||
@@ -449,44 +378,44 @@ class BurnRunTests(unittest.TestCase):
|
||||
# the test ends the burn rather than answer the same thing forever.
|
||||
self.daemon.units = "imperial"
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assertIn("asked thickness again", str(cm.exception))
|
||||
self.assertEqual(self.daemon.answers["laser.floor"], ["Yes", HOME, "Continue", "3.2"])
|
||||
self.assertEqual(self.daemon.aborted, ["laser.floor"])
|
||||
self.assertEqual(self.daemon.answers["laser.focus"], ["Yes", HOME, "Continue", "3.2"])
|
||||
self.assertEqual(self.daemon.aborted, ["laser.focus"])
|
||||
self.assert_found()
|
||||
|
||||
def test_a_prompt_the_test_does_not_answer_ends_the_burn(self):
|
||||
# The motors found released: the burn asks for the head pushed home
|
||||
# by hand, which the bench never needs.
|
||||
SCRIPT["laser.floor"][0].insert(1, ("home-push", "continue", ["Continue"]))
|
||||
SCRIPT["laser.focus"][0].insert(1, ("home-push", "continue", ["Continue"]))
|
||||
try:
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
finally:
|
||||
SCRIPT["laser.floor"][0].pop(1)
|
||||
SCRIPT["laser.focus"][0].pop(1)
|
||||
self.assertIn("asked home-push, which the test does not answer", str(cm.exception))
|
||||
self.assertEqual(self.daemon.aborted, ["laser.floor"])
|
||||
self.assertEqual(self.daemon.aborted, ["laser.focus"])
|
||||
self.assert_found()
|
||||
|
||||
def test_a_dark_burn_fails_and_still_restores(self):
|
||||
self.daemon.patch["laser.floor"] = {"emission": {"hv_max": 0, "laser_on_samples": 0, "thermopile_delta": 0}}
|
||||
self.daemon.patch["laser.focus"] = {"emission": {"hv_max": 0, "laser_on_samples": 0, "thermopile_delta": 0}}
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assertIn("the tube current never rose", str(cm.exception))
|
||||
self.assert_found()
|
||||
|
||||
def test_a_burn_with_a_silent_beam_sensor_passes(self):
|
||||
# A head whose beam sensor gives no output: the tube current and
|
||||
# LASER_ON saw the beam, so the burn counts.
|
||||
self.daemon.patch["laser.floor"] = {"emission": dict(EMISSION, thermopile_delta=0,
|
||||
self.daemon.patch["laser.focus"] = {"emission": dict(EMISSION, thermopile_delta=0,
|
||||
beam_sensor_seen=False)}
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assert_found()
|
||||
|
||||
def test_a_burn_that_does_not_record_the_beam_sensor_fails(self):
|
||||
self.daemon.patch["laser.floor"] = {"emission": {"hv_max": 610, "laser_on_samples": 900}}
|
||||
self.daemon.patch["laser.focus"] = {"emission": {"hv_max": 610, "laser_on_samples": 900}}
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assertIn("the result does not record the beam sensor", str(cm.exception))
|
||||
self.assert_found()
|
||||
|
||||
@@ -540,15 +469,14 @@ class BurnRunTests(unittest.TestCase):
|
||||
# refused before the burn starts: nothing burns.
|
||||
self.daemon.stray_z = 12.6
|
||||
with self.assertRaises(Failed) as cm:
|
||||
self.run_test("setup.floor")
|
||||
self.run_test("setup.focus")
|
||||
self.assertIn("outside the lens window", str(cm.exception))
|
||||
self.assertIn("12.6", str(cm.exception))
|
||||
self.assertEqual(self.daemon.order, [])
|
||||
self.assert_found()
|
||||
|
||||
def test_the_result_keys_the_tests_require(self):
|
||||
# every key either test reads is one the scripted burn reports
|
||||
self.assertTrue(set(setup_burns.FLOOR_KEYS) <= set(SCRIPT["laser.floor"][1]) | {"thickness_mm"})
|
||||
# every key the test reads is one the scripted burn reports
|
||||
self.assertTrue(set(setup_burns.FOCUS_KEYS)
|
||||
<= set(SCRIPT["laser.focus"][1]) | {"thickness_mm", "window", "ladder_z"})
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
"""The setup's checks in the catalog: registered as automatic tests, the
|
||||
takeover ones marked so, every covers map naming the check runner, and
|
||||
the two that put the intake fan count back as found refused unless it is
|
||||
set; and the check driver itself against the fake daemon: a start, the
|
||||
the one that puts the intake fan count back as found refused unless it
|
||||
is set; and the check driver itself against the fake daemon: a start, the
|
||||
run polled to its end with no question asked, the result taken, the
|
||||
record read at version 1, the settings read as before or put back as
|
||||
found, and the controller started again on a lens window put back."""
|
||||
@@ -19,11 +19,10 @@ import helpers # noqa: E402
|
||||
from forgetest import catalog # noqa: E402
|
||||
from forgetest.runner import Context, Run # noqa: E402
|
||||
|
||||
IDS = ["setup.check-switches", "setup.check-sensors", "setup.check-airflow", "setup.check-intakes",
|
||||
"setup.check-cameras", "setup.check-motion", "setup.check-lens", "setup.check-flow-verify"]
|
||||
# The checks that put the intake fan count back as found: they start only
|
||||
# with it set, so a put-back never leaves the gate closed behind them.
|
||||
COUNTED = ("setup.check-airflow", "setup.check-intakes")
|
||||
IDS = ["setup.check-switches", "setup.check-airflow", "setup.check-motion", "setup.check-lens"]
|
||||
# The check that puts the intake fan count back as found: it starts only
|
||||
# with it set, so a put-back never leaves the gate closed behind it.
|
||||
COUNTED = ("setup.check-airflow",)
|
||||
|
||||
|
||||
class RegistrationTests(unittest.TestCase):
|
||||
@@ -35,11 +34,9 @@ class RegistrationTests(unittest.TestCase):
|
||||
# bench fixture performs; the rest need no hands at all.
|
||||
for tid in IDS:
|
||||
self.assertIn(tid, self.reg)
|
||||
for tid in ("setup.check-switches", "setup.check-cameras"):
|
||||
self.assertEqual(self.reg[tid].kind, "operator", tid)
|
||||
self.assertTrue(self.reg[tid].fixture_runnable(("button", "lid", "interlock")), tid)
|
||||
for tid in ("setup.check-sensors", "setup.check-airflow", "setup.check-intakes",
|
||||
"setup.check-motion", "setup.check-lens", "setup.check-flow-verify"):
|
||||
self.assertEqual(self.reg["setup.check-switches"].kind, "operator")
|
||||
self.assertTrue(self.reg["setup.check-switches"].fixture_runnable(("button", "lid", "interlock")))
|
||||
for tid in ("setup.check-airflow", "setup.check-motion", "setup.check-lens"):
|
||||
self.assertEqual(self.reg[tid].kind, "auto", tid)
|
||||
|
||||
def test_no_check_asks_the_operator_to_prepare_anything(self):
|
||||
@@ -65,8 +62,7 @@ class RegistrationTests(unittest.TestCase):
|
||||
fake.stop()
|
||||
|
||||
def test_the_checks_that_take_the_machine_say_so(self):
|
||||
for tid in ("setup.check-airflow", "setup.check-intakes", "setup.check-motion", "setup.check-lens",
|
||||
"setup.check-flow-verify"):
|
||||
for tid in ("setup.check-airflow", "setup.check-motion", "setup.check-lens"):
|
||||
self.assertEqual(self.reg[tid].hardware, "takeover", tid)
|
||||
|
||||
def test_the_lens_check_covers_the_finder_and_the_frame(self):
|
||||
@@ -74,9 +70,6 @@ class RegistrationTests(unittest.TestCase):
|
||||
for want in (("forgectrl", "src/lensfind.*"), ("forgectrl", "src/lens.*")):
|
||||
self.assertIn(want, covers)
|
||||
self.assertIn("setup.check-motion", self.reg["setup.check-lens"].requires)
|
||||
covers = set(self.reg["setup.check-intakes"].covers)
|
||||
for want in (("forgectrl", "src/cool.c"), ("forgectrl", "src/airflow.*")):
|
||||
self.assertIn(want, covers)
|
||||
|
||||
def test_every_check_covers_the_runner_and_the_page(self):
|
||||
for tid in IDS:
|
||||
@@ -85,91 +78,6 @@ class RegistrationTests(unittest.TestCase):
|
||||
self.assertIn(("forgectrl", "src/ui/wizard.*"), covers, tid)
|
||||
|
||||
|
||||
class SensorsCheckTests(unittest.TestCase):
|
||||
"""The check driver against a scripted daemon."""
|
||||
|
||||
RESULT = {"coolant_down_c": 22.4, "coolant_up_c": 21.9, "chassis_c": 28.0, "soc_c": 45.2,
|
||||
"lid_ir_max": [110, 120, 105, 118], "accel_events": 0, "laser_pgood": 1,
|
||||
"hv_current_max": 0, "exhaust_rpm_idle": 3400, "intake_rpm_idle": 2200}
|
||||
|
||||
def setUp(self):
|
||||
self.fake = helpers.FakeForgectrl().start()
|
||||
self.fake.state["settings"].update({"cool_temp_offset_c": "0.5"})
|
||||
self.dark = {"id": "", "running": False, "phase": "", "progress": 0, "elapsed_s": 0,
|
||||
"log": [], "prompt": None, "result": None, "error": "",
|
||||
"shots": {"lid": False, "head": False}}
|
||||
self.versions = {}
|
||||
self.answers = []
|
||||
self.polls = 0
|
||||
|
||||
def on_get(path, q):
|
||||
if path == "/wiz/dark":
|
||||
# One poll sees the sampling; the next sees the result.
|
||||
if self.dark["running"]:
|
||||
self.polls += 1
|
||||
if self.polls >= 2:
|
||||
self.dark.update({"running": False, "prompt": None, "progress": 100,
|
||||
"result": dict(self.RESULT)})
|
||||
self.versions["sensors"] = 1
|
||||
return 200, dict(self.dark)
|
||||
if path == "/wiz":
|
||||
return 200, {"versions": dict(self.versions), "required": [], "completed": True}
|
||||
return None
|
||||
|
||||
def on_post(path, form):
|
||||
s = self.fake.state["settings"]
|
||||
if path == "/wiz/sensors/start":
|
||||
self.dark.update({"id": "sensors", "running": True, "phase": "sampling",
|
||||
"prompt": None})
|
||||
return 200, {"started": True, "id": "sensors"}
|
||||
if path == "/wiz/sensors/answer":
|
||||
self.answers.append(dict(form))
|
||||
return 409, {"error": "no such prompt is open"}
|
||||
if path == "/settings":
|
||||
for k, v in form.items():
|
||||
if v == "":
|
||||
s.pop(k, None)
|
||||
else:
|
||||
s[k] = v
|
||||
return 200, dict(s)
|
||||
return None
|
||||
self.fake.on_get = on_get
|
||||
self.fake.on_post = on_post
|
||||
|
||||
def tearDown(self):
|
||||
self.fake.stop()
|
||||
|
||||
def test_start_no_question_result_and_settings_untouched(self):
|
||||
t = catalog.load_suite()["setup.check-sensors"]
|
||||
run = Run("test", t.id, t.title)
|
||||
t.fn(Context(run, None, t))
|
||||
self.assertIn(("/wiz/sensors/start", {}), [(p, f) for p, f in self.fake.posts])
|
||||
self.assertEqual(self.answers, [])
|
||||
self.assertEqual(run.evidence["result"]["laser_pgood"], 1)
|
||||
self.assertEqual(run.evidence["settings_before"].get("cool_temp_offset_c"), "0.5")
|
||||
self.assertEqual(self.fake.state["settings"]["cool_temp_offset_c"], "0.5")
|
||||
self.assertNotIn("/settings", [p for p, _ in self.fake.posts])
|
||||
|
||||
def test_a_question_fails_the_check(self):
|
||||
t = catalog.load_suite()["setup.check-sensors"]
|
||||
run = Run("test", t.id, t.title)
|
||||
real_get = self.fake.on_get
|
||||
|
||||
def on_get(path, q):
|
||||
if path == "/wiz/dark" and self.dark["running"]:
|
||||
d = dict(self.dark)
|
||||
d["prompt"] = {"seq": 3, "id": "room-temp", "kind": "number", "text": "?",
|
||||
"options": ["Set", "Skip"]}
|
||||
return 200, d
|
||||
return real_get(path, q)
|
||||
self.fake.on_get = on_get
|
||||
with self.assertRaises(Exception) as cm:
|
||||
t.fn(Context(run, None, t))
|
||||
self.assertIn("asked a question", str(cm.exception))
|
||||
self.assertEqual(self.answers, [])
|
||||
self.assertIn(("/wiz/sensors/abort", {}), [(p, f) for p, f in self.fake.posts])
|
||||
|
||||
|
||||
class AirflowCheckTests(unittest.TestCase):
|
||||
"""setup.check-airflow's judge against a scripted daemon: the check
|
||||
writes its floors, and the intake fan count is the machine's. Intake
|
||||
@@ -522,91 +430,5 @@ class LensCheckTests(ScriptedCheck):
|
||||
self.assertEqual(run.evidence["restart"], {"mode": "cloud", "pid": 100})
|
||||
|
||||
|
||||
class IntakesCheckTests(ScriptedCheck):
|
||||
"""setup.check-intakes's judge: the tachs count the intake fans the
|
||||
machine held, the step writes the count, and it goes back as found.
|
||||
The tachs count only two turning fans: intake 2 giving no reading
|
||||
reads the same as a machine built with one, so the step asks the
|
||||
operator, and so it does when intake 1 does not come up. Both intakes
|
||||
turn on the bench reference, so either question fails the test
|
||||
unanswered."""
|
||||
|
||||
WID = "airflow.intakes"
|
||||
# The step's two questions, as the daemon asks them.
|
||||
SILENT_2 = ("Intake 1 turns; intake 2 gives no reading. Look at the back of the machine: how many "
|
||||
"intake fans does it have? If it has two, the second one needs attention before the "
|
||||
"machine cuts.")
|
||||
SLOW_1 = ("The intake fans could not be counted: intake 1 did not come up to speed. Look at the back "
|
||||
"of the machine: how many intake fans does it have? A fan that does not turn needs "
|
||||
"attention before the machine cuts.")
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.fake.state["settings"]["cool_intake_fans"] = "2"
|
||||
|
||||
def finish(self, s):
|
||||
# both intakes turned: two, by the tachs
|
||||
s["cool_intake_fans"] = "2"
|
||||
return {"intake_fans": 2, "intake_1_rpm": 4150.0, "intake_2_rpm": 4100.0, "intake_2_silent": False,
|
||||
"counted_by": "tachs", "summary": "x"}
|
||||
|
||||
def ask(self, text):
|
||||
self.prompt = {"id": "intake-count", "kind": "choice", "text": text, "options": ["One", "Two"]}
|
||||
|
||||
def test_two_intake_fans_counted_and_put_back(self):
|
||||
run = self.run_check("setup.check-intakes")
|
||||
self.assertEqual(run.evidence["count"]["counted"], 2)
|
||||
self.assertEqual(run.evidence["count"]["by"], "tachs")
|
||||
self.assertEqual(self.fake.state["settings"]["cool_intake_fans"], "2")
|
||||
self.assertIn(("/settings", {"cool_intake_fans": "2"}), self.fake.posts) # the put-back
|
||||
|
||||
def test_a_count_other_than_the_machine_held_fails_and_puts_it_back(self):
|
||||
# a machine that held one intake fan, whose tachs saw two turn
|
||||
self.fake.state["settings"]["cool_intake_fans"] = "1"
|
||||
with self.assertRaises(Exception) as cm:
|
||||
self.run_check("setup.check-intakes")
|
||||
self.assertIn("the tachs count 2 intake fans, the machine held 1", str(cm.exception))
|
||||
self.assertEqual(self.fake.state["settings"]["cool_intake_fans"], "1")
|
||||
|
||||
def test_a_silent_intake_2_is_a_fault_the_test_does_not_answer(self):
|
||||
# intake 1 turns, intake 2 gives no reading: the step asks the
|
||||
# operator, and on the bench reference that is a fan needing attention
|
||||
self.ask(self.SILENT_2)
|
||||
with self.assertRaises(Exception) as cm:
|
||||
self.run_check("setup.check-intakes")
|
||||
self.assertIn("asked for the count, which the tachs could not make", str(cm.exception))
|
||||
self.assertIn("intake 2 gives no reading", str(cm.exception))
|
||||
self.assertEqual(self.answers, [])
|
||||
self.assertIn(("/wiz/airflow.intakes/abort", {}), self.fake.posts)
|
||||
self.assertEqual(self.fake.state["settings"]["cool_intake_fans"], "2")
|
||||
|
||||
def test_a_silent_intake_2_on_a_machine_that_held_one_still_fails(self):
|
||||
# the test never answers the operator's question, whatever was held
|
||||
self.fake.state["settings"]["cool_intake_fans"] = "1"
|
||||
self.ask(self.SILENT_2)
|
||||
with self.assertRaises(Exception) as cm:
|
||||
self.run_check("setup.check-intakes")
|
||||
self.assertIn("asked for the count", str(cm.exception))
|
||||
self.assertEqual(self.answers, [])
|
||||
self.assertEqual(self.fake.state["settings"]["cool_intake_fans"], "1")
|
||||
|
||||
def test_intake_1_not_up_to_speed_fails_the_check(self):
|
||||
self.ask(self.SLOW_1)
|
||||
with self.assertRaises(Exception) as cm:
|
||||
self.run_check("setup.check-intakes")
|
||||
self.assertIn("asked for the count", str(cm.exception))
|
||||
self.assertIn("intake 1 did not come up to speed", str(cm.exception))
|
||||
self.assertEqual(self.answers, [])
|
||||
self.assertEqual(self.fake.state["settings"]["cool_intake_fans"], "2")
|
||||
|
||||
def test_the_tachs_count_is_judged_by_the_count_alone(self):
|
||||
# counted by the tachs, the count is the whole judgment: the speeds
|
||||
# are recorded, not judged again
|
||||
real = self.finish
|
||||
self.finish = lambda s: dict(real(s), intake_1_rpm=None)
|
||||
run = self.run_check("setup.check-intakes")
|
||||
self.assertIsNone(run.evidence["count"]["intake_1_rpm"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -137,7 +137,7 @@ class TakeoverPositionTests(unittest.TestCase):
|
||||
self.assertNotIn("restart_positions", cap)
|
||||
|
||||
def test_the_post_pass_fails_the_run_and_moves_nothing(self):
|
||||
# setup.check-envelope's case: a declared camera home, the head
|
||||
# a check's case: a declared camera home, the head
|
||||
# left out, the record put back under a takeover, whose restart
|
||||
# zeroes the counters where the head stands
|
||||
cap = self.cap()
|
||||
|
||||
Reference in New Issue
Block a user