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

82 lines
3.2 KiB
Python

"""Fixtures for the replica lane.
WHAT THE `printer` FIXTURE IS
A replica of the machine WITH OUR PACKAGE ALREADY INSTALLED -- installed by the
printer's own /usr/prog/app_startup.sh, from a genuine FAT filesystem on
/dev/sda1, exactly as a user installs it off a USB stick.
That is deliberate and it is the second time this repo has had to learn it.
An earlier version of this file handed the tests a machine into which
qa/replica/actions/install-payload.sh had copied payload/*.sh and
payload/init.d/S* by hand. Two things were wrong with that:
* it was a SECOND implementation of the install, so every assertion
downstream described a layout the harness had built rather than one the
installer produced -- the real installer could have broken and nothing
here would have gone red;
* it could only place what is in payload/, so anything the BUILD produces
was missing. The cross-compiled s6 is the obvious one: it lives in
work/.s6 and the payload carries it, so a hand-placed
payload has no supervisor at all and the tests had to carry a stand-in
scanner to paper over it.
Installing for real fixes both at once. The install is now under test because
it is the setup, and the payload under test is the built artefact -- real s6,
real CPython, real Klipper extras, staged exactly as they ship.
case-install.sh's header records the same lesson from the first time:
An earlier version of this file replayed app_startup.sh by hand -- which
meant a bug in our reading of it could never be caught.
COST
The install is the machine's own shell running under qemu and it takes
minutes, so it is baked into an image ONCE per package and cached under a tag
derived from the package's md5 -- the same trick
tools/replica/build-printer-image.sh uses for the stock baseline. Rebuild
the package and the tag changes, so the next run bakes again instead of
testing yesterday's build.
Module-scoped containers start from that image, which is the same isolation
boundary the case scripts have: each starts from a freshly assembled machine
and none shares state with another.
Nothing here skips. A machine with no docker, no daemon or no built package
cannot answer the question these tests exist to ask, and ReplicaMissing says
so -- with the fix -- rather than reporting a clean skip. See qa/conftest.py.
"""
import pytest
from lib import replica
from lib.config import Config, ConfigError
@pytest.fixture(scope="session")
def config():
try:
return Config.load()
except ConfigError as broken:
# Not a skip. A broken config.env is a broken harness, and the reason
# Config raises here is that the shell version could not tell the two
# apart -- a half-sourced file left STOCK_TGZ_* empty, and the run
# reported a clean skip on a machine that had everything.
pytest.fail(str(broken))
@pytest.fixture(scope="session")
def mod_image(config):
"""An image that IS a printer with our package installed. Baked once."""
return replica.installed_image(config, on_output=print)
@pytest.fixture(scope="module")
def printer(config, mod_image):
"""A live installed replica, held open for this module."""
live = replica.start(config, image=mod_image)
try:
yield live
finally:
replica.stop(live)