acceptance: a failed check prints the serial log where it looked (#136)
Build / Image PC (push) Successful in 3m20s
Build / v4 tests (32- and 64-bit cells) (push) Successful in 10m1s
Build / Image MILKV (push) Successful in 2m55s
Build / Image RASPI (push) Successful in 2m42s
Acceptance / Acceptance (pull_request) Successful in 18m44s

Run 197's riscv64 failure (#131) could only be read from the run's logs
artifact, which Gitea's API does not list. qemu_session.py's check() now
prints, on a FAIL, the serial lines after the first match of an anchor the
check names, or the log's last lines; control characters as escapes, so a
stray byte or terminal sequence in a line shows instead of only failing a
match. The boot-disk checks anchor on 'Boot successful!'. A passing run
prints as before (riscv64 here, logs/ committed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GE3SDvQn5EoxdGufk84zz2
This commit is contained in:
Claude
2026-11-07 22:49:03 +00:00
parent 777f739708
commit 1d4898496f
3 changed files with 31 additions and 4 deletions
+28 -4
View File
@@ -213,9 +213,33 @@ def wait_for(pat, secs, after=0):
return False
def check(name, cond):
def excerpt(near=None, after=20, tail=40):
"""The serial log where a failed check looked (#136): the lines after the first match of
`near`, else the log's last lines -- control characters shown as escapes, so a terminal's
sequences cannot garble the job's output."""
lines = text().splitlines()
start = None
if near is not None:
start = next((i for i, l in enumerate(lines) if re.search(near, l)), None)
if start is None:
start, what = max(0, len(lines) - tail), f"the log's last {tail} lines"
stop = len(lines)
else:
stop, what = min(len(lines), start + 1 + after), f"after /{near}/, line {start + 1}"
print(f" --- serial log, {what} ({os.path.relpath(log, ROOT)}):", flush=True)
for i in range(start, stop):
shown = "".join(c if c.isprintable() else repr(c)[1:-1] for c in lines[i])
print(f" {i + 1:6} | {shown}", flush=True)
print(" ---", flush=True)
def check(name, cond, near=None):
"""PASS or FAIL; a FAIL also prints the serial log where the check looked: after the first
line matching `near` (a regex), when it is given, else the log's end."""
global ok
print(f"{'PASS' if cond else 'FAIL'}: {name}", flush=True)
if not cond:
excerpt(near)
ok = ok and cond
@@ -328,7 +352,7 @@ if m == "headless":
elif m == "zuse":
check("Boot disk: not a StarshipOS stick (an ISO or ESP boot)",
re.search(r"Boot disk: not a StarshipOS stick", text()) is not None)
re.search(r"Boot disk: not a StarshipOS stick", text()) is not None, near=r"Boot successful!")
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))
n = count(PROMPT)
@@ -343,7 +367,7 @@ elif m in ("stick_boot", "two_sticks"):
if m == "stick_boot":
# the PC stick: Artemis is the rest of 2 GiB (well over 1 million user blocks), its blank
# identity 500 MiB (about 380000)
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None)
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None, near=r"Boot successful!")
check("System: StarForth v4", re.search(r"System: StarForth v4", text()) is not None)
check("Artemis: the stick's partition attached",
wait_for(r"Artemis: the stick's partition attached", 120))
@@ -370,7 +394,7 @@ elif m in ("stick_boot", "two_sticks"):
elif m in ("stick_artemis", "stick_zuse"):
if m == "stick_zuse":
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None)
check("Boot disk: a StarshipOS stick", re.search(r"Boot disk: a StarshipOS stick", text()) is not None, near=r"Boot successful!")
check("Artemis: the stick's partition attached", wait_for(r"Artemis: the stick's partition attached", 120))
check("Zuse: identity confirmed", wait_for(r"Zuse: identity confirmed from attached thumbdrive", 120))
check("[zuse@Hera] ok>", wait_for(PROMPT, 60))