Files
voculinux/tests/test_void_smoke_gate.py
T
xavierk 37cf549956
CI / security (push) Successful in 1m15s
CI / lint (push) Successful in 24s
CI / ai-review (push) Skipped
fix(void): carry python3-pysocks and constrain the audio and SOCKS floors (#39)
voculinux now depends on python3-PyAudio and python3-pysocks at the carried versions, so a stock Void package below the project floors is upgraded rather than silently kept. The container smoke installs the stock packages first and asserts the upgrade.
2026-11-06 16:17:19 +00:00

181 lines
8.2 KiB
Python

"""Offline policy guards for the Void package smoke gate (ticket #28).
Spec #16 gives the packaged artefact the same treatment as the other
distribution formats: one documented command runs lint, build, install
and version/import smoke for every Void template from a clean checkout,
using podman and the pinned container, with no host Void install.
Seams under test (public interfaces, not internals):
- ``packaging/void/smoke.sh`` — the gate command. It runs the same release
path as a tag (``build-release.sh smoke``), so a passing gate is a release
that would build.
- The ``void-smoke`` just recipe — how release work and CI call the
gate, same shape as the other packaging gates.
- ``packaging/void/README.md`` — the documented one-command entry.
Independent sources of truth: ``packaging/void/srcpkgs/*/template``
owns the template set (a renamed or added template must not silently
drop out), ``packaging/void/tool_checksums.txt`` owns the pins, and
``packaging/void/container-release.sh`` owns the install smoke each
package must pass. The gate must agree with all of them; the container
run itself is the proof, these tests are the wiring guard.
"""
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
VOID = REPO_ROOT / "packaging" / "void"
SRC = VOID / "srcpkgs"
SMOKE_SH = VOID / "smoke.sh"
RELEASE_PATH = VOID / "build-release.sh"
CONTAINER_RELEASE = VOID / "container-release.sh"
PINS = VOID / "tool_checksums.txt"
JUSTFILE = REPO_ROOT / "justfile"
README = VOID / "README.md"
def _srcpkg_templates() -> list:
"""Every template directory under srcpkgs, in dependency build order."""
return sorted(p.name for p in SRC.iterdir() if (p / "template").is_file())
def _smoke_text() -> str:
assert SMOKE_SH.is_file(), f"missing Void smoke gate: {SMOKE_SH}"
return SMOKE_SH.read_text(encoding="utf-8")
def _gate_packages() -> list:
"""The ordered package list the gate builds, parsed from smoke.sh."""
match = re.search(r'^PKGS="([^"]+)"', _smoke_text(), re.M)
assert match, "smoke.sh must declare its ordered PKGS list"
return match.group(1).split()
def test_gate_builds_every_template_and_nothing_else() -> None:
"""A renamed or added template cannot silently drop out of the smoke."""
assert _gate_packages() and set(_gate_packages()) == set(
_srcpkg_templates()
), f"gate covers {_gate_packages()}, srcpkgs holds {_srcpkg_templates()}"
def test_gate_builds_the_app_last() -> None:
"""voculinux resolves the other templates from the local repository."""
assert (
_gate_packages()[-1] == "voculinux"
), f"the app must build last, gate order is {_gate_packages()}"
def test_gate_delegates_to_the_release_path() -> None:
"""One release path; the gate only selects the smoke mode, and its exit status is the path's."""
text = _smoke_text()
assert 'exec "$HERE/build-release.sh" smoke' in text, "smoke.sh must exec the release path"
assert "build-release.sh" in RELEASE_PATH.read_text(encoding="utf-8")
def test_gate_stays_podman_only_with_pinned_inputs() -> None:
"""Podman, never Docker; pins live in tool_checksums.txt, not the gate."""
text = _smoke_text()
assert "podman" in text, "the smoke gate must use podman"
assert "docker" not in text.lower().replace("never docker", "")
assert "ghcr.io" not in text, "the base image pin lives in tool_checksums.txt"
assert PINS.is_file(), f"missing pins file: {PINS}"
def _case_branch(label: str) -> str:
"""Shell lines of one case branch in the install phase of container-release.sh."""
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
start = text.index(f"\n\t\t{label})")
end = text.index(";;", start)
return text[start:end]
def test_the_ggml_scan_belongs_to_the_whisper_binding_only() -> None:
"""Only the whisper binding ships libggml; the payload check is its own."""
assert "payload_pywhispercpp" in _case_branch("python3-pywhispercpp")
assert "payload_voculinux" not in _case_branch("python3-pywhispercpp")
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
assert "libggml-cpu" in text and "libggml-vulkan" in text
def test_the_leaf_packages_are_imported_headless() -> None:
"""PyAudio, pynput and pysocks are proven by import: pynput's dummy backend needs no display."""
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
assert "import pyaudio" in text
assert "PYNPUT_BACKEND=dummy" in text and "import pynput.keyboard" in text
assert "import socks" in text, "pysocks is proven by import"
def test_every_gate_package_has_an_install_recipe() -> None:
"""Each package the gate builds has a label of its own in the per-package case."""
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
start = text.index('case "$PKG" in')
block = text[start : text.index("esac", start)]
labels = set()
for match in re.finditer(r"^\s*([^)\n]+)\)", block, re.M):
labels.update(part.strip() for part in match.group(1).split("|"))
for name in _gate_packages():
assert name in labels, f"container-release.sh has no install recipe for {name}"
def test_install_smoke_upgrades_the_stock_packages_below_the_floors() -> None:
"""A user who already has Void's stock audio binding and SOCKS module must get
the carried ones: the smoke installs the stock packages first, then voculinux,
then checks each one reads back as the carried pkgver."""
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
stock = 'xbps-install -y --repository="$OFFICIAL" -S python3-PyAudio python3-pysocks'
assert stock in text, "the stock packages must be installed before voculinux"
assert text.index(stock) < text.index(
'xbps-install -y --repository="$XBPS_STAGE/repo" --repository="$OFFICIAL" -S voculinux'
), "the stock install must precede the voculinux install"
assert (
'xbps-query "$PKG" | grep -qx "pkgver: $PKGVER"' in text
), "each stock package must read back as the carried pkgver"
def test_stock_preinstall_matches_the_constrained_depends() -> None:
"""A carried dependency that voculinux version-constrains must be installed as
stock first and checked by pkgver. Three places name the set: the voculinux
depends, the stock pre-install line and the pkgver loop. They must agree, so a
third gap package cannot be missed in one of them."""
template = (SRC / "voculinux" / "template").read_text(encoding="utf-8")
depends = re.search(r'^depends="([^"]+)"', template, re.M).group(1).split()
constrained = {token.split(">=")[0] for token in depends if ">=" in token}
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
stock = re.search(r'--repository="\$OFFICIAL" -S ([^>\n]+?) >/dev/null', text).group(1).split()
checked = re.search(r"for PKG in (python3-[^;]+); do", text).group(1).split()
assert set(stock) == constrained == set(checked), (
f"stock pre-install {stock}, constrained depends {sorted(constrained)}, "
f"pkgver loop {checked} must name the same packages"
)
def test_a_package_without_a_smoke_recipe_fails_the_release() -> None:
"""A future template falls into *) and must fail loudly, never pass mute."""
text = CONTAINER_RELEASE.read_text(encoding="utf-8")
assert "no smoke recipe for package" in _case_branch("*") or (
"no smoke recipe for package" in text
)
def test_just_recipe_calls_the_gate_like_other_packaging_gates() -> None:
"""`just void-smoke` runs the gate script with no shared-output step."""
recipe = re.search(
r"^void-smoke:([^\n]*)\n((?:[ \t].*\n|\n)+)",
JUSTFILE.read_text(encoding="utf-8"),
re.MULTILINE,
)
assert recipe, "justfile must define a void-smoke recipe"
assert not recipe.group(1).strip(), "void-smoke must not take a shared-output prerequisite"
assert "packaging/void/smoke.sh" in recipe.group(
2
), "void-smoke must run packaging/void/smoke.sh"
def test_readme_documents_the_one_command_gate() -> None:
"""AC1: one documented command from a clean checkout, no host Void."""
text = README.read_text(encoding="utf-8")
assert "void-smoke" in text or "smoke.sh" in text, "README must document the smoke gate command"
assert "podman" in text.lower(), "README must state the gate uses podman"