From 1a5f0dac50bd3fc34fbf06881f68371cefcb89b9 Mon Sep 17 00:00:00 2001 From: David Hyunyoo Jang Date: Fri, 14 Aug 2026 20:36:49 +0900 Subject: [PATCH 1/4] fix waiver baseline tag selection --- check-qualification.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/check-qualification.py b/check-qualification.py index 0bbd568..94d1501 100644 --- a/check-qualification.py +++ b/check-qualification.py @@ -22,6 +22,7 @@ from pythonbuild import waiver from pythonbuild.qualification import ( + QUALIFICATION_ROOT, QualificationError, previous_qualified_tag, shipped_api_levels, @@ -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 + + +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, ) From de2261447f96e6c357c96e61a958959b350c74af Mon Sep 17 00:00:00 2001 From: David Hyunyoo Jang Date: Fri, 14 Aug 2026 20:37:03 +0900 Subject: [PATCH 2/4] test waiver baseline tag selection --- tests/test_check_qualification.py | 75 +++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/test_check_qualification.py diff --git a/tests/test_check_qualification.py b/tests/test_check_qualification.py new file mode 100644 index 0000000..0724735 --- /dev/null +++ b/tests/test_check_qualification.py @@ -0,0 +1,75 @@ +"""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 + +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 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") + original = SCRIPT["run"] + SCRIPT["run"] = self.with_git_tags({"20260729"}) + try: + self.assertEqual( + SCRIPT["previous_released_qualified_tag"]("20260814", root=root), + "20260729", + ) + finally: + SCRIPT["run"] = original + + def test_no_released_qualified_candidate_returns_none(self) -> None: + with TemporaryDirectory() as tmp: + root = Path(tmp) + self.write_receipt(root, "20260730") + original = SCRIPT["run"] + SCRIPT["run"] = self.with_git_tags(set()) + try: + self.assertIsNone( + SCRIPT["previous_released_qualified_tag"]("20260814", root=root) + ) + finally: + SCRIPT["run"] = original + + +if __name__ == "__main__": + unittest.main() From e21936a53e2bf77af80be1c3ea83de6d994a427a Mon Sep 17 00:00:00 2001 From: David Hyunyoo Jang Date: Fri, 14 Aug 2026 22:13:44 +0900 Subject: [PATCH 3/4] fix qualification tag test fixture --- tests/test_check_qualification.py | 32 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/tests/test_check_qualification.py b/tests/test_check_qualification.py index 0724735..6c892c1 100644 --- a/tests/test_check_qualification.py +++ b/tests/test_check_qualification.py @@ -42,33 +42,31 @@ def fake_run(argv: list[str]) -> SimpleNamespace: 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 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") - original = SCRIPT["run"] - SCRIPT["run"] = self.with_git_tags({"20260729"}) - try: - self.assertEqual( - SCRIPT["previous_released_qualified_tag"]("20260814", root=root), - "20260729", - ) - finally: - SCRIPT["run"] = original + 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") - original = SCRIPT["run"] - SCRIPT["run"] = self.with_git_tags(set()) - try: - self.assertIsNone( - SCRIPT["previous_released_qualified_tag"]("20260814", root=root) - ) - finally: - SCRIPT["run"] = original + self.assertIsNone(self.call_with_git_tags("20260814", root, set())) if __name__ == "__main__": From ffbe1bf152536279ccd5cbe11f7031c5cdec4a78 Mon Sep 17 00:00:00 2001 From: David Hyunyoo Jang Date: Fri, 14 Aug 2026 22:15:21 +0900 Subject: [PATCH 4/4] fix test helper typing --- tests/test_check_qualification.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_check_qualification.py b/tests/test_check_qualification.py index 6c892c1..79aeb39 100644 --- a/tests/test_check_qualification.py +++ b/tests/test_check_qualification.py @@ -8,7 +8,7 @@ from pathlib import Path from tempfile import TemporaryDirectory from types import SimpleNamespace -from typing import Any +from typing import Any, cast ROOT = Path(__file__).resolve().parents[1] SCRIPT = runpy.run_path(str(ROOT / "check-qualification.py")) @@ -48,7 +48,7 @@ def call_with_git_tags(self, tag: str, root: Path, tags: set[str]) -> str | None original = globals_["run"] globals_["run"] = self.with_git_tags(tags) try: - return function(tag, root=root) + return cast(str | None, function(tag, root=root)) finally: globals_["run"] = original