mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-18 10:31:42 -07:00
forgetest: the release acceptance tool and the bench diagnostics page
A stdlib-only daemon on the dev image (HTTP :8090) that runs the acceptance catalog against the machine from a self-contained page, keeps the append-only result log under /data/forgetest, and exports the release artifact the gate reads. Tests declare kind (auto / operator / live), hardware (api / takeover), coverage globs, prerequisites, and core membership; a test's domain fingerprint is the hash of the manifest files its globs select plus the platform and its own implementation, so a PASS stays valid exactly while nothing it covers changed. Campaign rules: a FAIL ends the campaign, the core (image health, kernel latch and drills, one live emission witness) is never inherited, invalidate-all forces a full campaign, no SKIP. Live tests need the operator acknowledgment and the physical arm press through the controller; takeover tests stop forgectrl for the duration with a crash-recoverable marker; the tool never touches the laser latch. Catalog v1: 24 tests ported from the proven bench drills with their recorded pass criteria (image, kernel K1-K3 and fire A/B/U, forgectrl API and logs, motion incl. dead-man, cooling, live laser, camera, update, cloud). The bench tab lists every scripts/bench tool and runs the board-side ones as subprocesses (takeover tools wrapped). 44 host unit tests, including the gate verification fixtures. Installed only by forgefirm-image-dev, with the bench scripts under /usr/share/forgetest/bench.
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
"""camera.* - the lid camera pipeline through forgectrl."""
|
||||
from ..catalog import test
|
||||
|
||||
_CAM_COVERS = [("forgectrl", "src/cam.*"), ("forgectrl", "src/debayer.*"), ("forgectrl", "src/vpu_jpeg.*"),
|
||||
("python3-gfhardware", "gfhardware/src/**"), ("python3-gfhardware", "gfhardware/cam*")]
|
||||
|
||||
|
||||
@test("camera.snapshot", title="Lid camera snapshot and stream", subsystem="camera",
|
||||
kind="operator", est_min=2,
|
||||
covers=_CAM_COVERS, requires=["forgectrl.panel-serves"],
|
||||
steps=["Lid closed. You will be asked to look at the control panel's Status tab."],
|
||||
description="/cam/snapshot returns a JPEG of a plausible size (a black frame compresses "
|
||||
"far smaller), the MJPEG stream starts and stops, and the operator confirms "
|
||||
"the panel shows the bed.")
|
||||
def snapshot(ctx):
|
||||
fc = ctx.forgectrl
|
||||
ev = ctx.evidence
|
||||
st, body = fc.get("/cam/status")
|
||||
ctx.check(st == 200 and isinstance(body, dict), "GET /cam/status -> %s", st)
|
||||
ev["cam_status"] = body
|
||||
ctx.log("cam status: %s", body)
|
||||
|
||||
st, data = fc.get("/cam/snapshot", params={"cam": "lid", "res": "half"}, raw=True)
|
||||
ctx.log("GET /cam/snapshot?cam=lid&res=half -> %s (%d bytes)", st, len(data) if data else 0)
|
||||
ctx.check(st == 200, "snapshot -> %s %s", st, data[:120] if data else "")
|
||||
ctx.check(data[:2] == b"\xff\xd8" and data[-2:] == b"\xff\xd9", "snapshot is not a complete JPEG")
|
||||
ev["snapshot_bytes"] = len(data)
|
||||
ctx.check(len(data) > 20000, "snapshot is only %d bytes - a dark or empty frame?", len(data))
|
||||
|
||||
st, data = fc.get("/cam/snapshot", params={"cam": "lid", "res": "full"}, raw=True)
|
||||
ctx.log("GET /cam/snapshot?cam=lid&res=full -> %s (%d bytes)", st, len(data) if data else 0)
|
||||
ctx.check(st == 200 and data[:2] == b"\xff\xd8", "full-resolution snapshot -> %s", st)
|
||||
ev["snapshot_full_bytes"] = len(data)
|
||||
|
||||
# the stream: fetch a little of it and let it close
|
||||
import urllib.request
|
||||
req = urllib.request.Request(fc.base + "/cam/stream", headers={"Host": fc.host_header()})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as r:
|
||||
ctype = r.headers.get("Content-Type", "")
|
||||
chunk = r.read(65536)
|
||||
except Exception as e: # noqa: BLE001
|
||||
ctx.fail("stream did not open: %s", e)
|
||||
ev["stream_ctype"] = ctype
|
||||
ctx.log("stream: %s, first %d bytes", ctype, len(chunk))
|
||||
ctx.check("multipart" in ctype and b"\xff\xd8" in chunk, "stream is not an MJPEG multipart")
|
||||
ctx.sleep(2)
|
||||
st, body = fc.get("/cam/status")
|
||||
ev["cam_status_after"] = body
|
||||
ctx.log("cam status after: %s", body)
|
||||
ctx.confirm("Open the control panel (port 8080), Status tab: does the lid snapshot show the bed "
|
||||
"(not black, not frozen, roughly the right orientation)?")
|
||||
Reference in New Issue
Block a user