fix(void): carry python3-pysocks and constrain the audio and SOCKS floors #39
@@ -57,6 +57,14 @@ is not `repository-key.plist`. The private key is never committed; only the
|
||||
public `.plist` is. Key generation and the check that a private key matches the
|
||||
committed public key are in `packaging/void/README.md`.
|
||||
|
||||
The carried Void templates in `packaging/void/srcpkgs/` track the project's own
|
||||
pins. `python3-PyAudio` and `python3-pysocks` replace stock Void packages below
|
||||
the floors in `pyproject.toml`, and `voculinux` depends on each with a `>=`
|
||||
constraint. When a floor or a carried version changes, change the template and
|
||||
its constraint in the same pull request: the offline tests in
|
||||
`tests/test_void_small_deps.py` and `tests/test_void_app_template.py` fail
|
||||
until they agree. The reasoning is in `packaging/void/README.md`, "Version floors".
|
||||
|
||||
## Quick Release Checklist
|
||||
|
||||
Use this checklist for every release:
|
||||
|
||||
@@ -21,6 +21,7 @@ packaging/void/
|
||||
├── python3-pywhispercpp/ ticket #26: whisper.cpp Python binding
|
||||
├── python3-pynput/ ticket #25: the input library Void does not ship
|
||||
├── python3-PyAudio/ ticket #25: the audio binding, updated past Void's copy
|
||||
├── python3-pysocks/ spec #16 gap: the SOCKS module, updated past Void's copy
|
||||
└── voculinux ticket #27: the application
|
||||
```
|
||||
|
||||
@@ -33,9 +34,48 @@ packaging/void/
|
||||
| Template | Ticket | Notes |
|
||||
|---|---|---|
|
||||
| `python3-pywhispercpp` | #26 | Vulkan backend on by default; see below |
|
||||
| `python3-pynput`, `python3-PyAudio` | #25 | Dependencies Void does not ship at the needed version |
|
||||
| `python3-pynput` | #25 | A dependency Void does not ship |
|
||||
| `python3-PyAudio`, `python3-pysocks` | spec #16 gap | Void's copies are below the project floors; see [Version floors](#version-floors) |
|
||||
| `voculinux` | #27 | The app, from the canonical release sdist; see below |
|
||||
|
||||
## Version floors
|
||||
|
||||
`pyproject.toml` declares floors that Void's own packages do not meet. On
|
||||
2026-11-06 the stock packages on void-packages master were:
|
||||
|
||||
| Package | Stock Void | Project floor | Carried template | `voculinux` depends |
|
||||
|---|---|---|---|---|
|
||||
| `python3-PyAudio` | 0.2.12_5 | `pyaudio>=0.2.13` | 0.2.14_1 | `python3-PyAudio>=0.2.14_1` |
|
||||
| `python3-pysocks` | 1.7.0_8 | `PySocks>=1.7.1,<2` | 1.7.1_1 | `python3-pysocks>=1.7.1_1` |
|
||||
|
||||
The gap is closed in the personal repository, not hidden. The carried
|
||||
templates build the versions `uv.lock` pins, and `voculinux` names each one
|
||||
with a `>=` constraint in the xbps `name>=version_revision` form (the xbps-src
|
||||
manual lists `<`, `>`, `<=` and `>=`). A bare name would be satisfied by the
|
||||
stock package a user already has, so xbps would leave it in place and the app
|
||||
would run below its floor. With the constraint, xbps takes the carried
|
||||
template from the signed repository instead. Dependency verification stays on.
|
||||
|
||||
The install smoke checks the upgrade, not just a clean install: it installs
|
||||
the stock packages from Void first, then `voculinux`, and fails unless each
|
||||
package reads back as its carried pkgver.
|
||||
`tests/test_void_app_template.py` requires every constraint to name its
|
||||
template's current version and revision, so a template bump without a
|
||||
`depends` bump fails offline. `tests/test_void_small_deps.py` requires the
|
||||
stock version below the floor and the carried version at or above it.
|
||||
|
||||
Known limit: xbps has one comparator per dependency, so PySocks' `<2` cannot
|
||||
be written. The carried 1.7.1 satisfies it. A future Void `python3-pysocks`
|
||||
2.x would also satisfy `>=1.7.1_1`, so it is not excluded by the dependency.
|
||||
|
||||
When a project floor rises, or Void's stock package moves up to the carried
|
||||
version, change the carried template and its constraint in the same pull
|
||||
request. The live signal is the install smoke: once the stock package reaches
|
||||
the carried version, the constraint is already met, the stock package is kept,
|
||||
and the pkgver check fails. That is the signal to retire the carried template
|
||||
deliberately. The offline stock-version test in `tests/test_void_small_deps.py`
|
||||
only records the versions verified on 2026-11-06; it does not query Void.
|
||||
|
||||
## python3-pywhispercpp
|
||||
|
||||
Version `1.5.0`, pinned to the same version `install.sh`
|
||||
@@ -70,9 +110,11 @@ upstream. Read [Version mapping](#version-mapping) and [Checksum](#checksum)
|
||||
before changing `version`, `_tag` or `checksum`.
|
||||
|
||||
- **Runtime deps** are Void's own interpreter, GTK and desktop bindings,
|
||||
the packaged Python libraries, the three templates from tickets #25
|
||||
and #26 (`python3-pynput`, `python3-PyAudio`, `python3-pywhispercpp`),
|
||||
and the helper tools the app shells out to: PipeWire (capture), `wtype`
|
||||
the packaged Python libraries, the carried templates (`python3-pynput`,
|
||||
`python3-PyAudio`, `python3-pysocks` and `python3-pywhispercpp`; the audio
|
||||
binding and SOCKS module are version-constrained, see
|
||||
[Version floors](#version-floors)), and the helper tools the app shells
|
||||
out to: PipeWire (capture), `wtype`
|
||||
(direct typing on niri), `wl-clipboard`, `xdotool` and
|
||||
`pulseaudio-utils`. `ibus` is not a dependency: it cannot inject on
|
||||
niri. Every name was verified to exist in current void-packages; the
|
||||
@@ -156,11 +198,14 @@ host Void install and nothing is published. Stages, in order, each fails the run
|
||||
with `xlint`, built with `xbps-src`, the sdist resolved by `xbps-src show`;
|
||||
4. `sign`: the packages indexed and signed, with no network;
|
||||
5. `install`: in a clean image with no key, an unsigned install is refused, the
|
||||
signed repository is trusted, `voculinux` installs, `voculinux --version`
|
||||
shows the version, the payload checks pass (desktop entry, icon, marker, no
|
||||
models, no pre-rename names, the Vulkan backend, no host-native CPU tuning),
|
||||
and the imports load (`pynput` through its dummy backend, `evdev`, `gi` with
|
||||
Gtk 3, `pywhispercpp.model`, `pyaudio`, and the app's own modules, which keep their retained internal name).
|
||||
signed repository is trusted, Void's stock `python3-PyAudio` and
|
||||
`python3-pysocks` are installed, then `voculinux` installs and upgrades both
|
||||
to the carried versions (see [Version floors](#version-floors)),
|
||||
`voculinux --version` shows the version, the payload checks pass (desktop
|
||||
entry, icon, marker, no models, no pre-rename names, the Vulkan backend, no
|
||||
host-native CPU tuning), and the imports load (`pynput` through its dummy
|
||||
backend, `evdev`, `gi` with Gtk 3, `pywhispercpp.model`, `pyaudio`, `socks`,
|
||||
and the app's own modules, which keep their retained internal name).
|
||||
|
||||
`tests/test_void_smoke_gate.py` asserts offline that the gate stays wired to
|
||||
every `srcpkgs` template.
|
||||
|
||||
@@ -137,12 +137,24 @@ phase_install() {
|
||||
xbps-query voculinux >/dev/null 2>&1 && fail "voculinux was installed by the untrusted attempt"
|
||||
echo "refused without the key, as required (rc=$REFUSAL_RC)"
|
||||
|
||||
echo "== trust the staged key, then install voculinux from the signed repository"
|
||||
echo "== trust the staged key"
|
||||
cp "$XBPS_STAGE"/keys/*.plist /var/db/xbps/keys/
|
||||
|
||||
echo "== stock Void packages below the project floors, as a user who has them already"
|
||||
xbps-install -y --repository="$OFFICIAL" -S python3-PyAudio python3-pysocks >/dev/null
|
||||
|
||||
echo "== install voculinux from the signed repository; the floors must upgrade the stock packages"
|
||||
xbps-install -y --repository="$XBPS_STAGE/repo" --repository="$OFFICIAL" -S voculinux >/dev/null
|
||||
xbps-query voculinux | grep -qx "pkgver: voculinux-${XBPS_VERSION}_1" ||
|
||||
fail "installed package is not voculinux-${XBPS_VERSION}_1"
|
||||
echo "installed: voculinux-${XBPS_VERSION}_1"
|
||||
for PKG in python3-PyAudio python3-pysocks; do
|
||||
PKGFILE=$(ls "$PKG_STAGE"/"$PKG"-[0-9]*.x86_64.xbps | head -n1)
|
||||
PKGVER=$(basename "$PKGFILE" .x86_64.xbps)
|
||||
xbps-query "$PKG" | grep -qx "pkgver: $PKGVER" ||
|
||||
fail "$PKG is not $PKGVER after voculinux: the stock package was not upgraded"
|
||||
echo "upgraded to the carried template: $PKGVER"
|
||||
done
|
||||
|
||||
echo "== the renamed binary reports its version"
|
||||
VERSION_OUT=$(voculinux --version 2>&1) || fail "voculinux --version exited non-zero"
|
||||
@@ -155,7 +167,7 @@ phase_install() {
|
||||
case "$PKG" in
|
||||
voculinux) payload_voculinux "$PKGFILE" ;;
|
||||
python3-pywhispercpp) payload_pywhispercpp "$PKGFILE" ;;
|
||||
python3-pynput | python3-PyAudio) ;; # checked by import below
|
||||
python3-pynput | python3-PyAudio | python3-pysocks) ;; # checked by import below
|
||||
*) fail "no smoke recipe for package $PKG; add one alongside its template" ;;
|
||||
esac
|
||||
done
|
||||
@@ -168,6 +180,7 @@ phase_install() {
|
||||
python3 -c "import gi; gi.require_version('Gtk', '3.0'); from gi.repository import Gtk; print('gi ok: Gtk', Gtk.get_major_version(), Gtk.get_minor_version())"
|
||||
python3 -c "from pywhispercpp.model import Model; print('pywhispercpp.model ok')"
|
||||
python3 -c "import pyaudio; print('pyaudio ok')"
|
||||
python3 -c "import socks; print('pysocks ok')"
|
||||
python3 -c "import vocalinux.main, vocalinux.speech_recognition.recognition_manager; print('vocalinux imports ok')"
|
||||
echo "INSTALL SMOKE OK: voculinux $VERSION installed from the signed repository"
|
||||
}
|
||||
|
||||
@@ -20,6 +20,6 @@ HERE=$(cd -- "$HERE" && pwd)
|
||||
# Dependency order: the app last. tests/test_void_smoke_gate.py fails if this
|
||||
# list drifts from packaging/void/srcpkgs.
|
||||
# shellcheck disable=SC2034 # read as data by build-release.sh, which parses this line
|
||||
PKGS="python3-PyAudio python3-pynput python3-pywhispercpp voculinux"
|
||||
PKGS="python3-PyAudio python3-pynput python3-pysocks python3-pywhispercpp voculinux"
|
||||
|
||||
exec "$HERE/build-release.sh" smoke
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Template file for 'python3-pysocks'
|
||||
# Carried for the personal repository: Void's python3-pysocks is 1.7.0, below
|
||||
# the project floor PySocks>=1.7.1 (pyproject.toml). The version and checksum
|
||||
# are the PyPI sdist that uv.lock pins. See packaging/void/README.md.
|
||||
pkgname=python3-pysocks
|
||||
version=1.7.1
|
||||
revision=1
|
||||
build_style=python3-module
|
||||
hostmakedepends="python3-setuptools"
|
||||
depends="python3"
|
||||
short_desc="Python3 SOCKS client module"
|
||||
maintainer="Orphaned <orphan@voidlinux.org>"
|
||||
license="BSD-3-Clause"
|
||||
homepage="https://github.com/Anorov/PySocks"
|
||||
distfiles="${PYPI_SITE}/P/PySocks/PySocks-${version}.tar.gz"
|
||||
checksum=3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
|
||||
|
||||
post_install() {
|
||||
vlicense LICENSE
|
||||
}
|
||||
@@ -15,7 +15,12 @@ makedepends="python3-devel"
|
||||
# direct typing on niri, wl-clipboard (wl-copy, wl-paste) for clipboard
|
||||
# paths, xdotool for X11 and the XWayland fallback, pulseaudio-utils for
|
||||
# pactl source listing. ibus is not a dependency: it is excluded on niri.
|
||||
depends="python3 python3-gobject python3-cairo gtk+3 libayatana-appindicator gobject-introspection python3-numpy python3-requests python3-pysocks python3-psutil python3-evdev python3-xlib python3-tqdm python3-platformdirs python3-PyAudio python3-pynput python3-pywhispercpp pipewire wtype wl-clipboard xdotool pulseaudio-utils"
|
||||
# python3-PyAudio and python3-pysocks are version-constrained. Void's stock
|
||||
# packages (0.2.12_5, 1.7.0_8) sit below the project floors, and a bare name is
|
||||
# already satisfied by a stock package a user has installed, so it would never
|
||||
# be upgraded to the carried template. Each >= names the carried template's
|
||||
# version and revision; tests/test_void_app_template.py keeps them in step.
|
||||
depends="python3 python3-gobject python3-cairo gtk+3 libayatana-appindicator gobject-introspection python3-numpy python3-requests python3-pysocks>=1.7.1_1 python3-psutil python3-evdev python3-xlib python3-tqdm python3-platformdirs python3-PyAudio>=0.2.14_1 python3-pynput python3-pywhispercpp pipewire wtype wl-clipboard xdotool pulseaudio-utils"
|
||||
short_desc="Free, offline voice dictation for Linux"
|
||||
maintainer="Xavier Karma <xavier@bongbetic.com>"
|
||||
license="AGPL-3.0-only"
|
||||
|
||||
@@ -23,6 +23,8 @@ import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
VOID = REPO_ROOT / "packaging" / "void"
|
||||
SRC = VOID / "srcpkgs"
|
||||
@@ -128,6 +130,19 @@ VERIFIED_VOID_PACKAGES = frozenset(
|
||||
#: never by hiding it behind one of these.
|
||||
FORBIDDEN_VARS = ("noverifypydeps", "nopyprovides")
|
||||
|
||||
#: Carried templates whose stock Void package sits below the project floor
|
||||
#: (tests/test_void_small_deps.py, GAP_PACKAGES). voculinux must require each
|
||||
#: one as name>=<carried version>_<revision>. A bare name is satisfied by the
|
||||
#: stock package a user already has, so it would never be upgraded.
|
||||
GAP_DEPENDS = ("python3-PyAudio", "python3-pysocks")
|
||||
|
||||
#: One xbps dependency token: a bare name, or name, comparator and
|
||||
#: version_revision. The whole token must match; a malformed token such as
|
||||
#: "python3-PyAudio>=" is refused, never stripped down to its name.
|
||||
DEPENDS_TOKEN = re.compile(
|
||||
r"(?P<name>[A-Za-z0-9+._-]+)(?:(?P<op>>=|<=|>|<)(?P<version>[0-9][A-Za-z0-9.]*_[0-9]+))?"
|
||||
)
|
||||
|
||||
|
||||
def _template_text() -> str:
|
||||
"""Read the raw template file."""
|
||||
@@ -193,6 +208,31 @@ def _project_dependencies() -> list:
|
||||
return list(data["project"]["dependencies"])
|
||||
|
||||
|
||||
def _depends_tokens(value: str) -> list:
|
||||
"""Validated tokens of an xbps depends value; a malformed token fails the test."""
|
||||
tokens = []
|
||||
for token in value.split():
|
||||
if token.startswith("${"):
|
||||
continue
|
||||
assert DEPENDS_TOKEN.fullmatch(token), f"malformed dependency token: {token!r}"
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def _depends_names(value: str) -> set:
|
||||
"""Package names of an xbps depends value, with any version constraint removed."""
|
||||
return {DEPENDS_TOKEN.fullmatch(token).group("name") for token in _depends_tokens(value)}
|
||||
|
||||
|
||||
def _carried_pkgver(name: str) -> str:
|
||||
"""The pkgver a carried template builds: its version and revision, as written."""
|
||||
text = (SRC / name / "template").read_text(encoding="utf-8")
|
||||
version = re.search(r"^version=(\S+)$", text, re.M)
|
||||
revision = re.search(r"^revision=(\d+)$", text, re.M)
|
||||
assert version and revision, f"{name} template lacks a version or revision"
|
||||
return f"{version.group(1)}_{revision.group(1)}"
|
||||
|
||||
|
||||
def test_app_template_lives_in_void_packages_layout() -> None:
|
||||
"""The app template sits where xbps-src expects it: srcpkgs/<pkgname>."""
|
||||
assert TEMPLATE.is_file(), f"{TEMPLATE} is missing"
|
||||
@@ -275,7 +315,7 @@ def test_checksum_is_a_recorded_digest_or_the_build_placeholder() -> None:
|
||||
|
||||
def test_every_project_dependency_is_covered() -> None:
|
||||
"""pyproject.toml runtime deps plus substrate must all resolve to depends."""
|
||||
depends = set(_template_vars()["depends"].split())
|
||||
depends = _depends_names(_template_vars()["depends"])
|
||||
uncovered = []
|
||||
for clause in _project_dependencies():
|
||||
pip_name = re.match(r"^([A-Za-z0-9._-]+)", clause).group(1).lower().replace("_", "-")
|
||||
@@ -293,14 +333,37 @@ def test_template_dependencies_exist_in_void_packages() -> None:
|
||||
variables = _template_vars()
|
||||
referenced = set()
|
||||
for key in ("depends", "hostmakedepends", "makedepends", "checkdepends"):
|
||||
for token in variables.get(key, "").split():
|
||||
if token.startswith("${"):
|
||||
continue
|
||||
referenced.add(token)
|
||||
referenced |= _depends_names(variables.get(key, ""))
|
||||
unknown = sorted(referenced - VERIFIED_VOID_PACKAGES)
|
||||
assert not unknown, f"template references unverified packages: {unknown}"
|
||||
|
||||
|
||||
def test_depends_tokens_are_validated_not_stripped() -> None:
|
||||
"""A constrained token is accepted only in full; a truncated one is malformed."""
|
||||
assert DEPENDS_TOKEN.fullmatch("python3-PyAudio>=0.2.14_1")
|
||||
assert DEPENDS_TOKEN.fullmatch("python3-pysocks")
|
||||
for malformed in ("python3-PyAudio>=", "python3-PyAudio>=0.2.14", ">=0.2.14_1"):
|
||||
assert not DEPENDS_TOKEN.fullmatch(malformed), malformed
|
||||
with pytest.raises(AssertionError):
|
||||
_depends_tokens(malformed)
|
||||
|
||||
|
||||
def test_gap_packages_are_version_constrained_to_their_carried_templates() -> None:
|
||||
"""voculinux requires name>=<carried pkgver>, never the bare name.
|
||||
|
||||
A bare name is satisfied by the stock package a user already has (below the
|
||||
floor), so it would keep the stock package. The constraint must track the
|
||||
carried template: bumping its version or revision without this line fails.
|
||||
"""
|
||||
tokens = _depends_tokens(_template_vars()["depends"])
|
||||
for name in GAP_DEPENDS:
|
||||
matching = [
|
||||
token for token in tokens if DEPENDS_TOKEN.fullmatch(token).group("name") == name
|
||||
]
|
||||
expected = f"{name}>={_carried_pkgver(name)}"
|
||||
assert matching == [expected], f"depends must name {expected}; found {matching}"
|
||||
|
||||
|
||||
def test_no_stale_indicator_library_name() -> None:
|
||||
"""The pre-rename libappindicator names do not exist in void-packages."""
|
||||
variables = _template_vars()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Offline policy tests for the Void template of the whisper.cpp binding (#26).
|
||||
|
||||
Spec #16 packages three templates in void-packages layout under
|
||||
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
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
"""Offline policy guards for the Void small-dependency templates (ticket #25).
|
||||
|
||||
Spec #16 (seam 2) closes the two easy Void dependency gaps — the X11
|
||||
keyboard input library and the updated audio binding — as in-repo xbps
|
||||
templates in void-packages layout under the packaging tree, with the audio
|
||||
binding at or above the project's declared floor and no
|
||||
verification-disabling template options anywhere.
|
||||
Spec #16 (seam 2) closes the Void dependency gaps — the X11 keyboard input
|
||||
library, and the audio binding and SOCKS module that sit below the project's
|
||||
declared floors — as in-repo xbps templates in void-packages layout under the
|
||||
packaging tree, each at or above its floor, and no verification-disabling
|
||||
template options anywhere.
|
||||
|
||||
These tests pin the templates to the project's own lockfile (`uv.lock`,
|
||||
`requirements/runtime.txt`, `pyproject.toml`) so a template that drifts
|
||||
@@ -17,6 +17,8 @@ import re
|
||||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||
VOID = REPO_ROOT / "packaging" / "void"
|
||||
SRC = VOID / "srcpkgs"
|
||||
@@ -25,10 +27,24 @@ PYPROJECT = REPO_ROOT / "pyproject.toml"
|
||||
RUNTIME = REPO_ROOT / "requirements" / "runtime.txt"
|
||||
LOCK = REPO_ROOT / "uv.lock"
|
||||
|
||||
TEMPLATES = ("python3-pynput", "python3-PyAudio")
|
||||
TEMPLATES = ("python3-pynput", "python3-PyAudio", "python3-pysocks")
|
||||
|
||||
#: uv.lock package name per template directory name.
|
||||
LOCK_NAMES = {"python3-pynput": "pynput", "python3-PyAudio": "pyaudio"}
|
||||
LOCK_NAMES = {
|
||||
"python3-pynput": "pynput",
|
||||
"python3-PyAudio": "pyaudio",
|
||||
"python3-pysocks": "pysocks",
|
||||
}
|
||||
|
||||
#: Carried templates that replace a stock Void package sitting below the
|
||||
#: project's floor: template -> (pip name, stock Void pkgver, the floor).
|
||||
#: The stock pkgvers were read from void-packages master on 2026-11-06
|
||||
#: (srcpkgs/python3-PyAudio/template 0.2.12_5, srcpkgs/python3-pysocks/template
|
||||
#: 1.7.0_8). Stock below the floor is what makes the gap real.
|
||||
GAP_PACKAGES = {
|
||||
"python3-PyAudio": ("pyaudio", "0.2.12_5", (0, 2, 13)),
|
||||
"python3-pysocks": ("pysocks", "1.7.0_8", (1, 7, 1)),
|
||||
}
|
||||
|
||||
#: Void package names covering pynput's linux-marked install_requires
|
||||
#: (evdev>=1.3, python-xlib>=0.17, six) as recorded in uv.lock.
|
||||
@@ -62,6 +78,7 @@ FORBIDDEN_VARS = ("noverifypydeps", "nopyprovides")
|
||||
EXPECTED_LICENSES = {
|
||||
"python3-pynput": "LGPL-3.0-or-later",
|
||||
"python3-PyAudio": "MIT",
|
||||
"python3-pysocks": "BSD-3-Clause",
|
||||
}
|
||||
|
||||
#: Void Manual mandatory variables, plus the fetch inputs every template
|
||||
@@ -165,7 +182,7 @@ def _void_pins() -> dict:
|
||||
|
||||
|
||||
def test_templates_live_in_void_packages_layout() -> None:
|
||||
"""Both templates sit where xbps-src expects them: srcpkgs/<pkgname>."""
|
||||
"""Every carried template sits where xbps-src expects it: srcpkgs/<pkgname>."""
|
||||
for name in TEMPLATES:
|
||||
template = SRC / name / "template"
|
||||
assert template.is_file(), f"{template} is missing"
|
||||
@@ -203,10 +220,12 @@ def test_template_checksums_match_lock_sdist() -> None:
|
||||
assert _template_vars(name)["checksum"] == digest, name
|
||||
|
||||
|
||||
def test_audio_binding_satisfies_project_floor() -> None:
|
||||
@pytest.mark.parametrize("name", sorted(GAP_PACKAGES))
|
||||
def test_carried_templates_satisfy_project_floors(name: str) -> None:
|
||||
"""The updated template meets the declared floor it was built to close."""
|
||||
specifier = _project_specifier("pyaudio")
|
||||
template = _version_tuple(_template_version("python3-PyAudio"))
|
||||
pip_name, _, _ = GAP_PACKAGES[name]
|
||||
specifier = _project_specifier(pip_name)
|
||||
template = _version_tuple(_template_version(name))
|
||||
for operator, floor in re.findall(r"(>=|==|~=|>)\s*([\d][\d.]*)", specifier):
|
||||
if operator == ">":
|
||||
assert template > _version_tuple(floor), specifier
|
||||
@@ -214,16 +233,20 @@ def test_audio_binding_satisfies_project_floor() -> None:
|
||||
assert template >= _version_tuple(floor), specifier
|
||||
|
||||
|
||||
def test_audio_binding_floor_was_not_relaxed() -> None:
|
||||
"""Void's 0.2.12 stays below the floor: the floor itself never moved."""
|
||||
specifier = _project_specifier("pyaudio")
|
||||
@pytest.mark.parametrize("name", sorted(GAP_PACKAGES))
|
||||
def test_stock_package_is_below_the_floor_and_the_floor_was_not_relaxed(name: str) -> None:
|
||||
"""Stock Void sits below the floor, so the gap is real; the floor never moved."""
|
||||
pip_name, stock, floor = GAP_PACKAGES[name]
|
||||
stock_version = _version_tuple(stock.split("_")[0])
|
||||
assert stock_version < floor, f"{name}: stock {stock} is not below the floor {floor}"
|
||||
specifier = _project_specifier(pip_name)
|
||||
lowers = [
|
||||
_version_tuple(floor)
|
||||
for operator, floor in re.findall(r"(>=|==|~=|>)\s*([\d][\d.]*)", specifier)
|
||||
_version_tuple(bound)
|
||||
for operator, bound in re.findall(r"(>=|==|~=|>)\s*([\d][\d.]*)", specifier)
|
||||
if operator in (">=", "==", "~=", ">")
|
||||
]
|
||||
assert lowers, f"pyaudio clause carries no lower bound: {specifier}"
|
||||
assert min(lowers) >= (0, 2, 13), f"floor relaxed to hide the gap: {specifier}"
|
||||
assert lowers, f"{pip_name} clause carries no lower bound: {specifier}"
|
||||
assert min(lowers) >= floor, f"floor relaxed to hide the gap: {specifier}"
|
||||
|
||||
|
||||
def test_no_verification_disabling_options() -> None:
|
||||
|
||||
@@ -100,10 +100,55 @@ def test_the_ggml_scan_belongs_to_the_whisper_binding_only() -> None:
|
||||
|
||||
|
||||
def test_the_leaf_packages_are_imported_headless() -> None:
|
||||
"""PyAudio and pynput are proven by import: pynput's dummy backend needs no display."""
|
||||
"""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:
|
||||
|
||||
Reference in New Issue
Block a user