mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -07:00
forgetest: the operator's part asked for by name, and fewer hands in a campaign
A campaign asked a person for about eighty things: lid, button and interlock actions, app jobs, and sixteen confirmations by eye, most of them as popups to read and answer while the head was already moving. This is the forgetest-only step of cutting that down. The operator channel. A test asks for its operator's part in four ways: ctx.ready() pre-announces a timed step and waits for the click that starts it; ctx.notice() is a standing instruction with no button, the test watching the machine for the result; ctx.act(channel, state) is a machine action by name (lid, interlock, button) - a notice for the operator today, proven done by the switch reading or an `until` condition, recorded in evidence.actions with who performed it, and the seam a bench actuator plugs into through runner.fixture; ctx.confirm() stays for the yes/no the evidence cannot answer. Tests declare `actions`; a `precheck` refuses a start the machine cannot honor (a reason, no result, a queue skips it and carries on). The page shows what you will do before it is asked: the running test's steps, a queue's attended tests still waiting, or the test whose title you clicked while idle; notices and prompts sit under it. The campaign card no longer carries baseline, queue and leftover notes: those go to the runner journal (daemon.log, syslog as `forgetest`, and the run in progress), with a Runner journal button in the footer. The catalog, 43 tests (27 auto, 8 operator, 8 live; was 45: 25/12/8): cloud.mode-switch absorbs cloud.hunt-lid-open and cloud.gfhome-homing (the connect made with the lid open, the hunt judged lid-open with its Z cycle, the re-hunt waited out after the close, the switch back, then $H judged by gfhome's own "homing complete" line with its motion windows; precheck homing_mode = gfcloud). kernel.fire-line is auto with the HV-not-good precheck, camera.snapshot is auto (a second frame with the lid lamp off differs and is smaller), motion.jog-roundtrip is auto (the head accelerometer per leg, the supervisor's own witness). The remaining attended tests use Ready gates and act(); the head's beam detector and the button LEDs replace the eye, leaving two confirms: the emission witness's mark and the app's display in cloud.pause-resume. Proof: tests/test_operator.py (the channel, the precheck, the journal), the cloud replays re-targeted to the merged round trip over a fake grbl and the bench excerpts, 196 host tests green, coverage lint 0 uncovered. Every re-ported attended test is owed one bench run on the next dev image (BRINGUP). Catalog consequence: the merged and reclassified tests' implementation hashes move; nothing else is invalidated.
This commit is contained in:
@@ -69,9 +69,11 @@ def _noop(ctx):
|
||||
pass
|
||||
|
||||
|
||||
def make_test(id, covers, always=False, requires=(), kind="auto", fn=None, subsystem=None, mode=None):
|
||||
def make_test(id, covers, always=False, requires=(), kind="auto", fn=None, subsystem=None, mode=None,
|
||||
actions=(), precheck=None, steps=()):
|
||||
return catalog_mod.Test(id, "Title " + id, subsystem or id.split(".")[0], kind, "api",
|
||||
covers, requires, always, 1, (), "desc", fn or _noop, mode=mode)
|
||||
covers, requires, always, 1, steps, "desc", fn or _noop, mode=mode,
|
||||
actions=actions, precheck=precheck)
|
||||
|
||||
|
||||
def registry(*tests):
|
||||
@@ -165,3 +167,90 @@ class FakeForgectrl:
|
||||
self._srv.server_close()
|
||||
os.environ.pop("FORGECTRL_URL", None)
|
||||
os.environ.pop("FORGECTRL_TOKEN_FILE", None)
|
||||
|
||||
|
||||
# ------------------------------------------------------------ fake grbl
|
||||
|
||||
class FakeGrbl:
|
||||
"""A Grbl-over-TCP stand-in for the controller: answers '?' with a
|
||||
status report built from its mutable `state`/`mpos`, 'ok' to every
|
||||
command line, and records what it was sent. Point the suite at it
|
||||
with GRBL_HOST/GRBL_PORT (see start/stop)."""
|
||||
|
||||
def __init__(self):
|
||||
import socket as _socket
|
||||
import threading as _threading
|
||||
self.state = "Idle"
|
||||
self.mpos = [0.0, 0.0, 0.0]
|
||||
self.sent = []
|
||||
self.extra = b"" # text pushed to the client on the next poll
|
||||
self._sock = _socket.socket()
|
||||
self._sock.setsockopt(_socket.SOL_SOCKET, _socket.SO_REUSEADDR, 1)
|
||||
self._sock.bind(("127.0.0.1", 0))
|
||||
self._sock.listen(5)
|
||||
self.port = self._sock.getsockname()[1]
|
||||
self._stop = False
|
||||
self._th = _threading.Thread(target=self._serve, daemon=True)
|
||||
|
||||
def _serve(self):
|
||||
import socket as _socket
|
||||
import threading as _threading
|
||||
self._sock.settimeout(0.2)
|
||||
while not self._stop:
|
||||
try:
|
||||
c, _ = self._sock.accept()
|
||||
except (_socket.timeout, OSError):
|
||||
continue
|
||||
_threading.Thread(target=self._client, args=(c,), daemon=True).start()
|
||||
|
||||
def _client(self, c):
|
||||
import socket as _socket
|
||||
c.settimeout(0.1)
|
||||
buf = b""
|
||||
try:
|
||||
c.sendall(b"\r\nGrblHAL 1.1f ['$' for help]\r\n")
|
||||
while not self._stop:
|
||||
try:
|
||||
d = c.recv(4096)
|
||||
if not d:
|
||||
break
|
||||
buf += d
|
||||
except _socket.timeout:
|
||||
continue
|
||||
except OSError:
|
||||
break
|
||||
out = b""
|
||||
if self.extra:
|
||||
out += self.extra
|
||||
self.extra = b""
|
||||
while buf:
|
||||
if buf[0:1] == b"?":
|
||||
buf = buf[1:]
|
||||
out += ("<%s|MPos:%.3f,%.3f,%.3f|FS:0,0>\r\n" % (self.state, *self.mpos)).encode()
|
||||
elif buf[0] in (0x18, 0x85, 0x7E, 0x21):
|
||||
buf = buf[1:]
|
||||
elif b"\n" in buf:
|
||||
line, buf = buf.split(b"\n", 1)
|
||||
self.sent.append(line.strip().decode("utf-8", "replace"))
|
||||
out += b"ok\r\n"
|
||||
else:
|
||||
break
|
||||
if out:
|
||||
c.sendall(out)
|
||||
finally:
|
||||
c.close()
|
||||
|
||||
def start(self):
|
||||
self._th.start()
|
||||
os.environ["GRBL_HOST"] = "127.0.0.1"
|
||||
os.environ["GRBL_PORT"] = str(self.port)
|
||||
return self
|
||||
|
||||
def stop(self):
|
||||
self._stop = True
|
||||
try:
|
||||
self._sock.close()
|
||||
except OSError:
|
||||
pass
|
||||
for k in ("GRBL_HOST", "GRBL_PORT"):
|
||||
os.environ.pop(k, None)
|
||||
|
||||
Reference in New Issue
Block a user