Files
voculinux/tests/test_void_pywhispercpp_template.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

243 lines
11 KiB
Python

"""Offline policy tests for the Void template of the whisper.cpp binding (#26).
Spec #16 packages four carried templates in void-packages layout under
``packaging/void``; this ticket owns the hard one, ``python3-pywhispercpp``,
built from the pinned PyPI source distribution with the Vulkan backend,
shared libraries, and portable CPU flags. Ticket #28 builds the container
smoke gate on top; everything asserted here runs offline from sources
already in the repo.
Independent sources of truth: ``install.sh`` pins the binding version
(``PYWHISPERCPP_VERSION``), ``uv.lock`` pins the sdist URL and sha256, and
the sdist's own ``setup.py``/``PKG-INFO`` name the build and runtime
requirements. The template must agree with all three.
"""
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
VOID_DIR = REPO_ROOT / "packaging" / "void"
TEMPLATE = VOID_DIR / "srcpkgs" / "python3-pywhispercpp" / "template"
PINS = VOID_DIR / "tool_checksums.txt"
BUILD_SH = VOID_DIR / "build-release.sh"
INSTALL_SH = REPO_ROOT / "install.sh"
UV_LOCK = REPO_ROOT / "uv.lock"
#: ggml SIMD options pinned off for the plain x86-64 baseline (#957).
#: GGML_NATIVE=OFF alone is not portable: while ggml's native default is on,
#: every GGML_* SIMD option stays on and still compiles -mavx2 -mfma.
#: Mirrors PORTABLE_GGML_OPTIONS in tests/test_flatpak_packaging.py and the
#: CMAKE_ARGS line in packaging/flatpak/python3-dependencies.yaml.
PORTABLE_GGML_OPTIONS = (
"GGML_NATIVE",
"GGML_SSE42",
"GGML_AVX",
"GGML_AVX2",
"GGML_BMI2",
"GGML_FMA",
"GGML_F16C",
)
#: install_requires of pywhispercpp's setup.py, mapped to the Void binary
#: package names. Each name was verified to exist as srcpkgs/<name>/template
#: in current void-packages (2026-11-05); the container build re-verifies via
#: xbps-query, so a removal upstream fails there rather than silently here.
VOID_RUNTIME_DEPS = (
"python3-numpy",
"python3-requests",
"python3-tqdm",
"python3-platformdirs",
)
def _template() -> str:
assert TEMPLATE.is_file(), f"missing Void template: {TEMPLATE}"
return TEMPLATE.read_text(encoding="utf-8")
def _pinned_version() -> str:
text = INSTALL_SH.read_text(encoding="utf-8")
match = re.search(r'^PYWHISPERCPP_VERSION="([^"]+)"', text, re.M)
assert match, "install.sh no longer pins PYWHISPERCPP_VERSION"
return match.group(1)
def _sdist_url_and_hash() -> tuple[str, str]:
text = UV_LOCK.read_text(encoding="utf-8")
match = re.search(
r'sdist = \{ url = "(https://files\.pythonhosted\.org/[^"]*?'
r"/pywhispercpp-([0-9.]+)\.tar\.gz)\","
r' hash = "sha256:([0-9a-f]{64})"',
text,
)
assert match, "uv.lock no longer pins the pywhispercpp sdist URL and hash"
return match.group(1), match.group(3)
def test_template_names_the_package_after_its_directory() -> None:
"""pkgname must match srcpkgs/<pkgname>, per the Void manual."""
text = _template()
match = re.search(r"^pkgname=(\S+)", text, re.M)
assert match, "template sets no pkgname"
assert match.group(1) == TEMPLATE.parent.name == "python3-pywhispercpp"
def test_template_carries_the_mandatory_void_variables() -> None:
"""Mandatory vars: version, revision, short_desc, maintainer, license."""
text = _template()
assert re.search(r"^version=[0-9][0-9.]*$", text, re.M), "unpinned or missing version"
assert re.search(r"^revision=1$", text, re.M), "new templates start at revision 1"
match = re.search(r'^short_desc="([^"]+)"', text, re.M)
assert match, "missing short_desc"
assert len(match.group(1)) <= 72, f"short_desc exceeds 72 chars: {match.group(1)!r}"
assert re.search(
r'^maintainer="?[^"]*<\S+@\S+>"?$', text, re.M
), "maintainer is not 'name <email>'"
assert re.search(r'^license="?MIT"?$', text, re.M), "pywhispercpp is MIT licensed"
assert re.search(r'^homepage="?https://github\.com/absadiki/pywhispercpp"?$', text, re.M)
def test_template_version_matches_the_installer_and_lockfile_pins() -> None:
"""The template builds the pinned binding, not newest upstream."""
text = _template()
pinned = _pinned_version()
match = re.search(r"^version=(\S+)", text, re.M)
assert match and match.group(1) == pinned, (
f"template builds {match.group(1) if match else 'nothing'}, " f"install.sh pins {pinned}"
)
def test_distfiles_and_checksum_match_the_locked_sdist() -> None:
"""One PyPI source distribution, checksum-verified against uv.lock."""
text = _template()
url, sha = _sdist_url_and_hash()
assert (
"${PYPI_SITE}/p/pywhispercpp/pywhispercpp-${version}.tar.gz" in text
), "distfiles must use the ${PYPI_SITE} mirror helper with ${version}"
match = re.search(r"^checksum=([0-9a-f]{64})$", text, re.M)
assert match, "checksum must be a bare sha256 hex digest"
assert match.group(1) == sha, "template checksum is behind uv.lock's sdist hash"
assert url.endswith(f"pywhispercpp-{_pinned_version()}.tar.gz")
def test_build_style_is_python3_module() -> None:
"""setup.py exists, so the module style applies (per the Void manual)."""
assert re.search(r"^build_style=python3-module$", _template(), re.M)
def test_portable_cpu_flags_pin_every_ggml_simd_option_off() -> None:
"""No host-native tuning: the plain x86-64 baseline, like the Flatpak."""
text = _template()
for flag in PORTABLE_GGML_OPTIONS:
assert f"-D{flag}=OFF" in text, (
f"template does not pin {flag} off; the package inherits the "
"builder's instruction set and SIGILLs on older CPUs (#938, #957)"
)
def _code_lines() -> str:
"""Template with comment lines stripped; flags live in code, not prose."""
return "\n".join(line for line in _template().splitlines() if not line.lstrip().startswith("#"))
def test_no_native_tuning_or_cuda_anywhere() -> None:
"""Void has no CUDA toolkit package; portable means portable."""
text = _code_lines()
assert "-march=native" not in text and "-mcpu=native" not in text
assert "GGML_NATIVE=ON" not in text
assert "cuda" not in text.lower(), "no CUDA build: Void ships no CUDA toolkit"
def test_vulkan_is_a_default_on_build_option() -> None:
"""Source builders rebuild CPU-only with -o ~vulkan."""
text = _template()
match = re.search(r"^build_options=(.*)$", text, re.M)
assert match and "vulkan" in match.group(1).replace('"', "").split()
match = re.search(r"^build_options_default=(.*)$", text, re.M)
assert match and "vulkan" in match.group(1).replace('"', "").split(), "Vulkan is on by default"
# The common options description already defines desc_option_vulkan;
# a local redefinition would drift from it.
assert "desc_option_vulkan" not in text
assert "GGML_VULKAN=1" in text, "the Vulkan backend is enabled via GGML_VULKAN=1"
assert "build_option_vulkan" in text, "GGML_VULKAN must be gated on the option"
def test_vulkan_deps_are_conditional_and_runtime_names_exist() -> None:
"""Vulkan-only deps vanish from CPU-only builds; runtime names are real."""
text = _template()
assert "vopt_if vulkan" in text, "conditional Vulkan deps use vopt_if"
assert "vulkan-loader" in text, "libvulkan comes from vulkan-loader"
assert "Vulkan-Headers" in text or "vulkan-loader-devel" in text
assert "shaderc" in text, "ggml-vulkan requires glslc at build time"
depends = re.search(r"^depends=(.*)$", text, re.M)
assert depends, "template declares no runtime depends"
for dep in VOID_RUNTIME_DEPS:
assert dep in depends.group(1), f"{dep} missing from depends"
def test_build_time_deps_cover_setup_py() -> None:
"""setup.py imports bdist_wheel at top level and shells out to cmake."""
text = _template()
host = re.search(r"^hostmakedepends=(.*)$", text, re.M)
assert host, "template declares no hostmakedepends"
for dep in ("cmake", "pkg-config", "python3-setuptools", "python3-wheel"):
assert dep in host.group(1), f"{dep} missing from hostmakedepends"
make = re.search(r"^makedepends=(.*)$", text, re.M)
assert make and "python3-devel" in make.group(1), "compiled extension needs python3-devel"
def test_version_is_injected_for_the_offline_build() -> None:
"""The sdist carries no version.txt; setup.py reads PYWHISPERCPP_VERSION."""
assert "PYWHISPERCPP_VERSION" in _template()
def test_build_fetches_nothing_beyond_the_sdist() -> None:
"""Only declared distfiles may come from the network."""
text = _template()
assert text.count("https://") == 1, "homepage is the only URL a template may name"
assert "FetchContent" not in text and "ExternalProject" not in text
assert "make_check=no" in text, "the sdist ships no tests to check"
def test_template_style_is_lint_clean() -> None:
"""Trailing whitespace and missing final newline fail xlint."""
raw = TEMPLATE.read_bytes().decode("utf-8")
for lineno, line in enumerate(raw.splitlines(), 1):
assert line == line.rstrip(), f"trailing whitespace on line {lineno}"
assert "\t" not in line or line.startswith("\t"), f"stray tab on line {lineno}"
assert raw.endswith("\n"), "template must end with a newline"
def test_tool_checksums_pin_the_void_glibc_image() -> None:
"""The container gate builds in one pinned image, like native/appimage."""
assert PINS.is_file(), f"missing pins file: {PINS}"
pins = {}
for line in PINS.read_text(encoding="utf-8").splitlines():
if not line.strip() or line.lstrip().startswith("#"):
continue
name, kind, value, _source = line.split(None, 3)
pins[name] = (kind, value)
assert "base-image" in pins, "no pinned base image"
kind, value = pins["base-image"]
assert kind == "docker", "base-image pin kind matches native/appimage convention"
assert value.startswith(
"ghcr.io/void-linux/void-glibc-full@sha256:"
), f"base image must be the pinned Void glibc image, got {value}"
assert len(value.rsplit(":", 1)[1]) == 64, "image digest must be a full sha256"
def test_build_script_uses_podman_and_the_pinned_image() -> None:
"""Container work uses podman, never Docker; the image comes from the pins."""
assert BUILD_SH.is_file(), f"missing build script: {BUILD_SH}"
text = BUILD_SH.read_text(encoding="utf-8")
assert "podman" in text, "the build harness must use podman"
scrubbed = text.lower().replace("never docker", "")
assert "docker" not in scrubbed, "never Docker"
assert "tool_checksums.txt" in text, "the image digest comes from the pins file"
# The container flow lives in a mounted file, not an inline sh -c string:
# single quotes inside sh -c silently regroup the command.
name = "container-release.sh"
assert (VOID_DIR / name).is_file(), f"missing container script: {name}"
assert name in text, f"{BUILD_SH.name} must mount {name}"