Files
firmware/test/printer-exec.sh
T
Oleksandr ShyshatskyiandClaude Opus 5 22d16f984e Run the printer's own boot script for the update test, and cut the suite to 5m
The install test used to re-implement app_startup.sh: it pulled the glob,
MACHINE and PID out of it with sed, then called the installer itself. A
mistake in our reading of the boot script could never be caught that way.

The package now sits on a real FAT filesystem on a loop device exposed as
/dev/sda1, and app_startup.sh runs verbatim through three boots -- stick in,
stick still in, stick pulled. The last one asserts what actually matters:
nginx and moonraker up, klipper launched, the UI in the foreground where
app_startup.sh's own 5-second ps watchdog can see it.

One substitution, declared in .SIMULATED: the printer's kernel has nls_cp936
and a container kernel does not, so /bin/mount retries without codepage= if
the real options are rejected.

Speed. The image carried the partitions but not the stock install, and
test-env.sh sourced the config files after the environment -- so PRINTER_IMAGE
on the command line was overwritten by the empty value in test.env and the
published image was never used. Measured, per replica case: 22s to unpack the
factory image, 37s to install the stock package, 0.7s with the image. The
stock install is now baked in at image build time (bake.sh -- it needs binfmt
and chroot, so it runs privileged and the result is committed) and recorded in
/usr/prog/.BASELINE; a run asking for a different package reinstalls.

make test: ~20m -> 5m26s. Every section header now carries elapsed seconds.

Dropped four checks that could not fail:

  * test-abi's execution half was `qemu "$f" -h || [ $? -lt 126 ]`, which
    accepts nearly every exit status
  * test-ash's md5sum -s probe printed "inconclusive" on every path
  * run-tests.sh's per-file `sh -n` "POSIX" loop ran bash, and its bashism
    grep flagged `local`, which busybox ash supports
  * CI's shellcheck ended in `|| true`

And one that mattered more: the release workflow's final sim-install loop
ignored every exit status, and sim-install.sh exits 0 on a skip -- so the last
gate before publishing could not fail. It now stops on the first failure and
runs with REQUIRE_PRINTER_SIM=1, which turns skips into failures throughout.

Also: the profile lists in run-tests.sh, RECOVERY_PROFILE and release.yml
still said ssh/web/full/helix and would have failed the release job; work/.sim
was shared, so two suites at once truncated each other's packages; and the
fixture half left a 380KB package in work/out where make test-install picks it
up.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GgNhSa1Qvwvxb2nQXT6b1Y
2026-08-23 16:36:45 +03:00

112 lines
4.6 KiB
Bash

#!/usr/bin/env bash
# Host-side launcher for the printer replica.
#
# test/printer-exec.sh <case-script> [name=package.tgz ...]
#
# The case script is executed by the printer's own busybox inside a chroot of
# the real extracted rootfs.squashfs, with MIPS binaries running under
# qemu-user. Named packages appear on the simulated USB stick as /mnt/<name>.
#
# BASE_PKG=<stock .tgz> installs stock firmware first, so /usr/prog holds the
# genuine klipper tree, unTar, app_startup.sh and firmwareExe rather than
# hand-written fakes.
#
# USB_STICK=1 puts the packages on a real FAT filesystem exposed as /dev/sda1
# instead of dropping them into /mnt, so the case script can let the printer's
# own app_startup.sh discover and mount them. Required by case-install.sh.
#
# PROG_DUMP=<tar or dir> supplies a real /usr/prog taken off a printer
# (`tar -cf /mnt/prog.tar /usr/prog` over ssh). With one, the replica has no
# invented files left at all.
set -uo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CASE="${1:?usage: printer-exec.sh <case-script> [name=pkg.tgz ...]}"; shift
# A skip here is a test that did not run. That is fine on a laptop and fatal
# in a release, so REQUIRE_PRINTER_SIM=1 turns every skip into a failure.
skip() {
if [ "${REQUIRE_PRINTER_SIM:-0}" = 1 ]; then echo " FAIL: $*" >&2; exit 1; fi
echo " SKIP: $*"; exit 0
}
DOCKER=docker
command -v docker >/dev/null 2>&1 || DOCKER=docker.exe
command -v $DOCKER >/dev/null 2>&1 || skip "docker not available"
$DOCKER info >/dev/null 2>&1 || skip "docker daemon not running"
# PRINTER_IMAGE names a prebuilt image that already carries the firmware --
# rootfs, /usr/prog and /usr/data baked in by test/build-printer-image.sh. With
# one, nothing local is needed and a run skips the unpack entirely.
IMAGE="${PRINTER_IMAGE:-}"
PREBUILT=1
if [ -z "$IMAGE" ]; then
PREBUILT=0
# Say why this is about to be slow. Unpacking the factory image is ~22s
# and installing the stock baseline is ~37s, on EVERY case; the published
# image has both done already and starts in under a second.
echo " printer-sim: PRINTER_IMAGE is not set, so the replica is being built" >&2
echo " locally -- about a minute of setup per test case. Set" >&2
echo " PRINTER_IMAGE=monstrofil/creator5-printer:latest in" >&2
echo " test.env to skip it." >&2
IMAGE=creator5-printer-sim
if [ ! -d "$ROOT/work/rootfs/bin" ]; then
skip "no printer rootfs -- run 'make rootfs' first (needs the stock package),
or set PRINTER_IMAGE to a prebuilt printer image"
fi
# Always rebuild: it is a cache hit in about a second, and a stale image
# silently testing yesterday's harness is not a trade worth making.
$DOCKER build -q -t "$IMAGE" -f "$ROOT/test/printer/Dockerfile" "$ROOT/test/printer" >/dev/null \
|| { echo " FAIL: could not build $IMAGE"; exit 1; }
fi
# Stage every input inside the repo: the docker daemon resolves bind-mount
# paths on the host, and the repo is the one directory guaranteed to exist
# there under the same name.
# Per-process, so two suites running at once cannot delete each other's
# staged packages half way through a run.
STAGE="$ROOT/work/.sim-$$"
rm -rf "$STAGE"; mkdir -p "$STAGE/pkgs"
trap 'rm -rf "$STAGE"' EXIT # the staged packages are ~80MB each
cp "$CASE" "$STAGE/case.sh"
PKGS=""
for spec in "$@"; do
name="${spec%%=*}"; path="${spec#*=}"
cp "$path" "$STAGE/pkgs/$name"
PKGS="$PKGS $name=/pkgs/$name"
done
BASE_ARG=""
if [ -n "${BASE_PKG:-}" ]; then
cp "$BASE_PKG" "$STAGE/pkgs/base.tgz"
BASE_ARG="/pkgs/base.tgz"
fi
DUMP_MOUNT=""
DUMP_ARG=""
if [ "$PREBUILT" = 0 ] && [ -n "${PROG_DUMP:-}" ] && [ -e "$PROG_DUMP" ]; then
DUMP_ABS="$(cd "$(dirname "$PROG_DUMP")" && pwd)/$(basename "$PROG_DUMP")"
DUMP_MOUNT="-v $DUMP_ABS:/progdump:ro"
DUMP_ARG="/progdump"
fi
# --privileged is needed for exactly two things: registering the binfmt handler
# for the printer's MIPS binaries, and building the mount layout that gives the
# replica a read-only root with writable prog/data partitions.
ROOTFS_MOUNT="-v $ROOT/work/rootfs:/rootfs:ro"
[ "$PREBUILT" = 1 ] && ROOTFS_MOUNT="" # the image carries its own firmware
# shellcheck disable=SC2086
$DOCKER run --rm -i --privileged \
$ROOTFS_MOUNT \
-v "$STAGE/pkgs:/pkgs:ro" \
-v "$STAGE/case.sh:/case.sh:ro" \
-v "$ROOT/payload:/payload:ro" \
-e "FF_KEY=${FF_KEY:-FFP0331&*%root}" \
-e "BASE_PKG=$BASE_ARG" \
-e "PKGS=$PKGS" \
-e "PROG_DUMP=$DUMP_ARG" $DUMP_MOUNT \
-e "PROG_MB=${PROG_MB:-}" -e "DATA_MB=${DATA_MB:-}" -e "SIM_VERBOSE=${SIM_VERBOSE:-0}" \
-e "USB_STICK=${USB_STICK:-0}" \
"$IMAGE" /case.sh