Files
voculinux/tests/test_nightly_versioning.py
T
xavierk 80a3792576
CI / security (pull_request) Failing after 1m25s
CI / lint (pull_request) Failing after 34s
CI / ai-review (pull_request) Successful in 25s
chore: import vocalinux project
Import the vocalinux source tree (tray app, Next.js site in web/,
packaging, installers, docs and tests) on top of the Gitea template
commit. The project README replaces the template README, and the
template CI files (.gitea/workflows/ci.yml, .jscpd.json, CI.md) are kept.
2026-11-06 11:51:44 +05:30

109 lines
4.3 KiB
Python

"""A nightly artifact's filename and its own metadata have to agree.
`nightly.yml` used to build the wheel and then rename the file to carry the
nightly version, leaving `.dist-info` still saying 0.16.1 while the filename
claimed 0.16.1.dev<date>+<sha>. pip installed that without complaint for as long
as the AppImage build used pip. #743 moved that install to uv, which rejects the
mismatch outright:
Wheel version does not match filename (0.16.1 != 0.16.1.dev20260831+71929b7),
which indicates a malformed wheel.
Nightly went red the first night it ran the new build.sh and stayed red. The fix
is to stamp `vocalinux.version.__version__` — where `tool.setuptools.dynamic`
reads the version from — before building, so the two cannot disagree.
Parsed as text rather than YAML: the only YAML parser in the venv arrives
transitively through pre-commit.
"""
import re
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
NIGHTLY = REPO_ROOT / ".github" / "workflows" / "nightly.yml"
PYPROJECT = REPO_ROOT / "pyproject.toml"
STAMP = '__version__ = \\"${'
#: The jobs that build a distribution, and so must stamp before they do.
BUILDING_JOBS = {"nightly", "nightly-appimage-arm64"}
def _text() -> str:
return NIGHTLY.read_text(encoding="utf-8")
def _jobs() -> dict:
"""job name -> its block of the workflow, split on the 2-space indent."""
text = _text()
start = text.index("\njobs:\n")
body = text[start:]
headers = list(re.finditer(r"^ ([a-z0-9][a-z0-9-]*):$", body, re.M))
assert headers, "no jobs found; nightly.yml's layout moved"
jobs = {}
for index, header in enumerate(headers):
end = headers[index + 1].start() if index + 1 < len(headers) else len(body)
jobs[header.group(1)] = body[header.start() : end]
return jobs
def _building_jobs() -> dict:
return {name: block for name, block in _jobs().items() if "python -m build" in block}
def test_the_version_still_comes_from_the_file_the_workflow_stamps():
"""If setuptools stopped reading version.py, stamping it would silently do
nothing and the mismatch would come back."""
pyproject = PYPROJECT.read_text(encoding="utf-8")
assert 'version = {attr = "vocalinux.version.__version__"}' in pyproject
def test_nightly_never_renames_a_built_artifact():
"""Renaming a wheel does not change what is recorded inside it."""
text = _text()
assert "Rename artifacts" not in text, "the rename step is back"
offenders = [
line.strip()
for line in text.splitlines()
if re.search(r"\bmv\b.*dist/", line) or re.search(r"newname=", line)
]
assert not offenders, "nightly renames build output:\n" + "\n".join(offenders)
def test_every_job_that_builds_stamps_the_version_first():
jobs = _building_jobs()
# A floor, not a fixed count. The guard is worthless if the parser quietly
# stops finding a job, but a third build job should not fail CI merely for
# existing: the loop below covers whatever is actually there.
missing = BUILDING_JOBS - set(jobs)
assert not missing, f"{sorted(missing)} no longer builds, or the parser stopped seeing it"
for name, block in jobs.items():
assert STAMP in block, f"{name} builds without stamping the version"
assert block.index(STAMP) < block.index(
"python -m build"
), f"{name} stamps the version after building, which changes nothing"
def test_the_stamp_is_verified_rather_than_assumed():
"""A `sed` that matches nothing exits 0 and hands the build a stale version."""
for name, block in _building_jobs().items():
assert re.search(
r'grep -q "\^__version__ = ', block
), f"{name} does not check that the stamp landed"
def test_both_jobs_stamp_the_same_version():
"""The aarch64 job builds its own wheel; if it stamps something else, the two
AppImages on one nightly release carry different versions.
Anchored on the assignment that feeds `sed`, not on the value appearing
anywhere in the job: it also appears in the AppImage step, which made an
earlier version of this test pass while aarch64 stamped a date.
"""
arm = _jobs()["nightly-appimage-arm64"]
assert (
'VERSION="${{ needs.nightly.outputs.version }}"' in arm
), "aarch64 stamps something other than the version the nightly job published"