qemu: json/stat/region channels, seeding, driver tests

- rootfs/sbin/init: the control bridge answers `@cat PATH` locally so the
  driver can read /tmp/warden-web-status.json out of the guest. That file
  has no trailing newline; the bridge adds one so the sentinel stays on
  its own line and the line-based reader never blocks.
- tests/qmp.py: wait_json/assert_json (dotted paths, eq/ne/contains/
  len_eq/len_ge/gt/lt), assert_stat off the FIFO's stats reply,
  capture_region, assert_region NAME [TOLERANCE] and assert_ocr. A
  tolerance other than the captured one, a reference box that does not
  fit the screendump, a missing reference or a missing tesseract is FATAL
  for that step and the run continues (flare-edge #147).
- tests/imgtools.py: P6 reader, crop, perceptual and structural hashes,
  compare, with a self-test.
- tests/test_qmp_drive.py: drive() with QMP and the control channel
  faked, pinning the per-step ok/fail/fatal contract.
- mkimage.sh SEED_DIR and ui-drive.sh --seed/--refs: settings fixtures
  staged into userdata before warden-ui starts, and a reference store
  handed to the driver.
- ci: the driver tests and the imgtools self-test run in qemu-tools.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013aHKWzT5EF86RFKRMtAv9n
This commit is contained in:
Noah
2026-09-07 23:26:01 -06:00
co-authored by Claude Fable 5.1
parent 8a57057053
commit 86a9544dcc
7 changed files with 1140 additions and 27 deletions
+46 -13
View File
@@ -129,9 +129,16 @@ fi
# equivalent seam is a second 16550 that run.sh --ctl exposes as a unix socket
# (pci-serial, the same device the RS485 bridge already rides). One command
# per line in, the FIFO's reply out, and a sentinel line so the reader knows
# the reply is complete without a timeout. The vocabulary is identical on both
# sides of that seam, which is what lets one flow script run against the sim
# and against a panel.
# the reply is complete without a timeout. The FIFO vocabulary itself is
# identical on both sides of that seam, which is what lets one flow script
# run against the sim and against a panel.
#
# One exception, answered by the bridge itself and never forwarded to the
# FIFO: `@cat PATH` replies with PATH's contents (or one "bridge: no such
# file: PATH" line if it is missing), then the same sentinel. This is how the
# json flow channel reads webstatus.c's /tmp/warden-web-status.json snapshot
# from OUTSIDE the VM -- on a panel that file is just as reachable over the
# SSH session tools/warden-ctl already has, so hardware needs no equivalent.
#
# run.sh lists the ctl port before any other pci-serial, so it is always the
# first 8250, and it says so with warden.ctl on the command line. The marker,
@@ -149,16 +156,42 @@ if grep -qw warden.ctl /proc/cmdline && [ -c /dev/ttyS0 ]; then
exec 3<> "$ctl"
while IFS= read -r cmd <&3; do
[ -n "$cmd" ] || continue
if [ -p /tmp/warden-ui.ctl ]; then
printf '%s\n' "$cmd" > /tmp/warden-ui.ctl
# The UI polls its FIFO every 100 ms and truncates the reply
# file on each command, so a short settle then a read is the
# same protocol warden-ctl uses over SSH.
sleep 0.3
cat /tmp/warden-ui.dbg 2>/dev/null >&3
else
echo "bridge: warden-ui control FIFO not present" >&3
fi
case "$cmd" in
"@cat "*)
# A bridge-local command, never forwarded to warden-ui's
# FIFO: `@cat PATH` reads PATH directly off the GUEST's
# own filesystem and answers with it, which is how the
# json flow channel gets webstatus.c's snapshot out to
# the host driving the VM from outside. `-f` so a
# directory or device node reports as missing rather than
# cat hanging or erroring oddly.
path="${cmd#@cat }"
if [ -f "$path" ]; then
cat "$path" >&3
# Force a newline after the file's own bytes: the
# status json (webstatus.c) is written with NO
# trailing newline, and without this the sentinel
# below would land on the SAME line as the content
# and the reader (qmp.py Ctl.send, line-based) would
# block forever waiting for a line that never comes.
echo >&3
else
echo "bridge: no such file: $path" >&3
fi
;;
*)
if [ -p /tmp/warden-ui.ctl ]; then
printf '%s\n' "$cmd" > /tmp/warden-ui.ctl
# The UI polls its FIFO every 100 ms and truncates the
# reply file on each command, so a short settle then a
# read is the same protocol warden-ctl uses over SSH.
sleep 0.3
cat /tmp/warden-ui.dbg 2>/dev/null >&3
else
echo "bridge: warden-ui control FIFO not present" >&3
fi
;;
esac
echo "<<END>>" >&3
done
) &