Files
voculinux/tests/test_void_pywhispercpp_template.py
T
xavierk 97f93e9492
CI / security (pull_request) Successful in 1m11s
CI / lint (pull_request) Successful in 25s
CI / ai-review (pull_request) Successful in 8s
fix(void): report pywhispercpp 1.5.0 and load CPU variants at runtime
python3-pywhispercpp was installed as 0.0.0. setup.py passes no version to
setup() and takes it from setuptools-scm, which the Void build did not
install; the template's PYWHISPERCPP_VERSION export is never read. The app
read 0.0.0 as an old binding and skipped GPU device selection. Install
setuptools-scm, pin the version with SETUPTOOLS_SCM_PRETEND_VERSION, and
make the app treat a 0.0.0 distribution as unknown so the runtime feature
probe decides.

The CPU backend was built with every SIMD option off, so the real-time
factor on an AVX2 host was about 1.7. Build with GGML_BACKEND_DL and
GGML_CPU_ALL_VARIANTS instead: one CPU library per instruction-set level,
with the x86-64 baseline always shipped. The binding loads the variants
from its own directory (patches/), and pre_install places them beside the
extension. The app's library preloader skips those variants: they export
identical symbols, so preloading them RTLD_GLOBAL would let one variant
serve every other. The install smoke asserts that the installed package
reports 1.5.0, that the baseline variant holds no AVX code, that the AVX2
variant does, and that the CPU and Vulkan backends load from the package.

Fixes #43
Fixes #44
2026-11-07 00:03:22 +05:30

302 lines
14 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, runtime-selected CPU variants (#44), and a version pinned
through setuptools-scm (#43). 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"
#: CMake arguments of the CPU backend (#44): one library per CPU level, loaded
#: at run time, with the x86-64 baseline always shipped. GGML_NATIVE must stay
#: OFF: ggml refuses it with GGML_BACKEND_DL, and it would compile the builder's
#: instruction set into every variant (#938).
REQUIRED_CMAKE_ARGS = (
"-DGGML_NATIVE=OFF",
"-DGGML_BACKEND_DL=ON",
"-DGGML_CPU_ALL_VARIANTS=ON",
"-DBUILD_SHARED_LIBS=ON",
)
#: The binding patch that loads the backend modules from the extension's directory.
BINDING_PATCH = (
VOID_DIR
/ "srcpkgs"
/ "python3-pywhispercpp"
/ "patches"
/ "load-ggml-backends-beside-module.patch"
)
INSTALL_SMOKE = VOID_DIR / "container-release.sh"
#: 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 _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_cpu_backend_is_built_as_runtime_selected_variants() -> None:
"""#44: every CPU level is its own library, and the x86-64 baseline ships."""
text = _code_lines()
for arg in REQUIRED_CMAKE_ARGS:
assert arg in text, f"template does not pass {arg}"
def test_no_simd_option_is_forced_on_for_the_whole_build() -> None:
"""A global SIMD switch would give every variant one instruction set (#938, #957)."""
text = _code_lines()
for feature in ("NATIVE", "SSE42", "AVX", "AVX2", "BMI2", "FMA", "F16C", "AVX512"):
assert f"-DGGML_{feature}=ON" not in text, f"GGML_{feature} forced on for every variant"
def test_backends_are_copied_beside_the_extension_before_install() -> None:
"""#44: whisper.cpp writes the modules to build/bin, which setup.py does not install."""
text = _template()
assert "pre_install()" in text
assert "build/temp.*/_pywhispercpp/bin/libggml-*.so" in text
assert "build/lib.*/" in text
assert "msg_error" in text, "the build must fail loudly when no backend module was built"
def test_binding_loads_the_backends_beside_the_extension() -> None:
"""#44: ggml's default search misses the package directory, so the binding loads it."""
assert BINDING_PATCH.is_file(), f"missing binding patch: {BINDING_PATCH}"
text = BINDING_PATCH.read_text(encoding="utf-8")
assert "--- a/src/main.cpp" in text and "+++ b/src/main.cpp" in text
assert " PYBIND11_MODULE(_pywhispercpp, m) {" in text
assert "+ load_ggml_backends_beside_module();" in text
assert "+ ggml_backend_load_all_from_path(dir.c_str());" in text
def test_install_smoke_checks_version_and_cpu_variants() -> None:
"""#43 and #44: the installed binding reports the pinned version, and its variants load."""
text = INSTALL_SMOKE.read_text(encoding="utf-8")
version = re.search(r"^version=(\S+)", _template(), re.M)
assert version, "template declares no version"
assert (
f'[ "$PWC_VERSION" = "{version.group(1)}" ]' in text
), "the install smoke must assert the installed pywhispercpp reports the template version"
assert "libggml-cpu-x64.so" in text and "libggml-cpu-haswell.so" in text
assert "objdump -d" in text, "the baseline variant must be checked for AVX code"
assert "libggml.so.0" in text, "the backend registry lives in libggml"
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-setuptools_scm",
"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_pinned_through_setuptools_scm() -> None:
"""#43: setup() gets no version; setuptools-scm supplies it, so the build must provide it."""
host = re.search(r"^hostmakedepends=(.*)$", _template(), re.M)
assert host and "python3-setuptools_scm" in host.group(
1
), "without setuptools-scm the build reports version 0.0.0"
assert (
"SETUPTOOLS_SCM_PRETEND_VERSION=${version}" in _code_lines()
), "the xbps build root has no git metadata; pin the template version"
installer = (REPO_ROOT / "requirements" / "installer-build.txt").read_text(encoding="utf-8")
assert re.search(
r"^setuptools-scm==", installer, re.M
), "the installer build tools pin setuptools-scm as well"
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}"