Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions check-qualification.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

from pythonbuild import waiver
from pythonbuild.qualification import (
QUALIFICATION_ROOT,
QualificationError,
previous_qualified_tag,
shipped_api_levels,
Expand Down Expand Up @@ -88,15 +89,36 @@ def changed_since(tag: str) -> list[str]:
return [line for line in result.stdout.splitlines() if line]


def _git_tag_exists(tag: str) -> bool:
"""Whether ``tag`` names a real Git tag in the release checkout."""
result = run(["git", "show-ref", "--verify", "--quiet", f"refs/tags/{tag}"])
return result.returncode == 0
Comment on lines +92 to +95


def previous_released_qualified_tag(
tag: str, root: Path = QUALIFICATION_ROOT
) -> str | None:
"""Newest earlier qualified candidate that was actually released as a Git tag.

Qualification receipts may intentionally exist for candidates that were never
released. Those receipts remain useful evidence, but there is no commit range
to diff from unless the candidate also has a Git tag.
"""
previous = previous_qualified_tag(tag, root=root)
while previous is not None and not _git_tag_exists(previous):
previous = previous_qualified_tag(previous, root=root)
return previous


def consider_waiver(
build: Build, tag: str, refusal: QualificationError, report: Path | None
) -> int:
"""Permit an unattended release only when the change is upstream's alone."""
previous = previous_qualified_tag(tag)
previous = previous_released_qualified_tag(tag)
if previous is None:
print(
f"qualification gate: REFUSED\n\n{refusal}\n\n"
f"No earlier qualified tag to compare against, so there is nothing a "
f"No earlier released qualified tag to compare against, so there is nothing a "
f"waiver could rest on.",
file=sys.stderr,
)
Expand Down
73 changes: 73 additions & 0 deletions tests/test_check_qualification.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
"""Regression tests for the release qualification command."""

from __future__ import annotations

import json
import runpy
import unittest
from pathlib import Path
from tempfile import TemporaryDirectory
from types import SimpleNamespace
from typing import Any, cast

ROOT = Path(__file__).resolve().parents[1]
SCRIPT = runpy.run_path(str(ROOT / "check-qualification.py"))


class ReleasedQualificationHistoryTest(unittest.TestCase):
def write_receipt(self, root: Path, tag: str) -> None:
directory = root / tag
directory.mkdir(parents=True, exist_ok=True)
document: dict[str, Any] = {
"receipt_kind": "android-device-qualification",
"verdict": {"pass": True, "failures": []},
"executed_artifact": {
"filename": (
f"cpython-3.14.6+{tag}-aarch64-linux-android-"
"install_only_stripped.tar.gz"
)
},
"checks": {"identity": {"android_api_level": 34}},
}
(directory / "receipt.json").write_text(json.dumps(document), encoding="utf-8")

def with_git_tags(self, tags: set[str]) -> Any:
def fake_run(argv: list[str]) -> SimpleNamespace:
ref = argv[-1]
return SimpleNamespace(
returncode=0 if ref.removeprefix("refs/tags/") in tags else 1,
stdout="",
stderr="",
)

return fake_run

def call_with_git_tags(self, tag: str, root: Path, tags: set[str]) -> str | None:
function = SCRIPT["previous_released_qualified_tag"]
globals_ = function.__globals__
original = globals_["run"]
globals_["run"] = self.with_git_tags(tags)
try:
return cast(str | None, function(tag, root=root))
finally:
globals_["run"] = original

def test_unreleased_qualified_candidate_is_skipped(self) -> None:
with TemporaryDirectory() as tmp:
root = Path(tmp)
self.write_receipt(root, "20260729")
self.write_receipt(root, "20260730")
self.assertEqual(
self.call_with_git_tags("20260814", root, {"20260729"}),
"20260729",
)

def test_no_released_qualified_candidate_returns_none(self) -> None:
with TemporaryDirectory() as tmp:
root = Path(tmp)
self.write_receipt(root, "20260730")
self.assertIsNone(self.call_with_git_tags("20260814", root, set()))


if __name__ == "__main__":
unittest.main()
Loading