mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-28 09:11:11 -07:00
forgetest: cloud tests stay in cloud mode; the page can ignore prerequisites
The cloud job tests (lid-abort, lid-during-button-wait, hunt-lid-open,
pause-resume) run in cloud mode and leave the machine there: enter_cloud
reuses a live session (pid-scoped from the client's own websocket state
lines) and switches once from GRBL mode, declaring the change to the
baseline; nothing switches back. Each test judges the log from its own
window, the print by its own "print [id]: finished" line, and waits the
service's follow-up moves out (wait_quiet) before it ends. hunt-lid-open
restarts the cloud client through the supervisor's stop/start lever for
a fresh connect. The former switch-back is what failed the last bench run
of hunt-lid-open (409 machine is not idle: the service was still
re-finding the head after the lid closed).
The baseline is mode-aware: in cloud mode the client owns the GRBL
controller's init values, the lid lamp, and the position counters; the
mode itself is preserved unless the run declared the change
(Context.mode_changed); controller_mode is never handed back as a bare
setting (a bare write left the persisted mode out of step with the live
one). The undeclared-change restore now uses the captured state, which
the post pass never saw before.
The acceptance page gets an "Ignore prerequisites" switch (remembered by
the browser): POST /start {ignore_requires} starts a test whose requires
are unmet, and the run records the unmet prerequisites in its evidence
and log; they stay required for the release. The cloud tests' requires
no longer chain through cloud.mode-switch.
Proof: tests/test_cloud_suite.py replays the four tests on the bench's
own gfcloud excerpts (fixtures/) and the run loop's emitted pause/resume
lines under the real runner Context against a fake forgectrl; baseline
mode tests and the server override test; the whole suite (98) and the
coverage lint pass. Catalog consequence: cloud.* fingerprints move with
the module; the catalog hash moves with the requires.
This commit is contained in:
@@ -16,6 +16,17 @@ restores it after (on every exit path), so a test cannot hand the next one
|
||||
The lid lamp is fixed too, at forgectrl's `lid_lamp_idle` setting (unset =
|
||||
236): the daemon asserts it at start and at every controller spawn.
|
||||
|
||||
The controller mode in force decides what the baseline owns. In GRBL mode
|
||||
everything above applies. In cloud mode the cloud client owns what it
|
||||
configures for itself - the GRBL controller's init values (it writes its
|
||||
own from each pulse header), the lid lamp (its lid-image level), and the
|
||||
position counters (re-zeroed at every service action, so not a preserved
|
||||
value) - and the baseline checks the rest: the safety readbacks, the
|
||||
latch, the ring, the module defaults, the diagnostics tools, the cooling
|
||||
engine. The mode itself is preserved: a run hands back the mode it
|
||||
found, unless it declared a deliberate change (Context.mode_changed) -
|
||||
the cloud tests enter cloud mode once and stay there.
|
||||
|
||||
Everything found off-baseline is a "leftover": logged, kept in the run's
|
||||
evidence, and surfaced on the page. The pre-run pass attributes leftovers
|
||||
to the previous run; the post-run pass attributes them to the run itself.
|
||||
@@ -53,6 +64,20 @@ FIXED_SYSFS = [
|
||||
("thermal/tec_on", "0"),
|
||||
]
|
||||
|
||||
# The subset the GRBL controller writes at its start: checked and restored
|
||||
# in GRBL mode only. The cloud client sets its own values for these from
|
||||
# every pulse header (step_freq 10 kHz, the run currents) and hands the
|
||||
# hold currents back at idle; forcing the GRBL values under it would be
|
||||
# the baseline configuring another controller's machine.
|
||||
GRBL_CONTROLLER_SYSFS = ("cnc/motor_lock", "cnc/x_mode", "cnc/y_mode", "cnc/x_decay", "cnc/y_decay",
|
||||
"cnc/step_freq", "pic/x_step_current", "pic/y_step_current")
|
||||
|
||||
# Settings the baseline never hands back as bare settings: controller_mode
|
||||
# is the persisted mirror of the live mode (the mode item restores it
|
||||
# through POST /mode, which keeps the two in step; a bare settings write
|
||||
# would leave the runtime in one mode and the boot in the other).
|
||||
UNPRESERVED_SETTINGS = ("controller_mode",)
|
||||
|
||||
# Read-only readbacks with their idle values (no direct restore: the state
|
||||
# comes right through forgectrl - see restore_forgectrl - or is fatal).
|
||||
IDLE_READBACKS = [
|
||||
@@ -149,6 +174,7 @@ class Baseline:
|
||||
self.abort = abort or (lambda: False)
|
||||
self.captured = None
|
||||
self.forgectrl = None
|
||||
self.mode = None # the controller mode in force (per enforce)
|
||||
|
||||
def log(self, msg):
|
||||
self._log("baseline: " + msg)
|
||||
@@ -228,7 +254,8 @@ class Baseline:
|
||||
cap["sysfs"][attr] = hw.sysfs_read(attr)
|
||||
st, body = self.fc_get("/settings")
|
||||
if st == 200 and isinstance(body, dict):
|
||||
cap["settings"] = {k: v for k, v in body.items() if isinstance(v, str)}
|
||||
cap["settings"] = {k: v for k, v in body.items()
|
||||
if isinstance(v, str) and k not in UNPRESERVED_SETTINGS}
|
||||
st, body = self.fc_get("/mode")
|
||||
if st == 200 and isinstance(body, dict):
|
||||
cap["mode"] = body.get("mode")
|
||||
@@ -241,7 +268,8 @@ class Baseline:
|
||||
phase is 'pre' or 'post' (log wording only). captured is the
|
||||
preserved state to hand back (post) - None compares nothing."""
|
||||
left = []
|
||||
self._forgectrl_side(left)
|
||||
self.mode = None
|
||||
self._forgectrl_side(left, captured)
|
||||
self._kernel_side(left)
|
||||
self._lamp_side(left)
|
||||
self._preserved(left, captured)
|
||||
@@ -259,11 +287,15 @@ class Baseline:
|
||||
time.sleep(1.0)
|
||||
return None
|
||||
|
||||
def _forgectrl_side(self, left):
|
||||
def cloud_mode(self):
|
||||
return self.mode == "cloud"
|
||||
|
||||
def _forgectrl_side(self, left, captured=None):
|
||||
st, mode = self.fc_get("/mode")
|
||||
if st != 200 or not isinstance(mode, dict):
|
||||
self.log("forgectrl not answering - service-side checks skipped")
|
||||
return
|
||||
self.mode = mode.get("mode")
|
||||
# a diagnostic left running seizes the thermal hardware: abort it
|
||||
st, d = self.fc_get("/diag/status")
|
||||
if st == 200 and isinstance(d, dict) and d.get("running"):
|
||||
@@ -278,11 +310,14 @@ class Baseline:
|
||||
CAM_IDLE_S)
|
||||
if w is None:
|
||||
left.append(Leftover("cam.running", True, False, "failed: still running"))
|
||||
# supervisor: the captured mode, controller running, motion verified
|
||||
want = (self.captured or {}).get("mode") or mode.get("mode") or "grbl"
|
||||
# supervisor: the mode the run found (or declared), controller
|
||||
# running, motion verified. A mode the run changed without
|
||||
# declaring it is handed back through the switch.
|
||||
want = (captured or {}).get("mode") or mode.get("mode") or "grbl"
|
||||
if mode.get("mode") != want:
|
||||
st, body = self.fc_post("/mode", data={"controller": want})
|
||||
mode = self.wait_settled() or mode
|
||||
self.mode = mode.get("mode")
|
||||
left.append(Leftover("mode", mode.get("mode"), want,
|
||||
"restored" if mode.get("mode") == want else "failed: %s %s" % (st, body)))
|
||||
if mode.get("controller") == "motion-fault":
|
||||
@@ -336,7 +371,10 @@ class Baseline:
|
||||
"waited" if w is not None else "failed: still %s" % found))
|
||||
|
||||
def _lamp_side(self, left):
|
||||
"""The lid lamp at forgectrl's idle level (the lid_lamp_idle setting)."""
|
||||
"""The lid lamp at forgectrl's idle level (the lid_lamp_idle setting).
|
||||
In cloud mode the cloud client owns the lamp (its lid-image level)."""
|
||||
if self.cloud_mode():
|
||||
return
|
||||
st, body = self.fc_get("/settings")
|
||||
if st != 200 or not isinstance(body, dict):
|
||||
return
|
||||
@@ -373,6 +411,8 @@ class Baseline:
|
||||
left.append(Leftover("pulse ring", "%d unplayed bytes" % residue, "0 (nothing queued)",
|
||||
"unrestorable: the next run would replay them first"))
|
||||
for attr, want in FIXED_SYSFS:
|
||||
if self.cloud_mode() and attr in GRBL_CONTROLLER_SYSFS:
|
||||
continue
|
||||
got = hw.sysfs_read(attr)
|
||||
if got is None or got == want:
|
||||
continue
|
||||
@@ -461,9 +501,11 @@ class Baseline:
|
||||
except OSError as e:
|
||||
act = "failed: %s" % e
|
||||
left.append(Leftover(attr, now, was, act))
|
||||
# In cloud mode the counters are the cloud client's: every service
|
||||
# action re-zeroes them at its start, so they preserve nothing.
|
||||
was = captured.get("position")
|
||||
now = read_position()
|
||||
if was is not None and now is not None and now != was:
|
||||
if was is not None and now is not None and now != was and not self.cloud_mode():
|
||||
left.append(Leftover("position", now, was, self._return_head(was, now)))
|
||||
was = captured.get("settings")
|
||||
if was:
|
||||
|
||||
Reference in New Issue
Block a user