"""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"