mirror of
https://github.com/Klipper4FlashForge/firmware.git
synced 2026-10-23 05:42:17 -05:00
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>
59 lines
2.1 KiB
Python
Executable File
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))
|