ci: Gitea checks — ruff, CPython 3.8–3.14 matrix, wheel+sdist smoke tests, ci-ok aggregate
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""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 pytest
|
||||
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():
|
||||
for name, job in load()["jobs"].items():
|
||||
cond = flat(job.get("if", ""))
|
||||
for clause in WIP_CLAUSES:
|
||||
assert clause in cond, "%s lacks WIP gate clause %s" % (name, clause)
|
||||
|
||||
|
||||
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()
|
||||
|
||||
|
||||
@pytest.mark.skipif(not VERIFY_SCRIPT.exists(), reason="added with the release task")
|
||||
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
|
||||
Reference in New Issue
Block a user