Files
firmware/test/integration/printer-exec.py
T
Oleksandr ShyshatskyiandClaude Opus 5 cd36ff584c Restore the exec bit on 31 scripts, so CI can run any of them
CI has never once run this suite. Four runs, four failures, each about
twenty seconds long, each ending the same way:

    ./test/run-tests.sh: Permission denied      exit 126

Not one tracked file in the repo was 100755. `core.filemode` is false on
my checkout and has to be -- the repo lives on a Windows drive where
every file reads back as 0777, so turning it on would mark the entire
tree executable. The consequence is that `chmod +x` here changes nothing
git can see. Locally everything ran anyway, because make invokes the
launchers through bash and because the working tree claims 0777, so the
only machine that could notice was the one nobody was reading.

The payload was never at risk: patch.sh chmods each file as it installs
it onto the printer. This is about the scripts the host executes.

The exec bit was only the first of two. With modes fixed the fixture job
still exits 1 -- honestly this time. It has no stock package by design,
so two gates cannot run: the tests that read the real rootfs, and the
replica. The suite refuses to call a gate it did not run a pass, which
is the rule the whole harness exists to enforce, so the fixture job is
exactly the case ALLOW_SKIP=1 is for. printer-sim and release.yml keep
REQUIRE_PRINTER_SIM=1 and no ALLOW_SKIP, because there a skip must fail.

test_harness.py gains the guard. It reads the mode from `git ls-files
-s`, not from the filesystem: asking this filesystem answers a question
about Windows, not about what was committed. It names each offender and
prints the fix. It passes `-c safe.directory=*`, because git will not
read a repository owned by another uid and every docker run here is root
over a checkout that is not -- without it the test skips inside the
build image, and release.yml runs the suite there with no ALLOW_SKIP, so
a skip would fail the release.

Writing that test turned up a flaw in the neighbouring one.
compileall.compile_file writes the .pyc beside the source and returns
False when it cannot, which under quiet=2 is indistinguishable from a
syntax error and carries an empty message -- a root-owned __pycache__
reported "python that does not compile:" and then nothing at all. It now
compiles through py_compile with an explicit cfile under tmp_path, so it
touches nothing outside the temp dir and reports the real exception.

VERIFIED:

  - Reproduced the ci.yml test job against the committed tree: a fresh
    clone on ext4, no config.env, no firmware, run in the build image.
    Before, exit 126 on the first line. After: shellcheck ok, 7 passed
    0 failed 2 skipped, exit 0 -- and without ALLOW_SKIP, exit 1, which
    is the behaviour being kept deliberately.
  - Full make test with the real firmware: 11 passed, 0 failed, all
    three replica gates including recovery.
  - The harness tests inside the build image, root over a uid-1000
    checkout -- the release.yml shape: 4 passed, nothing skipped.
  - Mutation: chmod -x a file and the new test fails naming it; append a
    syntax error and the compile test fails with SyntaxError rather than
    an empty string; restore and both pass.
  - Checked every path, make target and shellcheck glob the workflows
    reference: all present, no glob matching zero files.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 14:55:42 +03:00

59 lines
2.1 KiB
Python
Executable File

#!/usr/bin/env python3
"""Run an arbitrary case script inside the printer replica.
./test/integration/printer-exec.py <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.
Named packages appear on the simulated USB stick as /mnt/<name>.
BASE_PKG=<stock .tgz> install 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 put the packages on a real FAT filesystem exposed
as /dev/sda1 instead of dropping them into /mnt, so
the printer's own app_startup.sh has to discover and
mount them. Required by case-install.sh.
PROG_DUMP=<tar|dir> a real /usr/prog taken off a printer. With one, the
replica has no invented files left at all.
"""
import os
import sys
from pathlib import Path
for _p in Path(__file__).resolve().parents:
if (_p / "bin" / "common.sh").is_file():
sys.path.insert(0, str(_p / "test"))
break
from ffsim import cli # noqa: E402
from ffsim.config import Config # noqa: E402
from ffsim.replica import Replica # noqa: E402
def run():
if len(sys.argv) < 2:
raise SystemExit("usage: printer-exec.py <case-script> "
"[name=pkg.tgz ...]")
case = sys.argv[1]
packages = {}
for spec in sys.argv[2:]:
name, _, path = spec.partition("=")
packages[name] = path
config = Config.load()
replica = Replica.start(config, want_output=_echo)
replica.run_case(case, packages=packages,
base_pkg=os.environ.get("BASE_PKG") or None,
usb_stick=os.environ.get("USB_STICK") == "1",
on_output=_echo)
def _echo(text):
sys.stdout.write(str(text).rstrip("\n") + "\n")
sys.stdout.flush()
if __name__ == "__main__":
sys.exit(cli.main(run))