mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-09-27 16:51:12 -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,78 @@
|
||||
"""Shared fixtures for the forgetest unit tests: synthetic manifests and
|
||||
catalog entries that never touch hardware."""
|
||||
import hashlib
|
||||
import os
|
||||
import sys
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.insert(0, os.path.dirname(HERE))
|
||||
|
||||
from forgetest import catalog as catalog_mod # noqa: E402
|
||||
from forgetest import manifest as manifest_mod # noqa: E402
|
||||
|
||||
|
||||
def blob(text):
|
||||
"""A git-style blob id for text (what ls-tree would report)."""
|
||||
data = text.encode()
|
||||
return hashlib.sha1(b"blob %d\0" % len(data) + data).hexdigest()
|
||||
|
||||
|
||||
def make_manifest(components=None, platform=None, version="20260101000000 (dev)", name="forgefirm-image-dev"):
|
||||
comps = components if components is not None else {
|
||||
"forgectrl": {"srcrev": "aaa", "files": [["src/main.c", blob("main")], ["src/ui.c", blob("ui")],
|
||||
["src/auth.c", blob("auth")], ["src/cool.c", blob("cool")],
|
||||
["README.md", blob("readme")]]},
|
||||
"grblhal-glowforge": {"srcrev": "bbb", "files": [["src/driver.c", blob("drv")],
|
||||
["src/grbl", "cccc"], ["src/grbl/core.c", blob("core")]]},
|
||||
"kernel-module-glowforge": {"srcrev": "ddd", "files": [["src/cnc.c", blob("cnc")]]},
|
||||
"linux-fslc": {"srcrev": "eee", "files": [["@srcrev", "eee"], ["@config", "cfg1"]]},
|
||||
"forgetest": {"srcrev": None, "files": [["forgetest/x.py", blob("x")]]},
|
||||
}
|
||||
plat = platform if platform is not None else {
|
||||
"machine": "glowforge", "kernel_modules": ["6.12.20-fslc+g0e01ec9f0d3f"],
|
||||
"dtb": {"glowforge.dtb": "d" * 64},
|
||||
"layers": {"meta-forgefirm": {"content_sha256": "1" * 64}, "poky": {"rev": "p" * 40}},
|
||||
}
|
||||
data = {"format": 1, "image": {"name": name, "version": version},
|
||||
"components": comps, "platform": plat}
|
||||
data["content_sha256"] = manifest_mod.sha256_text(
|
||||
manifest_mod.canonical({"components": comps, "platform": plat}))
|
||||
return manifest_mod.Manifest(data)
|
||||
|
||||
|
||||
def with_file(manifest, component, path, text):
|
||||
"""A copy of the manifest with one file's content changed/added."""
|
||||
import copy
|
||||
data = copy.deepcopy(manifest.data)
|
||||
files = data["components"][component]["files"]
|
||||
for f in files:
|
||||
if f[0] == path:
|
||||
f[1] = blob(text)
|
||||
break
|
||||
else:
|
||||
files.append([path, blob(text)])
|
||||
data["content_sha256"] = manifest_mod.sha256_text(
|
||||
manifest_mod.canonical({"components": data["components"], "platform": data["platform"]}))
|
||||
return manifest_mod.Manifest(data)
|
||||
|
||||
|
||||
def with_platform(manifest, **changes):
|
||||
import copy
|
||||
data = copy.deepcopy(manifest.data)
|
||||
data["platform"].update(changes)
|
||||
data["content_sha256"] = manifest_mod.sha256_text(
|
||||
manifest_mod.canonical({"components": data["components"], "platform": data["platform"]}))
|
||||
return manifest_mod.Manifest(data)
|
||||
|
||||
|
||||
def _noop(ctx):
|
||||
pass
|
||||
|
||||
|
||||
def make_test(id, covers, always=False, requires=(), kind="auto", fn=None, subsystem=None):
|
||||
return catalog_mod.Test(id, "Title " + id, subsystem or id.split(".")[0], kind, "api",
|
||||
covers, requires, always, 1, (), "desc", fn or _noop)
|
||||
|
||||
|
||||
def registry(*tests):
|
||||
return {t.id: t for t in tests}
|
||||
Reference in New Issue
Block a user