mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
forgetest: the setup suite, and the sensors check asks nothing
The commission.* tests are setup.* (subsystem "setup"): suite/setup.py, setup_dark.py, setup_sheet.py, and the host tests test_setup_*.py. The coverage maps name src/setup.* in place of src/commission.*, the record is setup.json, and the bench seed in forgetest.init creates /run/forgefirm/setup-override. setup.check-sensors follows the check as it is now: no question is asked and no setting is written. The test reads the settings before and after and fails on any change, and fails at once if the check opens a prompt. The host test drives the fake daemon with no prompt and proves both outcomes. Proof: test_setup_dark.py, test_setup_sheet.py, and test_setup_suite.py pass (46 tests). The coverage lint names src/commission.c and .h as uncovered until the forgectrl pin moves to the revision that carries the rename.
This commit is contained in:
@@ -22,13 +22,13 @@ case "$1" in
|
|||||||
start)
|
start)
|
||||||
echo "Starting forgetest"
|
echo "Starting forgetest"
|
||||||
mkdir -p "$DATA"
|
mkdir -p "$DATA"
|
||||||
# The bench seed: the commissioning override lifts the wizard part of
|
# The bench seed: the setup override lifts the wizard part of
|
||||||
# the controller gate until the next reboot (/run is tmpfs), so a
|
# the controller gate until the next reboot (/run is tmpfs), so a
|
||||||
# campaign runs on a machine whose required wizards are not complete.
|
# campaign runs on a machine whose required wizards are not complete.
|
||||||
# Nothing lifts the agreements or the account: the bench accepts them
|
# Nothing lifts the agreements or the account: the bench accepts them
|
||||||
# once, and that stays in /data.
|
# once, and that stays in /data.
|
||||||
mkdir -p /run/forgefirm
|
mkdir -p /run/forgefirm
|
||||||
: > /run/forgefirm/commissioning-override
|
: > /run/forgefirm/setup-override
|
||||||
start-stop-daemon -S -q -p $PIDFILE -m -b -x /bin/sh -- -c \
|
start-stop-daemon -S -q -p $PIDFILE -m -b -x /bin/sh -- -c \
|
||||||
"exec /usr/bin/python3 -m forgetest >> $LOG 2>&1"
|
"exec /usr/bin/python3 -m forgetest >> $LOG 2>&1"
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -321,7 +321,7 @@ class Baseline:
|
|||||||
def wait_settled(self, timeout=SETTLE_S, unreachable_s=10):
|
def wait_settled(self, timeout=SETTLE_S, unreachable_s=10):
|
||||||
"""Block until forgectrl reports a settled supervisor: motion
|
"""Block until forgectrl reports a settled supervisor: motion
|
||||||
verified (the probe passed), motion-fault (the ladder exhausted),
|
verified (the probe passed), motion-fault (the ladder exhausted),
|
||||||
standby (the manual stop lever), or gated (the commissioning gate
|
standby (the manual stop lever), or gated (the setup gate
|
||||||
is closed: no controller spawns until it opens). Gives up after
|
is closed: no controller spawns until it opens). Gives up after
|
||||||
unreachable_s without an answer. Returns the last /mode body (None
|
unreachable_s without an answer. Returns the last /mode body (None
|
||||||
if unreachable)."""
|
if unreachable)."""
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ order. Each module registers its tests with @catalog.test."""
|
|||||||
from . import image # noqa: F401,E402
|
from . import image # noqa: F401,E402
|
||||||
from . import kernel # noqa: F401,E402
|
from . import kernel # noqa: F401,E402
|
||||||
from . import forgectrl # noqa: F401,E402
|
from . import forgectrl # noqa: F401,E402
|
||||||
from . import commission # noqa: F401,E402
|
from . import setup # noqa: F401,E402
|
||||||
from . import commission_dark # noqa: F401,E402
|
from . import setup_dark # noqa: F401,E402
|
||||||
from . import commission_sheet # noqa: F401,E402
|
from . import setup_sheet # noqa: F401,E402
|
||||||
from . import logs # noqa: F401,E402
|
from . import logs # noqa: F401,E402
|
||||||
from . import motion # noqa: F401,E402
|
from . import motion # noqa: F401,E402
|
||||||
from . import cooling # noqa: F401,E402
|
from . import cooling # noqa: F401,E402
|
||||||
|
|||||||
@@ -396,7 +396,7 @@ def lid_privacy(ctx):
|
|||||||
"authorizes a write; a rotation from the panel route stops the old key and "
|
"authorizes a write; a rotation from the panel route stops the old key and "
|
||||||
"the new one reads. The setting is put back after.")
|
"the new one reads. The setting is put back after.")
|
||||||
def key_read(ctx):
|
def key_read(ctx):
|
||||||
from .commission import request
|
from .setup import request
|
||||||
from .forgectrl import lan_ip
|
from .forgectrl import lan_ip
|
||||||
fc = ctx.forgectrl
|
fc = ctx.forgectrl
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
|
|||||||
@@ -891,7 +891,7 @@ CRASH_GATES = ["crash_abort", "crash_x_alert", "crash_y_alert"]
|
|||||||
"read as the three crash gates off by value (gates_off names them) with the "
|
"read as the three crash gates off by value (gates_off names them) with the "
|
||||||
"session OK; a threshold past the register range is refused; restored, the "
|
"session OK; a threshold past the register range is refused; restored, the "
|
||||||
"gates read ok. The tiers themselves (BUMP, CRASH) trip only on a physical "
|
"gates read ok. The tiers themselves (BUMP, CRASH) trip only on a physical "
|
||||||
"knock inside an armed window and are commissioned at the bench, not here.")
|
"knock inside an armed window and are proven at the bench, not here.")
|
||||||
def crash_watch_plumbing(ctx):
|
def crash_watch_plumbing(ctx):
|
||||||
fc = ctx.forgectrl
|
fc = ctx.forgectrl
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ def auth(ctx):
|
|||||||
# HTTPS the route itself refuses the peer (403 loopback only). Neither
|
# HTTPS the route itself refuses the peer (403 loopback only). Neither
|
||||||
# request follows the redirect, and the self-signed certificate is
|
# request follows the redirect, and the self-signed certificate is
|
||||||
# not verified.
|
# not verified.
|
||||||
from .commission import request, decode
|
from .setup import request, decode
|
||||||
ip = lan_ip()
|
ip = lan_ip()
|
||||||
ev["lan_ip"] = ip
|
ev["lan_ip"] = ip
|
||||||
ctx.check(ip, "cannot determine the board's LAN address")
|
ctx.check(ip, "cannot determine the board's LAN address")
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
"""commission.* - the first-run commissioning: the record and the
|
"""setup.* - the first-run setup: the record and the
|
||||||
controller gate, the advisories, the account and its login, the HTTPS
|
controller gate, the advisories, the account and its login, the HTTPS
|
||||||
boundary, SSH, the cloud switch, the machine's name, and the factory
|
boundary, SSH, the cloud switch, the machine's name, and the factory
|
||||||
return.
|
return.
|
||||||
|
|
||||||
The daemon reads the commissioning record (commissioning.json in the
|
The daemon reads the setup record (setup.json in the
|
||||||
data directory) and the account record (users) once, at its start, and
|
data directory) and the account record (users) once, at its start, and
|
||||||
keeps them in memory; only its own routes write them. A test that needs
|
keeps them in memory; only its own routes write them. A test that needs
|
||||||
a different record installs the file under a forgectrl restart (a
|
a different record installs the file under a forgectrl restart (a
|
||||||
@@ -34,13 +34,13 @@ from ..catalog import test
|
|||||||
from .. import hw
|
from .. import hw
|
||||||
from .forgectrl import lan_ip
|
from .forgectrl import lan_ip
|
||||||
|
|
||||||
RECORD = "commissioning.json"
|
RECORD = "setup.json"
|
||||||
USERS = "users"
|
USERS = "users"
|
||||||
OVERRIDE = "commissioning-override"
|
OVERRIDE = "setup-override"
|
||||||
SSH_FLAG = "ssh-enabled"
|
SSH_FLAG = "ssh-enabled"
|
||||||
TLS_BASE = "https://127.0.0.1" # FORGECTRL_TLS_URL overrides (host tests)
|
TLS_BASE = "https://127.0.0.1" # FORGECTRL_TLS_URL overrides (host tests)
|
||||||
|
|
||||||
# The image's required wizards, all at version 1 (commission.c).
|
# The image's required wizards, all at version 1 (setup.c).
|
||||||
REQUIRED_WIZARDS = ("advisories", "account", "preferences", "machine", "cloud")
|
REQUIRED_WIZARDS = ("advisories", "account", "preferences", "machine", "cloud")
|
||||||
ADVISORY_DOCS = ("safety-and-risk", "licenses", "privacy", "cloud-service")
|
ADVISORY_DOCS = ("safety-and-risk", "licenses", "privacy", "cloud-service")
|
||||||
SAFETY_PHRASE = "I UNDERSTAND"
|
SAFETY_PHRASE = "I UNDERSTAND"
|
||||||
@@ -176,8 +176,8 @@ def record_without_wizards(record):
|
|||||||
|
|
||||||
|
|
||||||
def complete_record(status, base=None, account_name="bench"):
|
def complete_record(status, base=None, account_name="bench"):
|
||||||
"""A complete commissioning record for a machine that must count as
|
"""A complete setup record for a machine that must count as
|
||||||
commissioned: every advisory at the hash GET /wiz reports, the
|
set up: every advisory at the hash GET /wiz reports, the
|
||||||
press recorded, an account (the one of `base` when it has one),
|
press recorded, an account (the one of `base` when it has one),
|
||||||
every wizard of the catalog at its version, and the run complete.
|
every wizard of the catalog at its version, and the run complete.
|
||||||
`status` is a GET /wiz body; `base` is an existing record to keep
|
`status` is a GET /wiz body; `base` is an existing record to keep
|
||||||
@@ -352,12 +352,12 @@ def gate_is_open():
|
|||||||
|
|
||||||
# -------------------------------------------------------------- the gate
|
# -------------------------------------------------------------- the gate
|
||||||
|
|
||||||
_GATE_COVERS = [("forgectrl", "src/commission.*"), ("forgectrl", "src/super.c"),
|
_GATE_COVERS = [("forgectrl", "src/setup.*"), ("forgectrl", "src/super.c"),
|
||||||
("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")]
|
("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")]
|
||||||
|
|
||||||
|
|
||||||
@test("commission.gate-blocks-controllers", title="The commissioning gate spawns no controller",
|
@test("setup.gate-blocks-controllers", title="The setup gate spawns no controller",
|
||||||
subsystem="commission", kind="auto", hardware="takeover", mode="grbl", est_min=5,
|
subsystem="setup", kind="auto", hardware="takeover", mode="grbl", est_min=5,
|
||||||
covers=_GATE_COVERS, requires=["forgectrl.auth"], precheck=gate_is_open,
|
covers=_GATE_COVERS, requires=["forgectrl.auth"], precheck=gate_is_open,
|
||||||
description="With the override removed and a record that lacks every required wizard "
|
description="With the override removed and a record that lacks every required wizard "
|
||||||
"(installed under a forgectrl restart, the consent and the account kept), "
|
"(installed under a forgectrl restart, the consent and the account kept), "
|
||||||
@@ -374,11 +374,11 @@ def gate_blocks_controllers(ctx):
|
|||||||
ctx.log("before: gate %s, override %s, required %s", before.get("gate"), before.get("override"),
|
ctx.log("before: gate %s, override %s, required %s", before.get("gate"), before.get("override"),
|
||||||
before.get("required"))
|
before.get("required"))
|
||||||
raw = read_file(record_path())
|
raw = read_file(record_path())
|
||||||
ctx.check(raw, "no commissioning record at %s", record_path())
|
ctx.check(raw, "no setup record at %s", record_path())
|
||||||
try:
|
try:
|
||||||
record = json.loads(raw.decode("utf-8"))
|
record = json.loads(raw.decode("utf-8"))
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
ctx.fail("the commissioning record is not JSON: %s", e)
|
ctx.fail("the setup record is not JSON: %s", e)
|
||||||
lacking = json.dumps(record_without_wizards(record), indent=1, sort_keys=True).encode() + b"\n"
|
lacking = json.dumps(record_without_wizards(record), indent=1, sort_keys=True).encode() + b"\n"
|
||||||
had_override = override_present()
|
had_override = override_present()
|
||||||
ev["override_before"] = had_override
|
ev["override_before"] = had_override
|
||||||
@@ -391,7 +391,7 @@ def gate_blocks_controllers(ctx):
|
|||||||
ctx.log("/mode after the restart: %s (after %s s)", m, took)
|
ctx.log("/mode after the restart: %s (after %s s)", m, took)
|
||||||
ctx.check(m.get("controller") == "gated" and m.get("gated") is True,
|
ctx.check(m.get("controller") == "gated" and m.get("gated") is True,
|
||||||
"the supervisor did not report the gate: %s", m)
|
"the supervisor did not report the gate: %s", m)
|
||||||
ctx.check("commissioning required" in (m.get("why") or ""),
|
ctx.check("setup required" in (m.get("why") or ""),
|
||||||
"the reason does not name the missing wizards: %r", m.get("why"))
|
"the reason does not name the missing wizards: %r", m.get("why"))
|
||||||
w = wiz(fc)
|
w = wiz(fc)
|
||||||
ev["gated_wiz"] = wiz_summary(w)
|
ev["gated_wiz"] = wiz_summary(w)
|
||||||
@@ -439,11 +439,11 @@ def gate_blocks_controllers(ctx):
|
|||||||
ctx.log("restored: gate %s, controller %s", after.get("gate"), m.get("controller"))
|
ctx.log("restored: gate %s, controller %s", after.get("gate"), m.get("controller"))
|
||||||
|
|
||||||
|
|
||||||
@test("commission.override-until-reboot", title="The override stands until the next reboot",
|
@test("setup.override-until-reboot", title="The override stands until the next reboot",
|
||||||
subsystem="commission", kind="auto", est_min=1,
|
subsystem="setup", kind="auto", est_min=1,
|
||||||
covers=_GATE_COVERS, requires=["forgectrl.auth"],
|
covers=_GATE_COVERS, requires=["forgectrl.auth"],
|
||||||
description="The bench seed: the dev image's forgetest init script writes "
|
description="The bench seed: the dev image's forgetest init script writes "
|
||||||
"/run/forgefirm/commissioning-override at boot. With it present, GET /wiz "
|
"/run/forgefirm/setup-override at boot. With it present, GET /wiz "
|
||||||
"reports the override and an open gate, the reason names the wizards it lifts "
|
"reports the override and an open gate, the reason names the wizards it lifts "
|
||||||
"when any is required, and GET /mode reports the controller running and not "
|
"when any is required, and GET /mode reports the controller running and not "
|
||||||
"gated. The file lives on tmpfs (/proc/mounts), so a reboot removes it by "
|
"gated. The file lives on tmpfs (/proc/mounts), so a reboot removes it by "
|
||||||
@@ -486,9 +486,9 @@ def override_until_reboot(ctx):
|
|||||||
|
|
||||||
# ------------------------------------------------------------ advisories
|
# ------------------------------------------------------------ advisories
|
||||||
|
|
||||||
@test("commission.advisories-rehash", title="An advisory is accepted at its current hash only",
|
@test("setup.advisories-rehash", title="An advisory is accepted at its current hash only",
|
||||||
subsystem="commission", kind="auto", hardware="takeover", est_min=3,
|
subsystem="setup", kind="auto", hardware="takeover", est_min=3,
|
||||||
covers=[("forgectrl", "src/advisories.*"), ("forgectrl", "src/commission.*"),
|
covers=[("forgectrl", "src/advisories.*"), ("forgectrl", "src/setup.*"),
|
||||||
("forgectrl", "src/wiz.*"), ("forgectrl", "src/sha256.*"), ("forgectrl", "src/main.c"),
|
("forgectrl", "src/wiz.*"), ("forgectrl", "src/sha256.*"), ("forgectrl", "src/main.c"),
|
||||||
("forgectrl", "src/ui/md.js"), ("forgectrl", "src/ui/embed_docs.cmake")],
|
("forgectrl", "src/ui/md.js"), ("forgectrl", "src/ui/embed_docs.cmake")],
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
@@ -504,7 +504,7 @@ def advisories_rehash(ctx):
|
|||||||
fc = ctx.forgectrl
|
fc = ctx.forgectrl
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
raw = read_file(record_path())
|
raw = read_file(record_path())
|
||||||
ctx.check(raw, "no commissioning record at %s", record_path())
|
ctx.check(raw, "no setup record at %s", record_path())
|
||||||
before = wiz(fc)
|
before = wiz(fc)
|
||||||
ev["before"] = wiz_summary(before)
|
ev["before"] = wiz_summary(before)
|
||||||
docs = {d.get("id"): d for d in before.get("documents") or []}
|
docs = {d.get("id"): d for d in before.get("documents") or []}
|
||||||
@@ -579,7 +579,7 @@ def advisories_rehash(ctx):
|
|||||||
|
|
||||||
# -------------------------------------------------------------- the login
|
# -------------------------------------------------------------- the login
|
||||||
|
|
||||||
@test("commission.account-login", title="The panel login over HTTPS", subsystem="commission",
|
@test("setup.account-login", title="The panel login over HTTPS", subsystem="setup",
|
||||||
kind="auto", est_min=2,
|
kind="auto", est_min=2,
|
||||||
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/session.*"), ("forgectrl", "src/users.*"),
|
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/session.*"), ("forgectrl", "src/users.*"),
|
||||||
("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"), ("forgectrl", "src/main.c"),
|
("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"), ("forgectrl", "src/main.c"),
|
||||||
@@ -702,7 +702,7 @@ def login_checks(ctx, name, pw):
|
|||||||
|
|
||||||
# ------------------------------------------------------- HTTPS boundary
|
# ------------------------------------------------------- HTTPS boundary
|
||||||
|
|
||||||
@test("commission.https-only-writes", title="Writes from the LAN go to HTTPS", subsystem="commission",
|
@test("setup.https-only-writes", title="Writes from the LAN go to HTTPS", subsystem="setup",
|
||||||
kind="auto", est_min=1,
|
kind="auto", est_min=1,
|
||||||
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"),
|
covers=[("forgectrl", "src/auth.*"), ("forgectrl", "src/tls.*"), ("forgectrl", "src/peer.*"),
|
||||||
("forgectrl", "src/main.c")],
|
("forgectrl", "src/main.c")],
|
||||||
@@ -802,8 +802,8 @@ def restore_ssh(fc, before):
|
|||||||
write_file(flag, b"" if before.get("enabled") else None)
|
write_file(flag, b"" if before.get("enabled") else None)
|
||||||
|
|
||||||
|
|
||||||
@test("commission.ssh-until-reboot", title="SSH is switched from the panel until the next reboot",
|
@test("setup.ssh-until-reboot", title="SSH is switched from the panel until the next reboot",
|
||||||
subsystem="commission", kind="auto", est_min=1,
|
subsystem="setup", kind="auto", est_min=1,
|
||||||
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")],
|
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/paths.h")],
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
description="GET /system/ssh reports enabled, running, and dev_image, and agrees with the "
|
description="GET /system/ssh reports enabled, running, and dev_image, and agrees with the "
|
||||||
@@ -888,8 +888,8 @@ def ssh_until_reboot(ctx):
|
|||||||
|
|
||||||
# ---------------------------------------------------------------- cloud
|
# ---------------------------------------------------------------- cloud
|
||||||
|
|
||||||
@test("commission.cloud-disabled-surface", title="Nothing points at the cloud while it is off",
|
@test("setup.cloud-disabled-surface", title="Nothing points at the cloud while it is off",
|
||||||
subsystem="commission", kind="auto", mode="grbl", est_min=1,
|
subsystem="setup", kind="auto", mode="grbl", est_min=1,
|
||||||
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/super.c"), ("forgectrl", "src/settings.*"),
|
covers=[("forgectrl", "src/main.c"), ("forgectrl", "src/super.c"), ("forgectrl", "src/settings.*"),
|
||||||
("forgectrl", "src/wiz.c"), ("forgectrl", "src/hooks.h")],
|
("forgectrl", "src/wiz.c"), ("forgectrl", "src/hooks.h")],
|
||||||
requires=["forgectrl.settings-bounds"],
|
requires=["forgectrl.settings-bounds"],
|
||||||
@@ -971,8 +971,8 @@ def cloud_disabled_surface(ctx):
|
|||||||
|
|
||||||
# ---------------------------------------------------------- the operator
|
# ---------------------------------------------------------- the operator
|
||||||
|
|
||||||
@test("commission.factory-return", title="The return to the factory firmware is guarded",
|
@test("setup.factory-return", title="The return to the factory firmware is guarded",
|
||||||
subsystem="commission", kind="auto", est_min=1,
|
subsystem="setup", kind="auto", est_min=1,
|
||||||
covers=[("forgectrl", "src/update.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.js")],
|
covers=[("forgectrl", "src/update.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.js")],
|
||||||
requires=["update.slots-and-signature"],
|
requires=["update.slots-and-signature"],
|
||||||
description="The return itself reboots the machine as a Glowforge, so it never runs from the "
|
description="The return itself reboots the machine as a Glowforge, so it never runs from the "
|
||||||
@@ -1034,8 +1034,8 @@ def cmdlines():
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
@test("commission.machine-name", title="The machine names itself from its MAC address",
|
@test("setup.machine-name", title="The machine names itself from its MAC address",
|
||||||
subsystem="commission", kind="auto", est_min=1,
|
subsystem="setup", kind="auto", est_min=1,
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
description="The machine calls itself forgefirm-<xxxx>, where xxxx is the last four hex "
|
description="The machine calls itself forgefirm-<xxxx>, where xxxx is the last four hex "
|
||||||
"digits of its WiFi MAC address, so two machines on one network answer to "
|
"digits of its WiFi MAC address, so two machines on one network answer to "
|
||||||
@@ -1213,12 +1213,12 @@ def finished_first_run(ctx):
|
|||||||
"the controller did not come back after the setup: %s", m)
|
"the controller did not come back after the setup: %s", m)
|
||||||
|
|
||||||
|
|
||||||
_FIRST_RUN_REQUIRES = ["commission.gate-blocks-controllers", "commission.advisories-rehash"]
|
_FIRST_RUN_REQUIRES = ["setup.gate-blocks-controllers", "setup.advisories-rehash"]
|
||||||
|
|
||||||
|
|
||||||
@test("commission.first-run-flow", title="The first run, driven as the page drives it", subsystem="commission",
|
@test("setup.first-run-flow", title="The first run, driven as the page drives it", subsystem="setup",
|
||||||
kind="operator", hardware="takeover", est_min=3,
|
kind="operator", hardware="takeover", est_min=3,
|
||||||
covers=[("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"), ("forgectrl", "src/users.*"),
|
covers=[("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"), ("forgectrl", "src/users.*"),
|
||||||
("forgectrl", "src/session.*"), ("forgectrl", "src/button.*"), ("forgectrl", "src/led.*"),
|
("forgectrl", "src/session.*"), ("forgectrl", "src/button.*"), ("forgectrl", "src/led.*"),
|
||||||
("forgectrl", "src/sheetid.*"), ("forgectrl", "src/advisories.*"), ("forgectrl", "src/hooks.h"),
|
("forgectrl", "src/sheetid.*"), ("forgectrl", "src/advisories.*"), ("forgectrl", "src/hooks.h"),
|
||||||
("forgectrl", "src/main.c")],
|
("forgectrl", "src/main.c")],
|
||||||
@@ -1309,11 +1309,11 @@ def first_run_flow(ctx):
|
|||||||
ctx.log("the run completed: gate open, the controller back")
|
ctx.log("the run completed: gate open, the controller back")
|
||||||
|
|
||||||
|
|
||||||
@test("commission.first-run-page", title="The first-run page, walked once", subsystem="commission",
|
@test("setup.first-run-page", title="The first-run page, walked once", subsystem="setup",
|
||||||
kind="operator", hardware="takeover", est_min=6,
|
kind="operator", hardware="takeover", est_min=6,
|
||||||
covers=[("forgectrl", "src/ui/wizard.*"), ("forgectrl", "src/ui/forms.js"), ("forgectrl", "src/ui/md.js"),
|
covers=[("forgectrl", "src/ui/wizard.*"), ("forgectrl", "src/ui/forms.js"), ("forgectrl", "src/ui/md.js"),
|
||||||
("forgectrl", "src/ui/login.*"), ("forgectrl", "src/ui/embed_docs.cmake"), ("forgectrl", "src/tls.*")],
|
("forgectrl", "src/ui/login.*"), ("forgectrl", "src/ui/embed_docs.cmake"), ("forgectrl", "src/tls.*")],
|
||||||
requires=_FIRST_RUN_REQUIRES + ["commission.first-run-flow"],
|
requires=_FIRST_RUN_REQUIRES + ["setup.first-run-flow"],
|
||||||
actions=["button"], hands=["workstation"],
|
actions=["button"], hands=["workstation"],
|
||||||
steps=["From the workstation open https://<address>/ and accept the self-signed certificate.",
|
steps=["From the workstation open https://<address>/ and accept the self-signed certificate.",
|
||||||
"Click through the setup: scroll each document to its end and accept it (type I "
|
"Click through the setup: scroll each document to its end and accept it (type I "
|
||||||
@@ -1328,7 +1328,7 @@ def first_run_flow(ctx):
|
|||||||
"clicks through (the documents accepted, the press step with the LEDs "
|
"clicks through (the documents accepted, the press step with the LEDs "
|
||||||
"breathing teal, the account created, the run complete, the gate open, the "
|
"breathing teal, the account created, the run complete, the gate open, the "
|
||||||
"controller back). The operator confirms the panel opened. Everything the run "
|
"controller back). The operator confirms the panel opened. Everything the run "
|
||||||
"wrote is restored as commission.first-run-flow restores it.")
|
"wrote is restored as setup.first-run-flow restores it.")
|
||||||
def first_run_page(ctx):
|
def first_run_page(ctx):
|
||||||
fc = ctx.forgectrl
|
fc = ctx.forgectrl
|
||||||
ev = ctx.evidence
|
ev = ctx.evidence
|
||||||
@@ -1364,8 +1364,8 @@ def first_run_page(ctx):
|
|||||||
ctx.confirm("Did the control panel open after Finish?")
|
ctx.confirm("Did the control panel open after Finish?")
|
||||||
|
|
||||||
|
|
||||||
@test("commission.cert-page", title="The certificate is checkable before the warning",
|
@test("setup.cert-page", title="The certificate is checkable before the warning",
|
||||||
subsystem="commission", kind="auto", est_min=1,
|
subsystem="setup", kind="auto", est_min=1,
|
||||||
covers=[("forgectrl", "src/tls.*"), ("forgectrl", "src/main.c"),
|
covers=[("forgectrl", "src/tls.*"), ("forgectrl", "src/main.c"),
|
||||||
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.js")],
|
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.js")],
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
@@ -1423,9 +1423,9 @@ def flag_of(w, level, wid):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@test("commission.what-changed", title="A replaced part asks for its checks again",
|
@test("setup.what-changed", title="A replaced part asks for its checks again",
|
||||||
subsystem="commission", kind="auto", est_min=2, hardware="takeover",
|
subsystem="setup", kind="auto", est_min=2, hardware="takeover",
|
||||||
covers=[("forgectrl", "src/commission.*"), ("forgectrl", "src/wiz.*"), ("forgectrl", "src/main.c"),
|
covers=[("forgectrl", "src/setup.*"), ("forgectrl", "src/wiz.*"), ("forgectrl", "src/main.c"),
|
||||||
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/index.html"),
|
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/index.html"),
|
||||||
("forgectrl", "src/ui/help.js")],
|
("forgectrl", "src/ui/help.js")],
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
@@ -1493,9 +1493,9 @@ def what_changed(ctx):
|
|||||||
ctx.check(bool(mode(fc).get("gated")) == gated_before, "the gate does not read as before")
|
ctx.check(bool(mode(fc).get("gated")) == gated_before, "the gate does not read as before")
|
||||||
|
|
||||||
|
|
||||||
@test("commission.record-export", title="The record: JSON, the printable page, and the log bundle",
|
@test("setup.record-export", title="The record: JSON, the printable page, and the log bundle",
|
||||||
subsystem="commission", kind="auto", est_min=2,
|
subsystem="setup", kind="auto", est_min=2,
|
||||||
covers=[("forgectrl", "src/commission.*"), ("forgectrl", "src/recordhtml.*"), ("forgectrl", "src/wiz.*"),
|
covers=[("forgectrl", "src/setup.*"), ("forgectrl", "src/recordhtml.*"), ("forgectrl", "src/wiz.*"),
|
||||||
("forgectrl", "src/logs.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/index.html"),
|
("forgectrl", "src/logs.*"), ("forgectrl", "src/main.c"), ("forgectrl", "src/ui/index.html"),
|
||||||
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/wizard.html")],
|
("forgectrl", "src/ui/panel.js"), ("forgectrl", "src/ui/wizard.html")],
|
||||||
requires=["forgectrl.auth", "logs.tree-tail-export"],
|
requires=["forgectrl.auth", "logs.tree-tail-export"],
|
||||||
@@ -1504,7 +1504,7 @@ def what_changed(ctx):
|
|||||||
"named after the sheet id; GET /wiz/record.html is a page with no script that "
|
"named after the sheet id; GET /wiz/record.html is a page with no script that "
|
||||||
"carries the sheet id and every completed wizard; without the token and without "
|
"carries the sheet id and every completed wizard; without the token and without "
|
||||||
"a login both are refused (403); and the sanitized log export carries "
|
"a login both are refused (403); and the sanitized log export carries "
|
||||||
"system/commissioning.json, parseable, with the same sheet id and no panel token.")
|
"system/setup.json, parseable, with the same sheet id and no panel token.")
|
||||||
def record_export(ctx):
|
def record_export(ctx):
|
||||||
import gzip
|
import gzip
|
||||||
import io
|
import io
|
||||||
@@ -1544,7 +1544,7 @@ def record_export(ctx):
|
|||||||
ctx.check(st == 200 and "text/html" in (hdrs.get("content-type") or ""), "GET /wiz/record.html -> %s %s",
|
ctx.check(st == 200 and "text/html" in (hdrs.get("content-type") or ""), "GET /wiz/record.html -> %s %s",
|
||||||
st, hdrs.get("content-type"))
|
st, hdrs.get("content-type"))
|
||||||
ctx.check("<script" not in page.lower(), "the printable page carries a script")
|
ctx.check("<script" not in page.lower(), "the printable page carries a script")
|
||||||
ctx.check(sid in page and "commissioning record" in page, "the page lacks the sheet id or its title")
|
ctx.check(sid in page and "setup record" in page, "the page lacks the sheet id or its title")
|
||||||
titles = {z["id"]: z["title"] for z in w.get("wizards") or []}
|
titles = {z["id"]: z["title"] for z in w.get("wizards") or []}
|
||||||
missing = [d for d in done if titles.get(d) and ("<h3>" + titles[d]) not in page]
|
missing = [d for d in done if titles.get(d) and ("<h3>" + titles[d]) not in page]
|
||||||
ev["missing_on_page"] = missing
|
ev["missing_on_page"] = missing
|
||||||
@@ -1565,11 +1565,11 @@ def record_export(ctx):
|
|||||||
try:
|
try:
|
||||||
tf = tarfile.open(fileobj=io.BytesIO(gzip.decompress(data)))
|
tf = tarfile.open(fileobj=io.BytesIO(gzip.decompress(data)))
|
||||||
for m in tf.getmembers():
|
for m in tf.getmembers():
|
||||||
if m.isfile() and m.name.endswith("system/commissioning.json"):
|
if m.isfile() and m.name.endswith("system/setup.json"):
|
||||||
member = tf.extractfile(m).read()
|
member = tf.extractfile(m).read()
|
||||||
except (OSError, tarfile.TarError, EOFError) as e:
|
except (OSError, tarfile.TarError, EOFError) as e:
|
||||||
ctx.fail("export is not a readable tar.gz: %s", e)
|
ctx.fail("export is not a readable tar.gz: %s", e)
|
||||||
ctx.check(member is not None, "the bundle lacks system/commissioning.json")
|
ctx.check(member is not None, "the bundle lacks system/setup.json")
|
||||||
if member is not None:
|
if member is not None:
|
||||||
try:
|
try:
|
||||||
inside = json.loads(member.decode("utf-8"))
|
inside = json.loads(member.decode("utf-8"))
|
||||||
@@ -1587,12 +1587,12 @@ def record_export(ctx):
|
|||||||
ctx.log("record %d wizards; page %d bytes; bundle carries the record", len(done), ev["page_bytes"])
|
ctx.log("record %d wizards; page %d bytes; bundle carries the record", len(done), ev["page_bytes"])
|
||||||
|
|
||||||
|
|
||||||
@test("commission.mirror", title="One browser drives a check; a second one follows",
|
@test("setup.mirror", title="One browser drives a check; a second one follows",
|
||||||
subsystem="commission", kind="auto", est_min=2, hardware="takeover",
|
subsystem="setup", kind="auto", est_min=2, hardware="takeover",
|
||||||
covers=[("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"), ("forgectrl", "src/wiz.*"),
|
covers=[("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"), ("forgectrl", "src/wiz.*"),
|
||||||
("forgectrl", "src/session.*"), ("forgectrl", "src/ui/wizard.js"),
|
("forgectrl", "src/session.*"), ("forgectrl", "src/ui/wizard.js"),
|
||||||
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.css")],
|
("forgectrl", "src/ui/wizard.html"), ("forgectrl", "src/ui/wizard.css")],
|
||||||
requires=["forgectrl.auth", "commission.account-login", "commission.check-sensors"],
|
requires=["forgectrl.auth", "setup.account-login", "setup.check-sensors"],
|
||||||
description="With a temporary account (made and removed as the login test does) and two "
|
description="With a temporary account (made and removed as the login test does) and two "
|
||||||
"login sessions: the first session starts the sensors check; GET /wiz/dark "
|
"login sessions: the first session starts the sensors check; GET /wiz/dark "
|
||||||
"reports the run as owned and its own to the first session, not to the second, "
|
"reports the run as owned and its own to the first session, not to the second, "
|
||||||
+34
-24
@@ -1,4 +1,4 @@
|
|||||||
"""commission.check-* - the setup's checks (the dark wizards), driven the
|
"""setup.check-* - the setup's checks (the dark wizards), driven the
|
||||||
way the page drives them: POST /wiz/<id>/start, GET /wiz/dark polled,
|
way the page drives them: POST /wiz/<id>/start, GET /wiz/dark polled,
|
||||||
the prompts answered from here (the bench fixture works the lid and the
|
the prompts answered from here (the bench fixture works the lid and the
|
||||||
button; a confirmation is answered yes once the snapshot exists), the
|
button; a confirmation is answered yes once the snapshot exists), the
|
||||||
@@ -12,7 +12,7 @@ import time
|
|||||||
from ..catalog import test
|
from ..catalog import test
|
||||||
from .. import hw
|
from .. import hw
|
||||||
from ..baseline import read_position
|
from ..baseline import read_position
|
||||||
from .commission import wiz, Restore # noqa: F401 - Restore is re-exported for the sheet
|
from .setup import wiz, Restore # noqa: F401 - Restore is re-exported for the sheet
|
||||||
|
|
||||||
POLL_S = 1.0
|
POLL_S = 1.0
|
||||||
# What the machine's own press prompt says, used when it sends no text.
|
# What the machine's own press prompt says, used when it sends no text.
|
||||||
@@ -22,7 +22,7 @@ PRESS_TEXT = "The button is lit white. Press it now: the laser fires after your
|
|||||||
# (74) for the first seconds and settles near 630.
|
# (74) for the first seconds and settles near 630.
|
||||||
PURGE_SPINUP_S = 30
|
PURGE_SPINUP_S = 30
|
||||||
DARK_COVERS = [("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"),
|
DARK_COVERS = [("forgectrl", "src/wizdark.*"), ("forgectrl", "src/wizcalc.*"),
|
||||||
("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"),
|
("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"),
|
||||||
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*")]
|
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*")]
|
||||||
|
|
||||||
|
|
||||||
@@ -113,8 +113,8 @@ def run_check(ctx, wid, on_prompt, timeout_s):
|
|||||||
return last
|
return last
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-switches", title="The switches check follows the lid and the button",
|
@test("setup.check-switches", title="The switches check follows the lid and the button",
|
||||||
subsystem="commission", kind="operator", est_min=3,
|
subsystem="setup", kind="operator", est_min=3,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/status.c")],
|
covers=DARK_COVERS + [("forgectrl", "src/status.c")],
|
||||||
requires=["forgectrl.auth"], actions=["lid", "button"],
|
requires=["forgectrl.auth"], actions=["lid", "button"],
|
||||||
description="POST /wiz/switches/start; the check asks for the lid to open and close and for "
|
description="POST /wiz/switches/start; the check asks for the lid to open and close and for "
|
||||||
@@ -148,30 +148,40 @@ def check_switches(ctx):
|
|||||||
ctx.log("switches: %s", json.dumps(r))
|
ctx.log("switches: %s", json.dumps(r))
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-sensors", title="The sensors check reads a plausible machine at rest",
|
@test("setup.check-sensors", title="The sensors check reads a plausible machine at rest",
|
||||||
subsystem="commission", kind="auto", est_min=2,
|
subsystem="setup", kind="auto", est_min=2,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/status.c"), ("forgectrl", "src/accel.c"),
|
covers=DARK_COVERS + [("forgectrl", "src/status.c"), ("forgectrl", "src/accel.c"),
|
||||||
("forgectrl", "src/cool.c")],
|
("forgectrl", "src/cool.c")],
|
||||||
requires=["forgectrl.auth"],
|
requires=["forgectrl.auth"],
|
||||||
description="POST /wiz/sensors/start; ten seconds of readings, then the room-temperature "
|
description="POST /wiz/sensors/start; ten seconds of readings and no question asked. The "
|
||||||
"prompt, answered Skip so no offset is written. The result carries both coolant "
|
"result carries both coolant temperatures, the chassis and SoC, the lid IR "
|
||||||
"temperatures, the chassis and SoC, the lid IR maxima, the accelerometer event "
|
"maxima, the accelerometer event count, the supply power-good, the HV current, "
|
||||||
"count, the supply power-good, the HV current, and the idle fan speeds; the "
|
"and the idle fan speeds; the record carries sensors at version 1 and the "
|
||||||
"record carries sensors at version 1 and cool_temp_offset_c reads as before.")
|
"settings read as before (the check writes none).")
|
||||||
def check_sensors(ctx):
|
def check_sensors(ctx):
|
||||||
with Restore(ctx, ["cool_temp_offset_c"]):
|
fc = ctx.forgectrl
|
||||||
last = run_check(ctx, "sensors", lambda p: "Skip" if p.get("id") == "room-temp" else None, 120)
|
ev = ctx.evidence
|
||||||
|
before = fc.settings() or {}
|
||||||
|
ev["settings_before"] = dict(before)
|
||||||
|
|
||||||
|
def on_prompt(p):
|
||||||
|
ctx.fail("the sensors check asked a question: %s", p)
|
||||||
|
last = run_check(ctx, "sensors", on_prompt, 120)
|
||||||
r = last.get("result") or {}
|
r = last.get("result") or {}
|
||||||
for k in ("coolant_down_c", "coolant_up_c", "lid_ir_max", "laser_pgood", "hv_current_max",
|
for k in ("coolant_down_c", "coolant_up_c", "lid_ir_max", "laser_pgood", "hv_current_max",
|
||||||
"exhaust_rpm_idle", "intake_rpm_idle"):
|
"exhaust_rpm_idle", "intake_rpm_idle"):
|
||||||
ctx.check(k in r, "the result lacks %s: %s", k, r)
|
ctx.check(k in r, "the result lacks %s: %s", k, r)
|
||||||
ctx.check(r.get("laser_pgood") == 1, "power-good read %s", r.get("laser_pgood"))
|
ctx.check(r.get("laser_pgood") == 1, "power-good read %s", r.get("laser_pgood"))
|
||||||
|
after = fc.settings() or {}
|
||||||
|
changed = {k: (before.get(k), after.get(k)) for k in set(before) | set(after)
|
||||||
|
if before.get(k) != after.get(k)}
|
||||||
|
ctx.check(not changed, "the sensors check wrote a setting: %s", changed)
|
||||||
ctx.log("sensors: coolant %s/%s C, IR %s, HV %s", r.get("coolant_down_c"), r.get("coolant_up_c"),
|
ctx.log("sensors: coolant %s/%s C, IR %s, HV %s", r.get("coolant_down_c"), r.get("coolant_up_c"),
|
||||||
r.get("lid_ir_max"), r.get("hv_current_max"))
|
r.get("lid_ir_max"), r.get("hv_current_max"))
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-airflow", title="The airflow check measures the fans and sets the floors",
|
@test("setup.check-airflow", title="The airflow check measures the fans and sets the floors",
|
||||||
subsystem="commission", kind="auto", hardware="takeover", est_min=3,
|
subsystem="setup", kind="auto", hardware="takeover", est_min=3,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/cool.c"), ("forgectrl", "src/airflow.*"),
|
covers=DARK_COVERS + [("forgectrl", "src/cool.c"), ("forgectrl", "src/airflow.*"),
|
||||||
("forgectrl", "src/gates.c"), ("forgectrl", "src/super.c")],
|
("forgectrl", "src/gates.c"), ("forgectrl", "src/super.c")],
|
||||||
requires=["forgectrl.auth", "cooling.fan-gate-trips"],
|
requires=["forgectrl.auth", "cooling.fan-gate-trips"],
|
||||||
@@ -229,8 +239,8 @@ def check_airflow(ctx):
|
|||||||
ctx.check(ok is not None, "the controller did not come back after the check")
|
ctx.check(ok is not None, "the controller did not come back after the check")
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-cameras", title="The cameras check captures both cameras",
|
@test("setup.check-cameras", title="The cameras check captures both cameras",
|
||||||
subsystem="commission", kind="operator", est_min=2,
|
subsystem="setup", kind="operator", est_min=2,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/cam.c")],
|
covers=DARK_COVERS + [("forgectrl", "src/cam.c")],
|
||||||
requires=["forgectrl.auth", "camera.snapshot"], actions=["lid"],
|
requires=["forgectrl.auth", "camera.snapshot"], actions=["lid"],
|
||||||
description="POST /wiz/cameras/start with the lid closed: a lid snapshot, the question, a "
|
description="POST /wiz/cameras/start with the lid closed: a lid snapshot, the question, a "
|
||||||
@@ -264,8 +274,8 @@ def check_cameras(ctx):
|
|||||||
shots.get("lid", {}).get("bytes", 0), shots.get("head", {}).get("bytes", 0))
|
shots.get("lid", {}).get("bytes", 0), shots.get("head", {}).get("bytes", 0))
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-motion", title="The motion check proves the rail, the lens reference, and the jogs",
|
@test("setup.check-motion", title="The motion check proves the rail, the lens reference, and the jogs",
|
||||||
subsystem="commission", kind="auto", hardware="takeover", est_min=5,
|
subsystem="setup", kind="auto", hardware="takeover", est_min=5,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/super.c"), ("forgectrl", "src/liveness.c"),
|
covers=DARK_COVERS + [("forgectrl", "src/super.c"), ("forgectrl", "src/liveness.c"),
|
||||||
("forgectrl", "src/lenshome.*"),
|
("forgectrl", "src/lenshome.*"),
|
||||||
("forgectrl", "src/accel.c"), ("forgectrl", "src/cool.c")],
|
("forgectrl", "src/accel.c"), ("forgectrl", "src/cool.c")],
|
||||||
@@ -305,8 +315,8 @@ def check_motion(ctx):
|
|||||||
r.get("rest"), {k: (v.get("p2p_lp_x"), v.get("p2p_lp_y")) for k, v in moves.items()})
|
r.get("rest"), {k: (v.get("p2p_lp_x"), v.get("p2p_lp_y")) for k, v in moves.items()})
|
||||||
|
|
||||||
|
|
||||||
@test("commission.check-flow-verify", title="The flow check runs as a setup check",
|
@test("setup.check-flow-verify", title="The flow check runs as a setup check",
|
||||||
subsystem="commission", kind="auto", hardware="takeover", est_min=5,
|
subsystem="setup", kind="auto", hardware="takeover", est_min=5,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/diag.c")],
|
covers=DARK_COVERS + [("forgectrl", "src/diag.c")],
|
||||||
requires=["forgectrl.auth", "cooling.aa-offset-calibrate", "cooling.flow-verify"],
|
requires=["forgectrl.auth", "cooling.aa-offset-calibrate", "cooling.flow-verify"],
|
||||||
description="POST /wiz/cooling.flow-verify/start drives the flow-verify diagnostic through the "
|
description="POST /wiz/cooling.flow-verify/start drives the flow-verify diagnostic through the "
|
||||||
@@ -323,8 +333,8 @@ def check_flow_verify(ctx):
|
|||||||
r.get("flow_rise"), r.get("noflow_rise"), r.get("thin_margin"))
|
r.get("flow_rise"), r.get("noflow_rise"), r.get("thin_margin"))
|
||||||
|
|
||||||
|
|
||||||
@test("commission.cloud-header-capture", title="The cloud header check takes one print's envelope",
|
@test("setup.cloud-header-capture", title="The cloud header check takes one print's envelope",
|
||||||
subsystem="commission", kind="operator", hardware="takeover", mode="grbl", est_min=8,
|
subsystem="setup", kind="operator", hardware="takeover", mode="grbl", est_min=8,
|
||||||
covers=DARK_COVERS + [("forgectrl", "src/super.c"),
|
covers=DARK_COVERS + [("forgectrl", "src/super.c"),
|
||||||
("python3-gfhardware", "gfhardware/machine.py"),
|
("python3-gfhardware", "gfhardware/machine.py"),
|
||||||
("python3-gfhardware", "forgefirm-app/gfcloud.py"),
|
("python3-gfhardware", "forgefirm-app/gfcloud.py"),
|
||||||
+6
-6
@@ -1,4 +1,4 @@
|
|||||||
"""commission.sheet - the setup's sheet (the live wizards) in one run,
|
"""setup.sheet - the setup's sheet (the live wizards) in one run,
|
||||||
driven the way the page drives them: POST /wiz/<id>/start, GET /wiz/dark
|
driven the way the page drives them: POST /wiz/<id>/start, GET /wiz/dark
|
||||||
polled, the prompts answered from here, each burn armed by a press, the
|
polled, the prompts answered from here, each burn armed by a press, the
|
||||||
result judged, and every setting a wizard wrote put back as found. One
|
result judged, and every setting a wizard wrote put back as found. One
|
||||||
@@ -18,13 +18,13 @@ restored. The page is where the real numbers go in.
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from ..catalog import test
|
from ..catalog import test
|
||||||
from .commission_dark import run_check, Restore
|
from .setup_dark import run_check, Restore
|
||||||
|
|
||||||
SHEET_COVERS = [("forgectrl", "src/wizlive.*"), ("forgectrl", "src/wizrun.h"),
|
SHEET_COVERS = [("forgectrl", "src/wizlive.*"), ("forgectrl", "src/wizrun.h"),
|
||||||
("forgectrl", "src/lens.*"), ("forgectrl", "src/sheet.*"),
|
("forgectrl", "src/lens.*"), ("forgectrl", "src/sheet.*"),
|
||||||
("forgectrl", "src/font_hershey.*"), ("forgectrl", "src/jobstream.*"),
|
("forgectrl", "src/font_hershey.*"), ("forgectrl", "src/jobstream.*"),
|
||||||
("forgectrl", "src/curverec.*"), ("forgectrl", "src/wizdark.*"),
|
("forgectrl", "src/curverec.*"), ("forgectrl", "src/wizdark.*"),
|
||||||
("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"),
|
("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"),
|
||||||
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*"),
|
("forgectrl", "src/main.c"), ("forgectrl", "src/ui/wizard.*"),
|
||||||
("forgectrl", "src/super.c"),
|
("forgectrl", "src/super.c"),
|
||||||
("forgectrl", "src/status.c"), ("forgectrl", "src/cool.c"),
|
("forgectrl", "src/status.c"), ("forgectrl", "src/cool.c"),
|
||||||
@@ -222,10 +222,10 @@ def burn(ctx, wid, answers, want):
|
|||||||
file_card(ctx, wid)
|
file_card(ctx, wid)
|
||||||
|
|
||||||
|
|
||||||
@test("commission.sheet", title="The commissioning sheet: the placement, the frame, and the five cards",
|
@test("setup.sheet", title="The setup sheet: the placement, the frame, and the five cards",
|
||||||
subsystem="commission", kind="live", hardware="takeover", mode="grbl", est_min=35,
|
subsystem="setup", kind="live", hardware="takeover", mode="grbl", est_min=35,
|
||||||
covers=SHEET_COVERS,
|
covers=SHEET_COVERS,
|
||||||
requires=["forgectrl.auth", "commission.check-motion", "laser.emission-witness", "cooling.flow-verify"],
|
requires=["forgectrl.auth", "setup.check-motion", "laser.emission-witness", "cooling.flow-verify"],
|
||||||
actions=["button"], hands=["scrap"],
|
actions=["button"], hands=["scrap"],
|
||||||
steps=["A piece of wood at least 200 x 150 mm (8 x 6 in) on the bed, pushed as far left as "
|
steps=["A piece of wood at least 200 x 150 mm (8 x 6 in) on the bed, pushed as far left as "
|
||||||
"it goes with its top edge at the top of the cut area (the head's home corner); "
|
"it goes with its top edge at the top of the cut area (the head's home corner); "
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
"""The setup's checks in the catalog: registered as automatic tests with
|
"""The setup's checks in the catalog: registered as automatic tests with
|
||||||
no precheck, the takeover ones marked so, every covers map naming the
|
no precheck, the takeover ones marked so, every covers map naming the
|
||||||
check runner; and the check driver itself against the fake daemon: a
|
check runner; and the check driver itself against the fake daemon: a
|
||||||
start, a prompt answered, the result taken, the record read at version
|
start, the run polled to its end with no question asked, the result
|
||||||
1, and the settings a check may write put back as found."""
|
taken, the record read at version 1, and the settings read as before."""
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import unittest
|
import unittest
|
||||||
@@ -12,8 +12,8 @@ import helpers # noqa: E402
|
|||||||
from forgetest import catalog # noqa: E402
|
from forgetest import catalog # noqa: E402
|
||||||
from forgetest.runner import Context, Run # noqa: E402
|
from forgetest.runner import Context, Run # noqa: E402
|
||||||
|
|
||||||
IDS = ["commission.check-switches", "commission.check-sensors", "commission.check-airflow",
|
IDS = ["setup.check-switches", "setup.check-sensors", "setup.check-airflow",
|
||||||
"commission.check-cameras", "commission.check-motion", "commission.check-flow-verify"]
|
"setup.check-cameras", "setup.check-motion", "setup.check-flow-verify"]
|
||||||
|
|
||||||
|
|
||||||
class RegistrationTests(unittest.TestCase):
|
class RegistrationTests(unittest.TestCase):
|
||||||
@@ -25,11 +25,11 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
# bench fixture performs; the rest need no hands at all.
|
# bench fixture performs; the rest need no hands at all.
|
||||||
for tid in IDS:
|
for tid in IDS:
|
||||||
self.assertIn(tid, self.reg)
|
self.assertIn(tid, self.reg)
|
||||||
for tid in ("commission.check-switches", "commission.check-cameras"):
|
for tid in ("setup.check-switches", "setup.check-cameras"):
|
||||||
self.assertEqual(self.reg[tid].kind, "operator", tid)
|
self.assertEqual(self.reg[tid].kind, "operator", tid)
|
||||||
self.assertTrue(self.reg[tid].fixture_runnable(("button", "lid", "interlock")), tid)
|
self.assertTrue(self.reg[tid].fixture_runnable(("button", "lid", "interlock")), tid)
|
||||||
for tid in ("commission.check-sensors", "commission.check-airflow", "commission.check-motion",
|
for tid in ("setup.check-sensors", "setup.check-airflow", "setup.check-motion",
|
||||||
"commission.check-flow-verify"):
|
"setup.check-flow-verify"):
|
||||||
self.assertEqual(self.reg[tid].kind, "auto", tid)
|
self.assertEqual(self.reg[tid].kind, "auto", tid)
|
||||||
|
|
||||||
def test_no_check_asks_the_operator_to_prepare_anything(self):
|
def test_no_check_asks_the_operator_to_prepare_anything(self):
|
||||||
@@ -37,7 +37,7 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
self.assertIsNone(getattr(self.reg[tid], "precheck", None), tid)
|
self.assertIsNone(getattr(self.reg[tid], "precheck", None), tid)
|
||||||
|
|
||||||
def test_the_checks_that_take_the_machine_say_so(self):
|
def test_the_checks_that_take_the_machine_say_so(self):
|
||||||
for tid in ("commission.check-airflow", "commission.check-motion", "commission.check-flow-verify"):
|
for tid in ("setup.check-airflow", "setup.check-motion", "setup.check-flow-verify"):
|
||||||
self.assertEqual(self.reg[tid].hardware, "takeover", tid)
|
self.assertEqual(self.reg[tid].hardware, "takeover", tid)
|
||||||
|
|
||||||
def test_every_check_covers_the_runner_and_the_page(self):
|
def test_every_check_covers_the_runner_and_the_page(self):
|
||||||
@@ -62,9 +62,17 @@ class SensorsCheckTests(unittest.TestCase):
|
|||||||
"shots": {"lid": False, "head": False}}
|
"shots": {"lid": False, "head": False}}
|
||||||
self.versions = {}
|
self.versions = {}
|
||||||
self.answers = []
|
self.answers = []
|
||||||
|
self.polls = 0
|
||||||
|
|
||||||
def on_get(path, q):
|
def on_get(path, q):
|
||||||
if path == "/wiz/dark":
|
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)
|
return 200, dict(self.dark)
|
||||||
if path == "/wiz":
|
if path == "/wiz":
|
||||||
return 200, {"versions": dict(self.versions), "required": [], "completed": True}
|
return 200, {"versions": dict(self.versions), "required": [], "completed": True}
|
||||||
@@ -74,19 +82,11 @@ class SensorsCheckTests(unittest.TestCase):
|
|||||||
s = self.fake.state["settings"]
|
s = self.fake.state["settings"]
|
||||||
if path == "/wiz/sensors/start":
|
if path == "/wiz/sensors/start":
|
||||||
self.dark.update({"id": "sensors", "running": True, "phase": "sampling",
|
self.dark.update({"id": "sensors", "running": True, "phase": "sampling",
|
||||||
"prompt": {"seq": 7, "id": "room-temp", "kind": "number",
|
"prompt": None})
|
||||||
"text": "Optional: the room temperature.",
|
|
||||||
"options": ["Set", "Skip"]}})
|
|
||||||
return 200, {"started": True, "id": "sensors"}
|
return 200, {"started": True, "id": "sensors"}
|
||||||
if path == "/wiz/sensors/answer":
|
if path == "/wiz/sensors/answer":
|
||||||
self.answers.append(dict(form))
|
self.answers.append(dict(form))
|
||||||
if form.get("seq") != "7":
|
|
||||||
return 409, {"error": "no such prompt is open"}
|
return 409, {"error": "no such prompt is open"}
|
||||||
if form.get("value") == "Skip":
|
|
||||||
self.dark.update({"running": False, "prompt": None, "progress": 100,
|
|
||||||
"result": dict(self.RESULT)})
|
|
||||||
self.versions["sensors"] = 1
|
|
||||||
return 200, {"ok": True}
|
|
||||||
if path == "/settings":
|
if path == "/settings":
|
||||||
for k, v in form.items():
|
for k, v in form.items():
|
||||||
if v == "":
|
if v == "":
|
||||||
@@ -101,15 +101,35 @@ class SensorsCheckTests(unittest.TestCase):
|
|||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
self.fake.stop()
|
self.fake.stop()
|
||||||
|
|
||||||
def test_start_prompt_answer_result_and_restore(self):
|
def test_start_no_question_result_and_settings_untouched(self):
|
||||||
t = catalog.load_suite()["commission.check-sensors"]
|
t = catalog.load_suite()["setup.check-sensors"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
t.fn(Context(run, None, t))
|
t.fn(Context(run, None, t))
|
||||||
self.assertIn(("/wiz/sensors/start", {}), [(p, f) for p, f in self.fake.posts])
|
self.assertIn(("/wiz/sensors/start", {}), [(p, f) for p, f in self.fake.posts])
|
||||||
self.assertEqual(self.answers, [{"seq": "7", "value": "Skip"}])
|
self.assertEqual(self.answers, [])
|
||||||
self.assertEqual(run.evidence["result"]["laser_pgood"], 1)
|
self.assertEqual(run.evidence["result"]["laser_pgood"], 1)
|
||||||
self.assertEqual(run.evidence["settings_before"], {"cool_temp_offset_c": "0.5"})
|
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.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])
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
@@ -14,7 +14,7 @@ sys.path.insert(0, os.path.dirname(__file__))
|
|||||||
import helpers # noqa: E402
|
import helpers # noqa: E402
|
||||||
from forgetest import catalog # noqa: E402
|
from forgetest import catalog # noqa: E402
|
||||||
from forgetest.runner import Context, Run # noqa: E402
|
from forgetest.runner import Context, Run # noqa: E402
|
||||||
from forgetest.suite import commission_sheet # noqa: E402
|
from forgetest.suite import setup_sheet # noqa: E402
|
||||||
|
|
||||||
LIVE = ["sheet.frame", "laser.focus", "laser.floor", "laser.dose-curve", "laser.corner", "cooling.flow-load"]
|
LIVE = ["sheet.frame", "laser.focus", "laser.floor", "laser.dose-curve", "laser.corner", "cooling.flow-load"]
|
||||||
# The focus card's program is read again after it ran: the served ladder
|
# The focus card's program is read again after it ran: the served ladder
|
||||||
@@ -107,7 +107,7 @@ FOUND = {"lens_hall_edge_z_mm": "3.35", "lens_stop_below_steps": "10", "lens_sto
|
|||||||
|
|
||||||
class RegistrationTests(unittest.TestCase):
|
class RegistrationTests(unittest.TestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.t = catalog.load_suite()["commission.sheet"]
|
self.t = catalog.load_suite()["setup.sheet"]
|
||||||
|
|
||||||
def test_one_live_test_on_the_button_with_the_sheet_at_hand(self):
|
def test_one_live_test_on_the_button_with_the_sheet_at_hand(self):
|
||||||
t = self.t
|
t = self.t
|
||||||
@@ -118,12 +118,12 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
self.assertTrue(t.hands)
|
self.assertTrue(t.hands)
|
||||||
self.assertFalse(t.fixture_runnable(("button", "lid", "interlock")))
|
self.assertFalse(t.fixture_runnable(("button", "lid", "interlock")))
|
||||||
self.assertIsNone(getattr(t, "precheck", None))
|
self.assertIsNone(getattr(t, "precheck", None))
|
||||||
for want in ("commission.check-motion", "laser.emission-witness", "cooling.flow-verify"):
|
for want in ("setup.check-motion", "laser.emission-witness", "cooling.flow-verify"):
|
||||||
self.assertIn(want, t.requires)
|
self.assertIn(want, t.requires)
|
||||||
|
|
||||||
def test_no_other_sheet_test_remains(self):
|
def test_no_other_sheet_test_remains(self):
|
||||||
ids = [i for i in catalog.load_suite() if i.startswith("commission.sheet")]
|
ids = [i for i in catalog.load_suite() if i.startswith("setup.sheet")]
|
||||||
self.assertEqual(ids, ["commission.sheet"])
|
self.assertEqual(ids, ["setup.sheet"])
|
||||||
|
|
||||||
def test_covers_the_live_runner_the_renderer_the_sender_the_page_and_the_driver(self):
|
def test_covers_the_live_runner_the_renderer_the_sender_the_page_and_the_driver(self):
|
||||||
covers = set(self.t.covers)
|
covers = set(self.t.covers)
|
||||||
@@ -137,7 +137,7 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
text = " ".join(self.t.steps).lower()
|
text = " ".join(self.t.steps).lower()
|
||||||
self.assertIn("press the machine's button once", text)
|
self.assertIn("press the machine's button once", text)
|
||||||
self.assertNotIn("when the page", text)
|
self.assertNotIn("when the page", text)
|
||||||
self.assertEqual(len(commission_sheet.CARDS), 6)
|
self.assertEqual(len(setup_sheet.CARDS), 6)
|
||||||
|
|
||||||
|
|
||||||
class ScriptedDaemon:
|
class ScriptedDaemon:
|
||||||
@@ -246,7 +246,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
self.fake.stop()
|
self.fake.stop()
|
||||||
|
|
||||||
def test_the_whole_sheet_on_one_piece(self):
|
def test_the_whole_sheet_on_one_piece(self):
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True # the ready gate passes; the arm cue is a notice
|
run.unattended = True # the ready gate passes; the arm cue is a notice
|
||||||
t.fn(Context(run, None, t))
|
t.fn(Context(run, None, t))
|
||||||
@@ -270,12 +270,12 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
# frame, the frame's program ran in the fallback window, the focus
|
# frame, the frame's program ran in the fallback window, the focus
|
||||||
# card's window was in the settings when the floor card started,
|
# card's window was in the settings when the floor card started,
|
||||||
# and the window check filed what it compared.
|
# and the window check filed what it compared.
|
||||||
for k in commission_sheet.LENS_SETTINGS:
|
for k in setup_sheet.LENS_SETTINGS:
|
||||||
self.assertNotIn(k, d.settings_at_start["sheet.frame"], k)
|
self.assertNotIn(k, d.settings_at_start["sheet.frame"], k)
|
||||||
self.assertEqual(run.evidence["lens_fresh"]["stops_found"], False)
|
self.assertEqual(run.evidence["lens_fresh"]["stops_found"], False)
|
||||||
self.assertEqual(run.evidence["program_z"]["sheet.frame"]["reach"], [-0.07, 7.46])
|
self.assertEqual(run.evidence["program_z"]["sheet.frame"]["reach"], [-0.07, 7.46])
|
||||||
floor_start = d.settings_at_start["laser.floor"]
|
floor_start = d.settings_at_start["laser.floor"]
|
||||||
self.assertEqual({k: floor_start[k] for k in commission_sheet.LENS_SETTINGS},
|
self.assertEqual({k: floor_start[k] for k in setup_sheet.LENS_SETTINGS},
|
||||||
{"lens_hall_edge_z_mm": "6.96", "lens_stop_below_steps": "14", "lens_stop_above_steps": "20"})
|
{"lens_hall_edge_z_mm": "6.96", "lens_stop_below_steps": "14", "lens_stop_above_steps": "20"})
|
||||||
fw = run.evidence["focus_window"]
|
fw = run.evidence["focus_window"]
|
||||||
self.assertEqual(fw["window"], FOCUS_WINDOW)
|
self.assertEqual(fw["window"], FOCUS_WINDOW)
|
||||||
@@ -294,7 +294,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
place_prompts, place_result = SCRIPT["sheet.place"]
|
place_prompts, place_result = SCRIPT["sheet.place"]
|
||||||
SCRIPT["sheet.place"] = (place_prompts, dict(place_result, thickness_mm=3.175))
|
SCRIPT["sheet.place"] = (place_prompts, dict(place_result, thickness_mm=3.175))
|
||||||
try:
|
try:
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True
|
run.unattended = True
|
||||||
t.fn(Context(run, None, t))
|
t.fn(Context(run, None, t))
|
||||||
@@ -310,7 +310,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
place_prompts, place_result = SCRIPT["sheet.place"]
|
place_prompts, place_result = SCRIPT["sheet.place"]
|
||||||
SCRIPT["sheet.place"] = (place_prompts, dict(place_result, thickness_mm=0.0))
|
SCRIPT["sheet.place"] = (place_prompts, dict(place_result, thickness_mm=0.0))
|
||||||
try:
|
try:
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True
|
run.unattended = True
|
||||||
with self.assertRaises(Failed) as cm:
|
with self.assertRaises(Failed) as cm:
|
||||||
@@ -326,7 +326,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
SCRIPT_DARK["emission"] = {"hv_max": 0, "laser_on_samples": 0, "thermopile_delta": 0}
|
SCRIPT_DARK["emission"] = {"hv_max": 0, "laser_on_samples": 0, "thermopile_delta": 0}
|
||||||
SCRIPT["laser.floor"] = (SCRIPT["laser.floor"][0], SCRIPT_DARK)
|
SCRIPT["laser.floor"] = (SCRIPT["laser.floor"][0], SCRIPT_DARK)
|
||||||
try:
|
try:
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True
|
run.unattended = True
|
||||||
with self.assertRaises(Failed):
|
with self.assertRaises(Failed):
|
||||||
@@ -347,7 +347,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
saved = dict(START_WRITES["laser.focus"])
|
saved = dict(START_WRITES["laser.focus"])
|
||||||
START_WRITES["laser.focus"] = {}
|
START_WRITES["laser.focus"] = {}
|
||||||
try:
|
try:
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True
|
run.unattended = True
|
||||||
with self.assertRaises(Failed) as cm:
|
with self.assertRaises(Failed) as cm:
|
||||||
@@ -364,7 +364,7 @@ class SheetRunTests(unittest.TestCase):
|
|||||||
# refused before the card starts: nothing burns.
|
# refused before the card starts: nothing burns.
|
||||||
from forgetest.runner import Failed
|
from forgetest.runner import Failed
|
||||||
self.daemon.stray_z = 10.6
|
self.daemon.stray_z = 10.6
|
||||||
t = catalog.load_suite()["commission.sheet"]
|
t = catalog.load_suite()["setup.sheet"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
run.unattended = True
|
run.unattended = True
|
||||||
with self.assertRaises(Failed) as cm:
|
with self.assertRaises(Failed) as cm:
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""The commission.* suite on the host: the registration (ids, kinds, the
|
"""The setup.* suite on the host: the registration (ids, kinds, the
|
||||||
takeover tests, the operator tests' hands), the record builders, the
|
takeover tests, the operator tests' hands), the record builders, the
|
||||||
machine's name, the cookie parsing, the LED cue, the settle rule for a
|
machine's name, the cookie parsing, the LED cue, the settle rule for a
|
||||||
gated supervisor, and the cloud-off surface test driven end to end
|
gated supervisor, and the cloud-off surface test driven end to end
|
||||||
@@ -12,15 +12,15 @@ import unittest
|
|||||||
import helpers
|
import helpers
|
||||||
from forgetest import baseline, catalog
|
from forgetest import baseline, catalog
|
||||||
from forgetest.runner import Context, Run
|
from forgetest.runner import Context, Run
|
||||||
from forgetest.suite import commission
|
from forgetest.suite import setup
|
||||||
|
|
||||||
IDS = ("commission.gate-blocks-controllers", "commission.override-until-reboot",
|
IDS = ("setup.gate-blocks-controllers", "setup.override-until-reboot",
|
||||||
"commission.advisories-rehash", "commission.account-login", "commission.https-only-writes",
|
"setup.advisories-rehash", "setup.account-login", "setup.https-only-writes",
|
||||||
"commission.ssh-until-reboot", "commission.cloud-disabled-surface",
|
"setup.ssh-until-reboot", "setup.cloud-disabled-surface",
|
||||||
"commission.factory-return", "commission.machine-name", "commission.first-run-flow",
|
"setup.factory-return", "setup.machine-name", "setup.first-run-flow",
|
||||||
"commission.first-run-page", "commission.what-changed", "commission.record-export",
|
"setup.first-run-page", "setup.what-changed", "setup.record-export",
|
||||||
"commission.mirror")
|
"setup.mirror")
|
||||||
OPERATOR = ("commission.first-run-flow", "commission.first-run-page")
|
OPERATOR = ("setup.first-run-flow", "setup.first-run-page")
|
||||||
|
|
||||||
|
|
||||||
class RegistrationTests(unittest.TestCase):
|
class RegistrationTests(unittest.TestCase):
|
||||||
@@ -35,49 +35,49 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
for tid in IDS:
|
for tid in IDS:
|
||||||
if tid not in OPERATOR:
|
if tid not in OPERATOR:
|
||||||
self.assertEqual(kinds[tid], "auto", tid)
|
self.assertEqual(kinds[tid], "auto", tid)
|
||||||
self.assertNotIn("commission.root-ssh-refused", self.reg) # folded into ssh-until-reboot
|
self.assertNotIn("setup.root-ssh-refused", self.reg) # folded into ssh-until-reboot
|
||||||
self.assertNotIn("commission.wizard-first-run", self.reg) # split into the flow and the page
|
self.assertNotIn("setup.wizard-first-run", self.reg) # split into the flow and the page
|
||||||
|
|
||||||
def test_the_record_swapping_tests_are_takeovers(self):
|
def test_the_record_swapping_tests_are_takeovers(self):
|
||||||
for tid in ("commission.gate-blocks-controllers", "commission.advisories-rehash",
|
for tid in ("setup.gate-blocks-controllers", "setup.advisories-rehash",
|
||||||
"commission.first-run-flow", "commission.first-run-page",
|
"setup.first-run-flow", "setup.first-run-page",
|
||||||
"commission.what-changed", "commission.mirror"):
|
"setup.what-changed", "setup.mirror"):
|
||||||
self.assertEqual(self.reg[tid].hardware, "takeover", tid)
|
self.assertEqual(self.reg[tid].hardware, "takeover", tid)
|
||||||
self.assertEqual(self.reg["commission.record-export"].hardware, "api")
|
self.assertEqual(self.reg["setup.record-export"].hardware, "api")
|
||||||
|
|
||||||
def test_the_lifecycle_tests_cover_their_files(self):
|
def test_the_lifecycle_tests_cover_their_files(self):
|
||||||
changed = set(self.reg["commission.what-changed"].covers)
|
changed = set(self.reg["setup.what-changed"].covers)
|
||||||
export = set(self.reg["commission.record-export"].covers)
|
export = set(self.reg["setup.record-export"].covers)
|
||||||
mirror = set(self.reg["commission.mirror"].covers)
|
mirror = set(self.reg["setup.mirror"].covers)
|
||||||
self.assertIn(("forgectrl", "src/ui/panel.js"), changed)
|
self.assertIn(("forgectrl", "src/ui/panel.js"), changed)
|
||||||
self.assertIn(("forgectrl", "src/recordhtml.*"), export)
|
self.assertIn(("forgectrl", "src/recordhtml.*"), export)
|
||||||
self.assertIn(("forgectrl", "src/logs.*"), export)
|
self.assertIn(("forgectrl", "src/logs.*"), export)
|
||||||
self.assertIn(("forgectrl", "src/wizdark.*"), mirror)
|
self.assertIn(("forgectrl", "src/wizdark.*"), mirror)
|
||||||
self.assertIn("commission.check-sensors", self.reg["commission.mirror"].requires)
|
self.assertIn("setup.check-sensors", self.reg["setup.mirror"].requires)
|
||||||
|
|
||||||
def test_flag_of_reads_the_status_document(self):
|
def test_flag_of_reads_the_status_document(self):
|
||||||
w = {"required": ["airflow", {"id": "laser.floor", "reason": "the tube was replaced"}],
|
w = {"required": ["airflow", {"id": "laser.floor", "reason": "the tube was replaced"}],
|
||||||
"recommended": [{"id": "laser.focus", "reason": "the tray was replaced"}]}
|
"recommended": [{"id": "laser.focus", "reason": "the tray was replaced"}]}
|
||||||
self.assertEqual(commission.flag_of(w, "required", "laser.floor"), "the tube was replaced")
|
self.assertEqual(setup.flag_of(w, "required", "laser.floor"), "the tube was replaced")
|
||||||
self.assertEqual(commission.flag_of(w, "recommended", "laser.focus"), "the tray was replaced")
|
self.assertEqual(setup.flag_of(w, "recommended", "laser.focus"), "the tray was replaced")
|
||||||
self.assertIsNone(commission.flag_of(w, "required", "airflow")) # a plain id is the table, not a flag
|
self.assertIsNone(setup.flag_of(w, "required", "airflow")) # a plain id is the table, not a flag
|
||||||
self.assertIsNone(commission.flag_of(w, "required", "laser.focus"))
|
self.assertIsNone(setup.flag_of(w, "required", "laser.focus"))
|
||||||
self.assertIsNone(commission.flag_of({}, "recommended", "x"))
|
self.assertIsNone(setup.flag_of({}, "recommended", "x"))
|
||||||
|
|
||||||
def test_the_first_run_flow_runs_unattended_with_the_fixture_and_the_page_walk_does_not(self):
|
def test_the_first_run_flow_runs_unattended_with_the_fixture_and_the_page_walk_does_not(self):
|
||||||
flow = self.reg["commission.first-run-flow"]
|
flow = self.reg["setup.first-run-flow"]
|
||||||
self.assertEqual(list(flow.actions), ["button"])
|
self.assertEqual(list(flow.actions), ["button"])
|
||||||
self.assertFalse(flow.hands)
|
self.assertFalse(flow.hands)
|
||||||
self.assertTrue(flow.fixture_runnable(("button", "lid", "interlock")))
|
self.assertTrue(flow.fixture_runnable(("button", "lid", "interlock")))
|
||||||
page = self.reg["commission.first-run-page"]
|
page = self.reg["setup.first-run-page"]
|
||||||
self.assertTrue(page.hands)
|
self.assertTrue(page.hands)
|
||||||
self.assertFalse(page.fixture_runnable(("button", "lid", "interlock")))
|
self.assertFalse(page.fixture_runnable(("button", "lid", "interlock")))
|
||||||
self.assertIn("commission.first-run-flow", page.requires)
|
self.assertIn("setup.first-run-flow", page.requires)
|
||||||
|
|
||||||
def test_the_first_run_split_keeps_the_backend_and_the_page_apart(self):
|
def test_the_first_run_split_keeps_the_backend_and_the_page_apart(self):
|
||||||
flow = set(self.reg["commission.first-run-flow"].covers)
|
flow = set(self.reg["setup.first-run-flow"].covers)
|
||||||
page = set(self.reg["commission.first-run-page"].covers)
|
page = set(self.reg["setup.first-run-page"].covers)
|
||||||
for want in (("forgectrl", "src/wiz.*"), ("forgectrl", "src/commission.*"), ("forgectrl", "src/users.*"),
|
for want in (("forgectrl", "src/wiz.*"), ("forgectrl", "src/setup.*"), ("forgectrl", "src/users.*"),
|
||||||
("forgectrl", "src/button.*"), ("forgectrl", "src/led.*"), ("forgectrl", "src/advisories.*")):
|
("forgectrl", "src/button.*"), ("forgectrl", "src/led.*"), ("forgectrl", "src/advisories.*")):
|
||||||
self.assertIn(want, flow, want)
|
self.assertIn(want, flow, want)
|
||||||
self.assertNotIn(want, page, want)
|
self.assertNotIn(want, page, want)
|
||||||
@@ -85,17 +85,17 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
self.assertIn(want, page, want)
|
self.assertIn(want, page, want)
|
||||||
|
|
||||||
def test_the_only_attended_tests_need_a_workstation_or_the_sheet(self):
|
def test_the_only_attended_tests_need_a_workstation_or_the_sheet(self):
|
||||||
# With the bench actuator up, three commissioning tests keep a
|
# With the bench actuator up, three setup tests keep a
|
||||||
# person: the page walk, the print from the Glowforge app, and
|
# person: the page walk, the print from the Glowforge app, and
|
||||||
# the sheet. Everything else runs from the queue.
|
# the sheet. Everything else runs from the queue.
|
||||||
attended = sorted(tid for tid, t in self.reg.items() if tid.startswith("commission.")
|
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")))
|
and t.kind != "auto" and not t.fixture_runnable(("button", "lid", "interlock")))
|
||||||
self.assertEqual(attended, ["commission.cloud-header-capture", "commission.first-run-page",
|
self.assertEqual(attended, ["setup.cloud-header-capture", "setup.first-run-page",
|
||||||
"commission.sheet"])
|
"setup.sheet"])
|
||||||
|
|
||||||
def test_the_factory_return_never_runs_the_return(self):
|
def test_the_factory_return_never_runs_the_return(self):
|
||||||
import inspect
|
import inspect
|
||||||
t = self.reg["commission.factory-return"]
|
t = self.reg["setup.factory-return"]
|
||||||
src = inspect.getsource(t.fn)
|
src = inspect.getsource(t.fn)
|
||||||
self.assertNotIn('"confirm": "1"', src) # the only argument that starts the return
|
self.assertNotIn('"confirm": "1"', src) # the only argument that starts the return
|
||||||
self.assertIn('"confirm": "0"', src)
|
self.assertIn('"confirm": "0"', src)
|
||||||
@@ -103,15 +103,15 @@ class RegistrationTests(unittest.TestCase):
|
|||||||
self.assertFalse(t.hands)
|
self.assertFalse(t.hands)
|
||||||
|
|
||||||
def test_the_machine_name_covers_nothing_by_design(self):
|
def test_the_machine_name_covers_nothing_by_design(self):
|
||||||
self.assertEqual(self.reg["commission.machine-name"].covers, ())
|
self.assertEqual(self.reg["setup.machine-name"].covers, ())
|
||||||
|
|
||||||
def test_the_login_test_makes_its_own_account(self):
|
def test_the_login_test_makes_its_own_account(self):
|
||||||
# No bench credentials, no precheck: the test installs a temporary
|
# No bench credentials, no precheck: the test installs a temporary
|
||||||
# account under a takeover and restores the real one.
|
# account under a takeover and restores the real one.
|
||||||
t = self.reg["commission.account-login"]
|
t = self.reg["setup.account-login"]
|
||||||
self.assertIsNone(getattr(t, "precheck", None))
|
self.assertIsNone(getattr(t, "precheck", None))
|
||||||
self.assertEqual(t.hardware, "takeover")
|
self.assertEqual(t.hardware, "takeover")
|
||||||
with open(commission.__file__) as f:
|
with open(setup.__file__) as f:
|
||||||
src = f.read()
|
src = f.read()
|
||||||
for k in ("FORGETEST_LOGIN_NAME", "FORGETEST_LOGIN_PASSWORD"):
|
for k in ("FORGETEST_LOGIN_NAME", "FORGETEST_LOGIN_PASSWORD"):
|
||||||
self.assertNotIn(k, src)
|
self.assertNotIn(k, src)
|
||||||
@@ -124,8 +124,8 @@ class RecordTests(unittest.TestCase):
|
|||||||
"wizards": [{"id": "advisories", "version": 1}, {"id": "account", "version": 1},
|
"wizards": [{"id": "advisories", "version": 1}, {"id": "account", "version": 1},
|
||||||
{"id": "machine", "version": 2}]}
|
{"id": "machine", "version": 2}]}
|
||||||
|
|
||||||
def test_complete_record_counts_as_commissioned(self):
|
def test_complete_record_counts_as_set_up(self):
|
||||||
rec = commission.complete_record(self.STATUS)
|
rec = setup.complete_record(self.STATUS)
|
||||||
self.assertEqual(rec["schema"], 1)
|
self.assertEqual(rec["schema"], 1)
|
||||||
self.assertEqual(rec["advisories"]["safety-and-risk"]["hash"], "a" * 64)
|
self.assertEqual(rec["advisories"]["safety-and-risk"]["hash"], "a" * 64)
|
||||||
self.assertEqual(rec["advisories"]["safety-and-risk"]["method"], "typed")
|
self.assertEqual(rec["advisories"]["safety-and-risk"]["method"], "typed")
|
||||||
@@ -139,16 +139,16 @@ class RecordTests(unittest.TestCase):
|
|||||||
def test_complete_record_keeps_the_base_account_and_machine(self):
|
def test_complete_record_keeps_the_base_account_and_machine(self):
|
||||||
base = {"account": {"name": "owner", "uid": 1000, "created": "x"}, "machine": {"model": "pro"},
|
base = {"account": {"name": "owner", "uid": 1000, "created": "x"}, "machine": {"model": "pro"},
|
||||||
"sheet_id": "KKKKK-LLLLL", "created": "then"}
|
"sheet_id": "KKKKK-LLLLL", "created": "then"}
|
||||||
rec = commission.complete_record(self.STATUS, base)
|
rec = setup.complete_record(self.STATUS, base)
|
||||||
self.assertEqual(rec["account"]["name"], "owner")
|
self.assertEqual(rec["account"]["name"], "owner")
|
||||||
self.assertEqual(rec["machine"], {"model": "pro"})
|
self.assertEqual(rec["machine"], {"model": "pro"})
|
||||||
self.assertEqual(rec["sheet_id"], "KKKKK-LLLLL")
|
self.assertEqual(rec["sheet_id"], "KKKKK-LLLLL")
|
||||||
self.assertEqual(rec["created"], "then")
|
self.assertEqual(rec["created"], "then")
|
||||||
|
|
||||||
def test_without_wizards_keeps_consent_and_account(self):
|
def test_without_wizards_keeps_consent_and_account(self):
|
||||||
rec = commission.complete_record(self.STATUS)
|
rec = setup.complete_record(self.STATUS)
|
||||||
rec["flags"] = {"machine": {"level": "required", "reason": "x"}}
|
rec["flags"] = {"machine": {"level": "required", "reason": "x"}}
|
||||||
out = commission.record_without_wizards(rec)
|
out = setup.record_without_wizards(rec)
|
||||||
self.assertEqual(out["wizards"], {})
|
self.assertEqual(out["wizards"], {})
|
||||||
self.assertEqual(out["flags"], {})
|
self.assertEqual(out["flags"], {})
|
||||||
self.assertEqual(out["advisories"], rec["advisories"])
|
self.assertEqual(out["advisories"], rec["advisories"])
|
||||||
@@ -158,14 +158,14 @@ class RecordTests(unittest.TestCase):
|
|||||||
def test_write_file_and_remove(self):
|
def test_write_file_and_remove(self):
|
||||||
tmp = tempfile.mkdtemp(prefix="forgetest-comm-")
|
tmp = tempfile.mkdtemp(prefix="forgetest-comm-")
|
||||||
try:
|
try:
|
||||||
p = os.path.join(tmp, "sub", "commissioning.json")
|
p = os.path.join(tmp, "sub", "setup.json")
|
||||||
commission.write_file(p, b"{}\n")
|
setup.write_file(p, b"{}\n")
|
||||||
self.assertEqual(commission.read_file(p), b"{}\n")
|
self.assertEqual(setup.read_file(p), b"{}\n")
|
||||||
if os.name == "posix": # a mode means nothing on a Windows host
|
if os.name == "posix": # a mode means nothing on a Windows host
|
||||||
self.assertEqual(oct(os.stat(p).st_mode & 0o777), oct(0o600))
|
self.assertEqual(oct(os.stat(p).st_mode & 0o777), oct(0o600))
|
||||||
commission.write_file(p, None)
|
setup.write_file(p, None)
|
||||||
self.assertIsNone(commission.read_file(p))
|
self.assertIsNone(setup.read_file(p))
|
||||||
commission.write_file(p, None) # a second remove is silent
|
setup.write_file(p, None) # a second remove is silent
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(tmp, ignore_errors=True)
|
shutil.rmtree(tmp, ignore_errors=True)
|
||||||
|
|
||||||
@@ -173,10 +173,10 @@ class RecordTests(unittest.TestCase):
|
|||||||
os.environ["FORGECTRL_DATA_DIR"] = "/tmp/ffdata"
|
os.environ["FORGECTRL_DATA_DIR"] = "/tmp/ffdata"
|
||||||
os.environ["GF_RUN_DIR"] = "/tmp/ffrun"
|
os.environ["GF_RUN_DIR"] = "/tmp/ffrun"
|
||||||
try:
|
try:
|
||||||
self.assertEqual(commission.record_path(), "/tmp/ffdata/commissioning.json")
|
self.assertEqual(setup.record_path(), "/tmp/ffdata/setup.json")
|
||||||
self.assertEqual(commission.users_path(), "/tmp/ffdata/users")
|
self.assertEqual(setup.users_path(), "/tmp/ffdata/users")
|
||||||
self.assertEqual(commission.override_path(), "/tmp/ffrun/commissioning-override")
|
self.assertEqual(setup.override_path(), "/tmp/ffrun/setup-override")
|
||||||
self.assertEqual(commission.ssh_flag_path(), "/tmp/ffrun/ssh-enabled")
|
self.assertEqual(setup.ssh_flag_path(), "/tmp/ffrun/ssh-enabled")
|
||||||
finally:
|
finally:
|
||||||
os.environ.pop("FORGECTRL_DATA_DIR", None)
|
os.environ.pop("FORGECTRL_DATA_DIR", None)
|
||||||
os.environ.pop("GF_RUN_DIR", None)
|
os.environ.pop("GF_RUN_DIR", None)
|
||||||
@@ -188,14 +188,14 @@ class MachineNameTests(unittest.TestCase):
|
|||||||
self.addCleanup(shutil.rmtree, self.root, ignore_errors=True)
|
self.addCleanup(shutil.rmtree, self.root, ignore_errors=True)
|
||||||
|
|
||||||
def _net(self, **devs):
|
def _net(self, **devs):
|
||||||
"""A /sys/class/net tree; commission.mac_suffix reads it through
|
"""A /sys/class/net tree; setup.mac_suffix reads it through
|
||||||
commission.read_file, which takes an absolute path."""
|
setup.read_file, which takes an absolute path."""
|
||||||
for dev, mac in devs.items():
|
for dev, mac in devs.items():
|
||||||
d = os.path.join(self.root, dev)
|
d = os.path.join(self.root, dev)
|
||||||
os.makedirs(d)
|
os.makedirs(d)
|
||||||
with open(os.path.join(d, "address"), "w") as f:
|
with open(os.path.join(d, "address"), "w") as f:
|
||||||
f.write(mac + "\n")
|
f.write(mac + "\n")
|
||||||
real = commission.read_file
|
real = setup.read_file
|
||||||
|
|
||||||
def read(path):
|
def read(path):
|
||||||
head = "/sys/class/net/"
|
head = "/sys/class/net/"
|
||||||
@@ -203,52 +203,52 @@ class MachineNameTests(unittest.TestCase):
|
|||||||
return real(os.path.join(self.root, path[len(head):]))
|
return real(os.path.join(self.root, path[len(head):]))
|
||||||
return real(path)
|
return real(path)
|
||||||
|
|
||||||
commission.read_file = read
|
setup.read_file = read
|
||||||
self.addCleanup(setattr, commission, "read_file", real)
|
self.addCleanup(setattr, setup, "read_file", real)
|
||||||
|
|
||||||
def test_the_wifi_address_names_the_machine(self):
|
def test_the_wifi_address_names_the_machine(self):
|
||||||
self._net(wlan0="2C:6B:7D:0D:B0:0A", eth0="00:11:22:33:44:55")
|
self._net(wlan0="2C:6B:7D:0D:B0:0A", eth0="00:11:22:33:44:55")
|
||||||
self.assertEqual(commission.mac_suffix(), "b00a")
|
self.assertEqual(setup.mac_suffix(), "b00a")
|
||||||
|
|
||||||
def test_a_machine_with_no_wifi_falls_back_to_the_wired_address(self):
|
def test_a_machine_with_no_wifi_falls_back_to_the_wired_address(self):
|
||||||
self._net(eth0="00:11:22:33:44:55")
|
self._net(eth0="00:11:22:33:44:55")
|
||||||
self.assertEqual(commission.mac_suffix(), "4455")
|
self.assertEqual(setup.mac_suffix(), "4455")
|
||||||
|
|
||||||
def test_an_unread_address_is_no_address(self):
|
def test_an_unread_address_is_no_address(self):
|
||||||
self._net(wlan0="00:00:00:00:00:00")
|
self._net(wlan0="00:00:00:00:00:00")
|
||||||
self.assertEqual(commission.mac_suffix(), "")
|
self.assertEqual(setup.mac_suffix(), "")
|
||||||
|
|
||||||
def test_no_interface_is_no_address(self):
|
def test_no_interface_is_no_address(self):
|
||||||
self._net()
|
self._net()
|
||||||
self.assertEqual(commission.mac_suffix(), "")
|
self.assertEqual(setup.mac_suffix(), "")
|
||||||
|
|
||||||
|
|
||||||
class SmallHelpersTests(unittest.TestCase):
|
class SmallHelpersTests(unittest.TestCase):
|
||||||
def test_cookie_parsing(self):
|
def test_cookie_parsing(self):
|
||||||
sid = "ab" * 32
|
sid = "ab" * 32
|
||||||
value = "ffsid=%s; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=43200" % sid
|
value = "ffsid=%s; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=43200" % sid
|
||||||
self.assertEqual(commission.session_from_cookie(value), sid)
|
self.assertEqual(setup.session_from_cookie(value), sid)
|
||||||
self.assertEqual(commission.cookie_flags(value), {"path", "httponly", "secure", "samesite", "max-age"})
|
self.assertEqual(setup.cookie_flags(value), {"path", "httponly", "secure", "samesite", "max-age"})
|
||||||
self.assertIsNone(commission.session_from_cookie("ffsid=; Path=/; Max-Age=0"))
|
self.assertIsNone(setup.session_from_cookie("ffsid=; Path=/; Max-Age=0"))
|
||||||
self.assertIsNone(commission.session_from_cookie(""))
|
self.assertIsNone(setup.session_from_cookie(""))
|
||||||
|
|
||||||
def test_the_teal_breathe_cue(self):
|
def test_the_teal_breathe_cue(self):
|
||||||
self.assertTrue(commission.breathes_teal({"target": [0, 180, 200], "pulse_on": [0, 1400, 1400],
|
self.assertTrue(setup.breathes_teal({"target": [0, 180, 200], "pulse_on": [0, 1400, 1400],
|
||||||
"pulse_off": [0, 1400, 1400]}))
|
"pulse_off": [0, 1400, 1400]}))
|
||||||
self.assertFalse(commission.breathes_teal({"target": [0, 255, 40], "pulse_on": [0, 0, 0],
|
self.assertFalse(setup.breathes_teal({"target": [0, 255, 40], "pulse_on": [0, 0, 0],
|
||||||
"pulse_off": [0, 0, 0]})) # solid green
|
"pulse_off": [0, 0, 0]})) # solid green
|
||||||
self.assertFalse(commission.breathes_teal({"target": [200, 200, 200], "pulse_on": [1800, 1800, 1800],
|
self.assertFalse(setup.breathes_teal({"target": [200, 200, 200], "pulse_on": [1800, 1800, 1800],
|
||||||
"pulse_off": [1800] * 3})) # white
|
"pulse_off": [1800] * 3})) # white
|
||||||
self.assertFalse(commission.breathes_teal({"target": [None, None, None], "pulse_on": [None] * 3}))
|
self.assertFalse(setup.breathes_teal({"target": [None, None, None], "pulse_on": [None] * 3}))
|
||||||
self.assertFalse(commission.breathes_teal({}))
|
self.assertFalse(setup.breathes_teal({}))
|
||||||
|
|
||||||
def test_decode(self):
|
def test_decode(self):
|
||||||
self.assertEqual(commission.decode(b'{"a": 1}'), {"a": 1})
|
self.assertEqual(setup.decode(b'{"a": 1}'), {"a": 1})
|
||||||
self.assertEqual(commission.decode(b"cloud mode is not enabled"), "cloud mode is not enabled")
|
self.assertEqual(setup.decode(b"cloud mode is not enabled"), "cloud mode is not enabled")
|
||||||
|
|
||||||
|
|
||||||
class GatedSettleTests(unittest.TestCase):
|
class GatedSettleTests(unittest.TestCase):
|
||||||
"""A supervisor that reports the commissioning gate closed is settled:
|
"""A supervisor that reports the setup gate closed is settled:
|
||||||
it spawns nothing until the gate opens, so a takeover that installs
|
it spawns nothing until the gate opens, so a takeover that installs
|
||||||
a gating record must not wait the whole settle timeout for it."""
|
a gating record must not wait the whole settle timeout for it."""
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ class GatedSettleTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_gated_returns_at_once(self):
|
def test_gated_returns_at_once(self):
|
||||||
self.fake.state["mode"] = {"mode": "grbl", "controller": "gated", "pid": 0, "motion": "unverified",
|
self.fake.state["mode"] = {"mode": "grbl", "controller": "gated", "pid": 0, "motion": "unverified",
|
||||||
"gated": True, "why": "commissioning required: machine"}
|
"gated": True, "why": "setup required: machine"}
|
||||||
lines = []
|
lines = []
|
||||||
body = baseline.Baseline(lines.append).wait_settled(timeout=8)
|
body = baseline.Baseline(lines.append).wait_settled(timeout=8)
|
||||||
self.assertEqual(body["controller"], "gated")
|
self.assertEqual(body["controller"], "gated")
|
||||||
@@ -284,7 +284,7 @@ class FirstRunSeedTests(unittest.TestCase):
|
|||||||
"advisories": {"safety-and-risk": {"hash": "x"}}, "acceptance": {"pressed_at": "t"},
|
"advisories": {"safety-and-risk": {"hash": "x"}}, "acceptance": {"pressed_at": "t"},
|
||||||
"account": {"name": "scott", "uid": 1000}, "flags": {"flow_thin": True},
|
"account": {"name": "scott", "uid": 1000}, "flags": {"flow_thin": True},
|
||||||
"wizards": {"advisories": {"version": 1}, "switches": {"version": 1, "result": {"lid": True}}}}
|
"wizards": {"advisories": {"version": 1}, "switches": {"version": 1, "result": {"lid": True}}}}
|
||||||
rec = commission.first_run_record(self.STATUS, base)
|
rec = setup.first_run_record(self.STATUS, base)
|
||||||
self.assertNotIn("completed", rec)
|
self.assertNotIn("completed", rec)
|
||||||
self.assertNotIn("acceptance", rec)
|
self.assertNotIn("acceptance", rec)
|
||||||
self.assertNotIn("account", rec)
|
self.assertNotIn("account", rec)
|
||||||
@@ -296,7 +296,7 @@ class FirstRunSeedTests(unittest.TestCase):
|
|||||||
self.assertEqual(rec["wizards"]["motion"]["version"], 2) # written at the catalog version
|
self.assertEqual(rec["wizards"]["motion"]["version"], 2) # written at the catalog version
|
||||||
|
|
||||||
def test_without_a_base_record(self):
|
def test_without_a_base_record(self):
|
||||||
rec = commission.first_run_record(self.STATUS, None)
|
rec = setup.first_run_record(self.STATUS, None)
|
||||||
self.assertEqual(sorted(rec["wizards"]), ["motion", "sheet.frame", "switches"])
|
self.assertEqual(sorted(rec["wizards"]), ["motion", "sheet.frame", "switches"])
|
||||||
self.assertEqual(rec["wizards"]["sheet.frame"]["version"], 1)
|
self.assertEqual(rec["wizards"]["sheet.frame"]["version"], 1)
|
||||||
|
|
||||||
@@ -312,7 +312,7 @@ class SshdPolicyTests(unittest.TestCase):
|
|||||||
real = hw.run
|
real = hw.run
|
||||||
hw.run = fake_run
|
hw.run = fake_run
|
||||||
try:
|
try:
|
||||||
self.assertEqual(commission.sshd_policy(), {"permitrootlogin": "no", "passwordauthentication": "yes",
|
self.assertEqual(setup.sshd_policy(), {"permitrootlogin": "no", "passwordauthentication": "yes",
|
||||||
"permitemptypasswords": "no"})
|
"permitemptypasswords": "no"})
|
||||||
finally:
|
finally:
|
||||||
hw.run = real
|
hw.run = real
|
||||||
@@ -323,7 +323,7 @@ class SshdPolicyTests(unittest.TestCase):
|
|||||||
real = hw.run
|
real = hw.run
|
||||||
hw.run = lambda cmd, timeout=60: (1, "sshd: no hostkeys available")
|
hw.run = lambda cmd, timeout=60: (1, "sshd: no hostkeys available")
|
||||||
try:
|
try:
|
||||||
self.assertEqual(commission.sshd_policy(), {"error": "sshd: no hostkeys available"})
|
self.assertEqual(setup.sshd_policy(), {"error": "sshd: no hostkeys available"})
|
||||||
finally:
|
finally:
|
||||||
hw.run = real
|
hw.run = real
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ class CloudDisabledSurfaceTests(unittest.TestCase):
|
|||||||
self.fake.stop()
|
self.fake.stop()
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
t = catalog.load_suite()["commission.cloud-disabled-surface"]
|
t = catalog.load_suite()["setup.cloud-disabled-surface"]
|
||||||
run = Run("test", t.id, t.title)
|
run = Run("test", t.id, t.title)
|
||||||
t.fn(Context(run, None, t))
|
t.fn(Context(run, None, t))
|
||||||
return run
|
return run
|
||||||
Reference in New Issue
Block a user