Files
Oleksandr ShyshatskyiandClaude Opus 5 c185ef3285 The replica is a build tool; test/ is 28 unit tests
Two changes that turned out to be one.

MOVED, NOT DELETED. `test/ffsim/` and `test/integration/printer/` are
`tools/replica/`. They were never tests: bin/patch.sh calls
bin/build-payload.py, which starts a replica and assembles the payload by
running the PRINTER'S OWN opkg against / inside it -- so the "test harness"
was on the build path, and deleting it as asked would have broken `make
build`. qa/lib/replica.py drives the same directory unmodified. No content
changed; what changed is qa/lib/replica.py's build directory, three launcher
sys.path lines, the Makefile targets and the CI shellcheck glob.
tools/replica/README.md says who drives it and why it is not under test/.

CUT TO 28. The five surviving modules were 136 tests; they are 28, chosen on
one question: would this failure be SILENT? What is kept is the mistakes that
run perfectly and are wrong --

  test_startup       8  no stamp unless the values are verifiably saved (a
                        stamp written early is a printer that has lost its
                        factory calibration for good), and the handover order
  test_tool_transform 7 every one is a move to the wrong place: a sign, a
                        frame left applied while probing, a stale gcode_move
                        cache, a config a calibrated printer cannot parse
  test_ffscreen      5  the four ways it can do harm rather than look wrong
  test_chamber       4  the chamber mistakes that cost a print before anyone
                        sees them, plus the parse gate they rest on
  test_gcode         4  a renamed macro, and the safe file being cold

What went: the panel narration, the geometry probe and parse_geometry, the
z_adjust and per-job-Z bookkeeping, the config layout checks, the G-code
bounds checks. Each is either a wrong number shown to a person, or a failure
the printer reports loudly the first time -- and qa/replica/test_boot_screen.py
already renders the real frames on the real machine.

The rewrite is not a delete: assertions were folded so a kept test asks its
question in every direction it can be got wrong (both models, both rotations,
all four ways the migration can fail), and each file's header now says what it
dropped and why, where the next person will open it.

Also gone: conftest's `rootfs` fixture, orphaned two commits ago. Nothing under
test/ needs the firmware, docker, or a network any more.

28 host-side tests pass in 0.15s. qa/static unchanged at 218. `make rootfs`
re-verified end to end from its new path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UzLebCrvc8ssWR5JuEsha4
2026-08-29 23:27:34 +03:00

70 lines
2.9 KiB
Python

"""Host side of the test harness.
Everything in here runs on YOUR machine. The scripts that run on the printer
-- tools/replica/printer/*.sh and the whole payload -- stay shell on
purpose: they are executed by the printer's own busybox ash under qemu, and
the fact that they survive that is a large part of what the suite proves.
Rewriting those in Python would test a Python the printer does not have.
Two things here exist to fix specific accidents.
`repo_root` finds the checkout by looking for a file that marks it, walking
upward until it does. The shell version computed it by counting `..` from
$0, so moving a launcher one directory deeper silently made $ROOT point at
test/ instead of the repo -- which happened, to five scripts at once, and
shipped. A search does not care where the file lives.
`Skip` is an exception, and that is the whole point of it. In the shell suite
"this gate did not run" was reported by printing the string "SKIP:" and
exiting 0, and the runner decided by grepping stdout for it. So a launcher
that had ALREADY failed -- one whose config never loaded because the path to
it was wrong -- concluded it had nothing to work on, printed those five
characters, exited 0, and was counted as a clean skip on a machine that could
have run it perfectly well. Text on stdout cannot tell you what happened
inside a process. An exception can, and nothing else can forge it.
"""
from pathlib import Path
__all__ = ["Skip", "Fail", "repo_root"]
class Skip(Exception):
"""This gate cannot run here, and that is a legitimate answer.
No docker, no proprietary firmware. Raise it only where the precondition
is genuinely absent -- never as a way to swallow a failure, which is the
exact confusion this class exists to end.
"""
class Fail(Exception):
"""This gate ran and the answer was no.
Also raised for a harness that is itself broken: a missing file, a bad
path, a docker that will not start. That merging is deliberate. The
alternative is a third state, and a third state is somewhere for "the
test did not really run" to hide again.
"""
# A file that exists in this repo and nowhere above it. bin/common.sh is
# sourced by every build script, so if it ever moves the build is broken
# anyway and a loud failure here is the least of it.
_MARKER = ("bin", "common.sh")
def repo_root(start=None):
"""The checkout root, found by looking rather than by counting.
Depth-independent by construction: this file can move to any directory in
the repo and it still resolves correctly, which is what makes the old
`cd "$(dirname "$0")/.."` class of bug impossible rather than merely
tested for.
"""
here = Path(start or __file__).resolve()
for d in (here,) + tuple(here.parents):
if (d.joinpath(*_MARKER)).is_file():
return d
raise Fail("not inside the repo: no %s above %s"
% ("/".join(_MARKER), here))