Files
2026-10-05 21:35:52 -06:00

134 lines
4.4 KiB
Python

"""Pins the CI workflow's invariants (spec: docs/superpowers/specs/
2026-10-04-modernization-design.md). CI drift shows up as a red test here."""
import pathlib
import re
import yaml
ROOT = pathlib.Path(__file__).resolve().parent.parent
WORKFLOW = ROOT / ".gitea" / "workflows" / "ci.yaml"
VERIFY_SCRIPT = ROOT / "scripts" / "verify_index_release.sh"
# Newest CPython the project claims. To add a version, bump this, the
# matrix, the classifiers and the verify script's list together.
NEWEST_MINOR = 14
WIP_CLAUSES = (
"!startsWith(github.event.pull_request.title, 'WIP:')",
"!startsWith(github.event.pull_request.title, '[WIP]')",
)
def load():
return yaml.safe_load(WORKFLOW.read_text())
def triggers(wf):
# PyYAML follows YAML 1.1, where the bare key `on` parses as boolean True.
return wf["on"] if "on" in wf else wf[True]
def expected_versions():
text = (ROOT / "pyproject.toml").read_text()
floor = int(re.search(r'requires-python\s*=\s*">=3\.(\d+)"', text).group(1))
return ["3.%d" % minor for minor in range(floor, NEWEST_MINOR + 1)]
def flat(text):
return " ".join(text.split())
def test_triggers():
on = triggers(load())
assert set(on["pull_request"]["types"]) == {
"opened",
"synchronize",
"reopened",
"edited",
}
assert on["push"]["branches"] == ["develop", "main"]
assert "workflow_dispatch" in on
def test_superseded_pr_runs_cancel_but_push_runs_never_do():
assert (
load()["concurrency"]["cancel-in-progress"]
== "${{ github.event_name == 'pull_request' }}"
)
def test_every_job_is_wip_gated():
# Compared whole, not by substring: `&&` turned into `||` must fail.
gate = "github.event_name != 'pull_request' || ( %s && %s )" % WIP_CLAUSES
for name, job in load()["jobs"].items():
cond = flat(job.get("if", ""))
assert cond in (gate, "always() && ( %s )" % gate), name
def test_no_gate_can_be_softened_by_continue_on_error():
# continue-on-error makes needs.<job>.result read success, so ci-ok
# would go green over a failure.
for name, job in load()["jobs"].items():
assert "continue-on-error" not in job, name
for step in job.get("steps", []):
assert "continue-on-error" not in step, name
def test_each_lane_really_runs_its_matrix_python():
test = load()["jobs"]["test"]
assert test["container"] == "python:${{ matrix.python }}"
assert test["env"]["UV_PYTHON"] == "${{ matrix.python }}"
assert test["env"]["UV_PYTHON_DOWNLOADS"] == "never"
def test_matrix_matches_requires_python():
test = load()["jobs"]["test"]
assert test["strategy"]["matrix"]["python"] == expected_versions()
assert test["strategy"]["fail-fast"] is False
def test_classifiers_match_matrix():
text = (ROOT / "pyproject.toml").read_text()
claimed = re.findall(r'"Programming Language :: Python :: (3\.\d+)"', text)
assert claimed == expected_versions()
def test_verify_script_covers_matrix():
match = re.search(r"^pythons=\(([^)]*)\)", VERIFY_SCRIPT.read_text(), re.M)
assert match.group(1).split() == expected_versions()
def test_checkout_fetches_full_history_for_setuptools_scm():
for name, job in load()["jobs"].items():
for step in job.get("steps", []):
if step.get("uses", "").startswith("actions/checkout"):
assert step.get("with", {}).get("fetch-depth") == 0, name
def test_lint_job_runs_ruff():
runs = flat(" ".join(s.get("run", "") for s in load()["jobs"]["lint"]["steps"]))
assert "uv run ruff check" in runs
assert "uv run ruff format --check" in runs
def test_test_lanes_run_locked_suite_and_smoke_test_both_artifacts():
runs = flat(" ".join(s.get("run", "") for s in load()["jobs"]["test"]["steps"]))
assert "uv sync --locked" in runs
assert "uv run pytest" in runs
assert "uv build" in runs
assert "dist/*.whl dist/*.tar.gz" in runs
assert "scripts/smoke_import.py" in runs
def test_ci_ok_fails_unless_every_gate_succeeded():
jobs = load()["jobs"]
ci_ok = jobs["ci-ok"]
# Gitea reports a skipped job as success, so ci-ok must run always()
# and check each result explicitly.
assert set(ci_ok["needs"]) == set(jobs) - {"ci-ok"}
assert flat(ci_ok["if"]).startswith("always() &&")
runs = " ".join(s.get("run", "") for s in ci_ok["steps"])
for needed in ci_ok["needs"]:
assert 'test "${{ needs.%s.result }}" = success' % needed in runs