Files
rdm-card-reader/tests/test_wheels.py
T
ske087 031cf0f25a Add a status LED driven by card events
The workstation app lit an LED on GPIO 23 with the card. This brings that
across, with three deliberate differences: it never blocks the reader, it
cannot be fatal, and its state is reported.

WMT toggled the LED directly inside card_inserted, on the reader thread, where
a slow write stalls card detection. Here every change is queued to a worker, so
insert() returns immediately (tested with a deliberately slow pin).

Two modes, because the light can mean two things:
  error    (default) lit while the reader runs, dark while reading, blinks on a
                     bad read - a health indicator
  presence           lit only while a card is on the reader

gpiozero raises BadPinFactory on a machine with no GPIO, and that subclasses
ImportError - so a single except ImportError reports 'gpiozero is not
installed' when the library is present but has no usable pin provider. The
library check and the pin construction are therefore separate.

Vendors gpiozero + colorzero (~180 KB) so the LED installs offline too, and
deliberately not setuptools: gpiozero declares it via colorzero, but it is only
a packaging requirement and is never imported at runtime. Installing to the
bundled wheels therefore uses --no-deps, saving 818 KB.

Verified end to end over a pseudo-terminal with a fake pin: a real RDM6300
frame drives the LED through the app's own CLI in both modes.
2026-10-01 16:43:29 +03:00

230 lines
7.6 KiB
Python

"""Tests for the vendored wheels and their verification.
The bundled wheels let a device with no internet set itself up, so the things
that matter are: they are actually present and pure-Python (portable), the
checksums match, and a tampered wheel is refused rather than installed.
"""
import os
import shutil
import sys
SRC = os.path.join(
os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "src"
)
if SRC not in sys.path:
sys.path.insert(0, SRC)
import pytest # noqa: E402
from rdm_card_reader import bootstrap # noqa: E402
from rdm_card_reader.bootstrap import ( # noqa: E402
bundled_wheel_dir,
ensure_dependencies,
local_wheel_missing,
verify_wheels,
)
# -- the wheels that ship with the package ---------------------------------
def test_bundled_wheel_dir_exists():
folder = bundled_wheel_dir()
assert folder is not None
assert os.path.isdir(folder)
def test_both_required_wheels_are_present():
folder = bundled_wheel_dir()
names = os.listdir(folder)
assert any(n.startswith("rdm6300-") and n.endswith(".whl") for n in names)
assert any(n.startswith("pyserial-") and n.endswith(".whl") for n in names)
def test_bundled_wheels_are_pure_python():
"""A compiled wheel would only work on one architecture; these must not."""
folder = bundled_wheel_dir()
wheels = [n for n in os.listdir(folder) if n.endswith(".whl")]
assert wheels
for name in wheels:
assert name.endswith("-none-any.whl"), (
"{} is architecture-specific and cannot be shipped".format(name)
)
def test_bundled_wheels_verify_against_their_checksums():
folder = bundled_wheel_dir()
assert verify_wheels(folder) == []
def test_checksums_file_is_shipped():
folder = bundled_wheel_dir()
assert os.path.exists(os.path.join(folder, "SHA256SUMS"))
def test_total_vendored_size_is_reasonable():
"""Guard against someone dropping a huge binary into the repo."""
folder = bundled_wheel_dir()
total = sum(
os.path.getsize(os.path.join(folder, name))
for name in os.listdir(folder)
)
assert total < 2 * 1024 * 1024
# -- verification -----------------------------------------------------------
def test_tampered_wheel_is_detected(tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
wheel = folder / "rdm6300-0.1.1-py3-none-any.whl"
wheel.write_bytes(b"not the real wheel")
(folder / "SHA256SUMS").write_text(
"afb067da8b8d23a477138ca67d4ff1d198f12d157b64668c9dd3cbd7df54e74e"
" rdm6300-0.1.1-py3-none-any.whl\n"
)
problems = verify_wheels(str(folder))
assert problems
assert any("checksum mismatch" in p for p in problems)
def test_missing_wheel_is_reported(tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "SHA256SUMS").write_text(
"abc123 rdm6300-0.1.1-py3-none-any.whl\n"
)
problems = verify_wheels(str(folder))
assert any("missing" in p for p in problems)
def test_no_sums_file_means_nothing_to_check(tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "x-1.0-py3-none-any.whl").write_bytes(b"x")
assert verify_wheels(str(folder)) == []
def test_verification_passes_for_untouched_copy(tmp_path):
source = bundled_wheel_dir()
dest = tmp_path / "wheels"
shutil.copytree(source, dest)
assert verify_wheels(str(dest)) == []
# -- coverage check ---------------------------------------------------------
def test_local_wheel_missing_detects_absent_requirement(tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
assert local_wheel_missing(["rdm6300>=0.1.1"], str(folder)) == ["rdm6300>=0.1.1"]
def test_local_wheel_missing_accepts_present_requirement():
folder = bundled_wheel_dir()
assert local_wheel_missing(["rdm6300>=0.1.1", "pyserial>=3.5"], folder) == []
def test_local_wheel_missing_normalises_underscores(tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "charset_normalizer-3.4.1-py3-none-any.whl").write_bytes(b"x")
assert local_wheel_missing(["charset-normalizer>=3"], str(folder)) == []
# -- integration into ensure_dependencies -----------------------------------
def test_bundled_wheels_used_automatically(monkeypatch):
"""No --wheel-dir given, so the bundled ones must be picked up."""
seen = {}
monkeypatch.setattr(
bootstrap, "missing_packages",
lambda packages=None: [] if seen.get("done") else [("rdm6300", "rdm6300>=0.1.1")],
)
def fake_install(requirements, wheel_dir=None, logger=None, timeout=300, no_deps=False):
seen["wheel_dir"] = wheel_dir
seen["done"] = True
return True
monkeypatch.setattr(bootstrap, "install", fake_install)
lines = []
assert ensure_dependencies(logger=lines.append) is True
assert seen["wheel_dir"] == bundled_wheel_dir()
assert any("bundled" in line for line in lines)
def test_tampered_bundle_falls_back_to_pypi(monkeypatch, tmp_path):
"""A bad checksum must not block installation - but must be reported."""
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "rdm6300-0.1.1-py3-none-any.whl").write_bytes(b"tampered")
(folder / "SHA256SUMS").write_text("deadbeef rdm6300-0.1.1-py3-none-any.whl\n")
seen = {}
monkeypatch.setattr(bootstrap, "bundled_wheel_dir", lambda: str(folder))
monkeypatch.setattr(
bootstrap, "missing_packages",
lambda packages=None: [] if seen.get("done") else [("rdm6300", "rdm6300>=0.1.1")],
)
def fake_install(requirements, wheel_dir=None, logger=None, timeout=300, no_deps=False):
seen["wheel_dir"] = wheel_dir
seen["done"] = True
return True
monkeypatch.setattr(bootstrap, "install", fake_install)
lines = []
assert ensure_dependencies(logger=lines.append) is True
assert seen["wheel_dir"] is None # fell back to PyPI
assert any("checksum mismatch" in line for line in lines)
def test_explicit_wheel_dir_overrides_bundled(monkeypatch, tmp_path):
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "rdm6300-0.1.1-py3-none-any.whl").write_bytes(b"x")
(folder / "pyserial-3.5-py2.py3-none-any.whl").write_bytes(b"x")
seen = {}
monkeypatch.setattr(
bootstrap, "missing_packages",
lambda packages=None: [] if seen.get("done") else [("rdm6300", "rdm6300>=0.1.1")],
)
def fake_install(requirements, wheel_dir=None, logger=None, timeout=300, no_deps=False):
seen["wheel_dir"] = wheel_dir
seen["done"] = True
return True
monkeypatch.setattr(bootstrap, "install", fake_install)
ensure_dependencies(wheel_dir=str(folder))
assert seen["wheel_dir"] == str(folder)
def test_bundle_not_covering_a_requirement_falls_back(monkeypatch, tmp_path):
"""A bundle missing one package must not cause a guaranteed pip failure."""
folder = tmp_path / "wheels"
folder.mkdir()
(folder / "rdm6300-0.1.1-py3-none-any.whl").write_bytes(b"x")
seen = {}
monkeypatch.setattr(bootstrap, "bundled_wheel_dir", lambda: str(folder))
monkeypatch.setattr(
bootstrap, "missing_packages",
lambda packages=None: [] if seen.get("done")
else [("rdm6300", "rdm6300>=0.1.1"), ("serial", "pyserial>=3.5")],
)
def fake_install(requirements, wheel_dir=None, logger=None, timeout=300, no_deps=False):
seen["wheel_dir"] = wheel_dir
seen["wheel_dir_at_call"] = wheel_dir
seen["done"] = True
return True
monkeypatch.setattr(bootstrap, "install", fake_install)
lines = []
ensure_dependencies(logger=lines.append)
assert seen["wheel_dir"] is None
assert any("do not cover" in line for line in lines)