mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 19:01:54 +00:00
The wait job picks the newest privileged run on the head commit of the PR. Two fork PRs that share a head repository, branch and commit but target different bases each start such a run, and the commit alone cannot tell them apart. A failed run of the other PR would then end the wait of this one while its images are still being built. The run name of the workflow carries the PR number, so the match now requires it next to the head commit. A run without that name matches nothing, and the wait does what it does whenever no run is found: it keeps polling for the images until they exist or the wait times out. Raised by the CodeRabbit review of this PR. Refs #784 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
158 lines
5.7 KiB
Python
158 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
from utils.cache.files import PROJECT_ROOT
|
|
|
|
SCRIPT = PROJECT_ROOT / "scripts" / "meta" / "wait" / "images.sh"
|
|
HEAD_SHA = "5b002678df222f1095fae2f42e3bb10964adb8ef"
|
|
RUN_TITLE = "pull_request_target / PR #1 / synchronize / fork:branch"
|
|
STUBS = {
|
|
"docker": '#!/usr/bin/env bash\n[[ -e "${STUB_DIR}/images-ready" ]]\n',
|
|
"gh": (
|
|
"#!/usr/bin/env bash\n"
|
|
'printf "gh %s\\n" "$*" >&2\n'
|
|
'if [[ -n "${STUB_IMAGES_APPEAR:-}" ]]; then\n'
|
|
'\ttouch "${STUB_DIR}/images-ready"\n'
|
|
"fi\n"
|
|
'if [[ -e "${STUB_DIR}/gh-response" ]]; then\n'
|
|
'\tcat "${STUB_DIR}/gh-response"\n'
|
|
"\texit 0\n"
|
|
"fi\n"
|
|
'echo "HTTP 504" >&2\n'
|
|
"exit 1\n"
|
|
),
|
|
}
|
|
|
|
|
|
def wait_for_images(
|
|
tmp: str, runs: list[dict] | None = None, **overrides: str
|
|
) -> subprocess.CompletedProcess:
|
|
"""Run the wait script against stubbed ``docker`` and ``gh``.
|
|
|
|
Args:
|
|
tmp: directory that holds the stubs and their state.
|
|
runs: workflow runs ``gh`` answers with; without them every lookup fails.
|
|
overrides: environment entries layered over the defaults.
|
|
"""
|
|
stub_dir = Path(tmp)
|
|
for name, body in STUBS.items():
|
|
stub = stub_dir / name
|
|
stub.write_text(body)
|
|
stub.chmod(0o755)
|
|
if runs is not None:
|
|
(stub_dir / "gh-response").write_text(json.dumps({"workflow_runs": runs}))
|
|
inherited = {k: v for k, v in os.environ.items() if k != "BASH_ENV"}
|
|
env = {
|
|
**inherited,
|
|
"PATH": f"{stub_dir}{os.pathsep}{os.environ['PATH']}",
|
|
"STUB_DIR": str(stub_dir),
|
|
"GITHUB_REPOSITORY": "acme/widgets",
|
|
"GH_TOKEN": "unused",
|
|
"WORKFLOW_FILE": "entry.yml",
|
|
"TARGET_EVENT": "pull_request_target",
|
|
"PR_NUMBER": "1",
|
|
"PR_HEAD_SHA": HEAD_SHA,
|
|
"IMAGE_TAG": "ci-test",
|
|
"INFINITO_DISTROS": "debian",
|
|
"WAIT_ATTEMPTS": "100",
|
|
"WAIT_SLEEP_SECONDS": "0",
|
|
**overrides,
|
|
}
|
|
return subprocess.run(
|
|
["bash", str(SCRIPT)],
|
|
cwd=PROJECT_ROOT,
|
|
env=env,
|
|
capture_output=True,
|
|
text=True,
|
|
check=False,
|
|
)
|
|
|
|
|
|
class TestWaitForImages(unittest.TestCase):
|
|
def test_failed_lookup_does_not_end_the_wait(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, STUB_IMAGES_APPEAR="true")
|
|
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"Lookup of the privileged workflow run failed (1/30)", result.stdout
|
|
)
|
|
self.assertIn("All required CI images are available.", result.stdout)
|
|
|
|
def test_lookup_that_keeps_failing_ends_the_wait(self) -> None:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp)
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn("failed 30 times in a row", result.stderr)
|
|
|
|
def test_failed_privileged_run_of_a_fork_ends_the_wait(self) -> None:
|
|
fork_run = {
|
|
"id": 7,
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"created_at": "2026-10-04T12:41:18Z",
|
|
"head_sha": HEAD_SHA,
|
|
"display_title": RUN_TITLE,
|
|
"pull_requests": [],
|
|
}
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, runs=[fork_run])
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"Privileged run 7 finished with conclusion=failure", result.stderr
|
|
)
|
|
self.assertIn(f"head_sha={HEAD_SHA}", result.stderr)
|
|
|
|
def test_run_of_another_head_is_ignored(self) -> None:
|
|
other_run = {
|
|
"id": 8,
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"created_at": "2026-10-04T12:41:18Z",
|
|
"head_sha": "0" * 40,
|
|
"display_title": RUN_TITLE,
|
|
"pull_requests": [],
|
|
}
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, runs=[other_run], WAIT_ATTEMPTS="2")
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn(
|
|
"No matching privileged workflow run found yet", result.stdout
|
|
)
|
|
self.assertIn("Timed out waiting for CI images", result.stderr)
|
|
|
|
def test_run_of_another_pull_request_on_the_same_head_is_ignored(self) -> None:
|
|
own_run = {
|
|
"id": 7,
|
|
"status": "in_progress",
|
|
"conclusion": None,
|
|
"created_at": "2026-10-04T12:41:18Z",
|
|
"head_sha": HEAD_SHA,
|
|
"display_title": RUN_TITLE,
|
|
"pull_requests": [],
|
|
}
|
|
other_run = {
|
|
"id": 9,
|
|
"status": "completed",
|
|
"conclusion": "failure",
|
|
"created_at": "2026-10-04T12:45:00Z",
|
|
"head_sha": HEAD_SHA,
|
|
"display_title": "pull_request_target / PR #12 / synchronize / fork:branch",
|
|
"pull_requests": [],
|
|
}
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
result = wait_for_images(tmp, runs=[own_run, other_run], WAIT_ATTEMPTS="2")
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIn("Privileged run 7 ", result.stdout)
|
|
self.assertNotIn("Privileged run 9", result.stdout + result.stderr)
|
|
self.assertIn("Timed out waiting for CI images", result.stderr)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|