Version bumps are version-string changes; the full pipeline is the wrong tool. Per Nick: - provenance: bump PR body now RECORDS the PR that earned the promotion (Earned by: #N — title, with link) — the promotion has a paper trail - light gate: bump PRs skip the 7 heavy jobs (head-ref if); required-checks runs scripts/check_version_surfaces.py instead — proves all five version surfaces agree (__init__ canonical, rune_tui header, canary literal, pyproject wheel, README badge) so the TUI footer will render the new version (VersionLabel reads rune.__version__ at call time) - red-first: 7 tests for the surface gate; full suite 877+84
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""Red-first tests for scripts/check_version_surfaces.py — the lightweight
|
|
version-bump gate (Nick's policy, 2026-09-08: version bumps don't need the
|
|
full pipeline, just proof the TUI will display the new version).
|
|
|
|
Surfaces checked (all must agree on ONE version):
|
|
- src/rune/__init__.py __version__ = "X.Y.Z" (canonical; TUI reads this)
|
|
- src/rune/rune_tui.py header "Version: vX.Y.Z" (drift guard)
|
|
- src/rune/canary_gitea_automation.py '__version__ = "X.Y.Z"' assertion
|
|
- pyproject.toml version = "X.Y.Z" (wheel metadata)
|
|
- README.md vX.Y.Z badge
|
|
|
|
The TUI-correctness core: VersionLabel.render() reads rune.__version__ at
|
|
call time, so __init__ IS the displayed version. The gate proves every
|
|
surface agrees with it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SPEC = importlib.util.spec_from_file_location("check_version_surfaces", ROOT / "scripts" / "check_version_surfaces.py")
|
|
cvs = importlib.util.module_from_spec(SPEC)
|
|
SPEC.loader.exec_module(cvs)
|
|
|
|
|
|
@pytest.fixture
|
|
def good_tree(tmp_path):
|
|
(tmp_path / "src" / "rune").mkdir(parents=True)
|
|
(tmp_path / "src" / "rune" / "__init__.py").write_text('__version__ = "0.55.0"\n')
|
|
(tmp_path / "src" / "rune" / "rune_tui.py").write_text("Version: v0.55.0\n")
|
|
(tmp_path / "src" / "rune" / "canary_gitea_automation.py").write_text(
|
|
"'__version__ = \"0.55.0\"' in rune_version,\n"
|
|
)
|
|
(tmp_path / "pyproject.toml").write_text('version = "0.55.0"\n')
|
|
(tmp_path / "README.md").write_text("# Rune v0.55.0\n")
|
|
return tmp_path
|
|
|
|
|
|
def test_all_surfaces_agree_passes(good_tree):
|
|
assert cvs.check(good_tree, "0.55.0") == []
|
|
|
|
|
|
def test_stale_tui_header_fails(good_tree):
|
|
p = good_tree / "src" / "rune" / "rune_tui.py"
|
|
p.write_text("Version: v0.54.1\n")
|
|
errs = cvs.check(good_tree, "0.55.0")
|
|
assert any("rune_tui" in e for e in errs), "TUI header drift must be caught"
|
|
|
|
|
|
def test_stale_pyproject_fails(good_tree):
|
|
p = good_tree / "pyproject.toml"
|
|
p.write_text('version = "0.49.0"\n')
|
|
errs = cvs.check(good_tree, "0.55.0")
|
|
assert any("pyproject" in e for e in errs)
|
|
|
|
|
|
def test_stale_canary_assertion_fails(good_tree):
|
|
p = good_tree / "src" / "rune" / "canary_gitea_automation.py"
|
|
p.write_text("'__version__ = \"0.54.1\"' in rune_version,\n")
|
|
errs = cvs.check(good_tree, "0.55.0")
|
|
assert any("canary_gitea_automation" in e for e in errs)
|
|
|
|
|
|
def test_stale_readme_fails(good_tree):
|
|
p = good_tree / "README.md"
|
|
p.write_text("# Rune v0.54.1\n")
|
|
errs = cvs.check(good_tree, "0.55.0")
|
|
assert any("README" in e for e in errs)
|
|
|
|
|
|
def test_missing_surface_fails(good_tree):
|
|
(good_tree / "pyproject.toml").unlink()
|
|
errs = cvs.check(good_tree, "0.55.0")
|
|
assert any("pyproject" in e for e in errs)
|
|
|
|
|
|
def test_main_exit_codes(good_tree, capsys):
|
|
assert cvs.main_with(["check", "--root", str(good_tree), "--version", "0.55.0"]) == 0
|
|
(good_tree / "README.md").write_text("# Rune v0.54.1\n")
|
|
assert cvs.main_with(["check", "--root", str(good_tree), "--version", "0.55.0"]) == 1
|