Files
firmware/test/integration/test_paths.py
T
Oleksandr ShyshatskyiandClaude Opus 5 43baf4054e Drop the unit lane; one suite, in test/integration
The split existed so a plain pull request had something to run. This repo has
one maintainer who always has the firmware to hand, so it was a boundary kept
in sync for a contributor who never arrives. One directory now.

The tests moved rather than went away. test_chamber.py is the only check in
the suite that can catch a printer heating something it has no element for,
and the replica cannot cover it: case-install.sh touches .cfg files at exactly
two lines, both a grep for a sentinel proving the user config survived, and it
asserts klippy.py exists and c_helper.so has the right ABI without ever
starting klippy. Declaring [heater_generic chamber_heater] on the plain
Creator 5 fails four of these tests, including M191 waiting on a target the
machine can never reach; that same package installs, boots, and passes every
replica gate. make-stock-fixture.sh came too, since the packaging pipeline is
built on it.

Ordering changed, and that is the part to look at. With two lanes, pytest ran
twice -- once per lane, each with its own -m expression or path. With one it
runs once, so it has to run at the point where it can see the most: the stock
package is now resolved and the rootfs extracted BEFORE pytest rather than
after it. On a configured machine that is 28 tests in one pass; on a bare tree
the five that read the rootfs skip.

config.env is read in a subshell while doing that. It is the BUILD config, and
it is now consulted upstream of the fixture build, where the entire point is
that the fixture config wins -- sourcing it into this shell would put MOD_NAME
and TARGET_MACHINE where that build could see them. Verified by pointing a
config.env at MOD_NAME=LEAKED and confirming the fixture still builds
Creator5Pro-anvil-ci.tgz.

One thing gets worse, and it is inherent to having a single lane: on a tree
with no rootfs, pytest reports 5 skips, so sub() marks the whole gate SKIP
rather than ok, and the summary reads 6 passed instead of 7. Those 23 passes
are real and the line above the SKIP says so. This is the existing policy
working as designed -- a gate that did not fully run must not read as success
-- but the summary is coarser than it was when a lane existed that never
skipped.

Verified: 28 passed with a real extracted rootfs, 23 passed / 5 skipped
without; the full suite gives 7 passed / 1 failed / 1 skipped with a rootfs
present and 6 / 1 / 2 without, the failure being shellcheck, which lives in
the build image; no test/unit reference is left outside the passages that
explain why it is gone.

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

75 lines
3.1 KiB
Python

"""Absolute paths the payload uses must exist on the printer.
What is left of this check is the half that earns its keep: every literal
/bin, /sbin, /etc, /usr/bin, /usr/sbin path in an on-printer script has to
exist in the real extracted rootfs. Its value is the branches no simulation
ever reaches -- S50wifi bails at `[ -d /sys/class/net/wlan0 ]`, which the
replica has not got, so /sbin/udhcpc, /sbin/ifconfig, /usr/sbin/wpa_supplicant
and /usr/sbin/wpa_cli are verified here and nowhere else.
The bare-command-word scan that used to live here is gone, along with
test/applets.allow. It extracted only the first word of a simple, unprefixed
command, so `if timeout 5 foo; then` yielded ['if','echo','fi'] -- the exact
failure its own docstring claimed to guard was invisible to it, as were
`VAR=1 cmd`, `nohup cmd`, and everything reached through a variable or an
absolute path, which is how the payload invokes essentially everything. Its
allowlist had drifted to nine entries that excused nothing. What it could
really catch, case-install.sh catches properly by booting the machine and
grepping the boot log for "command not found".
/usr/prog and /usr/data are deliberately not checked: they live on partitions
the rootfs does not contain, and the install simulation covers them.
The two extractor guards below need no rootfs and so still run when there is
no firmware; they exist to stop the check above from passing vacuously.
"""
import os
import re
import pytest
PATHS = re.compile(
r"(?<![\w.])(/(?:bin|sbin|etc)/[A-Za-z0-9_./-]+|/usr/(?:bin|sbin)/[A-Za-z0-9_./-]+)")
def payload_scripts(root):
out = []
for dirpath, _, names in os.walk(os.path.join(root, "payload")):
for n in names:
if n.endswith(".sh") or n == "firmwareExe" or re.match(r"^S\d", n):
out.append(os.path.join(dirpath, n))
return sorted(out)
def absolute_paths(root):
"""(script, path) for every rootfs-absolute path the payload names."""
for f in payload_scripts(root):
src = open(f, encoding="utf-8", errors="replace").read()
for p in sorted(set(PATHS.findall(src))):
yield os.path.relpath(f, root), p
def test_payload_scripts_are_found(root):
assert payload_scripts(root), "no on-printer scripts found under payload/"
def test_some_absolute_paths_are_checked(root):
"""Guard against the regex silently matching nothing and the test passing."""
assert list(absolute_paths(root)), "no absolute paths extracted -- regex broken?"
def test_absolute_paths_exist_on_the_printer(root, rootfs):
missing = ["%s: %s" % (script, p)
for script, p in absolute_paths(root)
if not os.path.exists(rootfs + p)]
assert not missing, \
"paths that do not exist on the printer:\n " + "\n ".join(missing)
@pytest.mark.parametrize("binary", [
"/sbin/ifconfig", "/sbin/udhcpc", "/usr/sbin/wpa_supplicant", "/usr/sbin/wpa_cli",
])
def test_wifi_binaries_exist(rootfs, binary):
"""Named explicitly: S50wifi's connect() never runs in any simulation."""
assert os.path.exists(rootfs + binary), "%s missing from the printer rootfs" % binary