Skip to content

Commit 2c22d8a

Browse files
authored
fix(reuse): accept a pinned cancelled sweep run as a reuse source (#2389)
A fail-fast sweep cancels its remaining jobs, so a run whose benchmark jobs all passed still concludes cancelled once a later job is cut short. The pinned path already allows an incomplete source run because ingestion skips rows without results, but it only permitted success and failure, so those sweeps could not be pinned at all.
1 parent a11302d commit 2c22d8a

2 files changed

Lines changed: 64 additions & 2 deletions

File tree

utils/find_reusable_sweep_run.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,9 +244,18 @@ def validate_reusable_run(
244244
raise RuntimeError(f"Reusable source run {run_id} is not a pull_request run.")
245245
if run.get("status") != "completed":
246246
raise RuntimeError(f"Reusable source run {run_id} is not completed.")
247-
allowed_conclusions = {"success", "failure"} if allow_failed else {"success"}
247+
# A pinned run is an explicit maintainer choice, so incomplete sweeps are
248+
# allowed: ingestion skips rows without results, leaving only the completed
249+
# points. ``cancelled`` belongs here alongside ``failure`` because a
250+
# fail-fast sweep cancels its remaining jobs, so a run whose benchmark jobs
251+
# all passed still concludes ``cancelled`` when a later job is cut short.
252+
allowed_conclusions = (
253+
{"success", "failure", "cancelled"} if allow_failed else {"success"}
254+
)
248255
if run.get("conclusion") not in allowed_conclusions:
249-
expected = "success or failure" if allow_failed else "success"
256+
expected = (
257+
"success, failure, or cancelled" if allow_failed else "success"
258+
)
250259
raise RuntimeError(
251260
f"Reusable source run {run_id} has conclusion {run.get('conclusion')!r}; "
252261
f"expected {expected}."

utils/test_find_reusable_sweep_run.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,59 @@ def test_validate_reusable_run_rejects_failed_run_by_default(monkeypatch) -> Non
413413
raise AssertionError("expected an unpinned failed run to be rejected")
414414

415415

416+
def test_validate_reusable_run_accepts_cancelled_run_when_explicitly_allowed(
417+
monkeypatch,
418+
) -> None:
419+
"""A fail-fast sweep concludes ``cancelled`` once a job is cut short.
420+
421+
Its completed benchmark jobs still uploaded usable artifacts, so a pinned
422+
``cancelled`` run is reusable on the same terms as a pinned failure.
423+
"""
424+
monkeypatch.setattr(reuse, "artifact_names", lambda *args: {"results_bmk"})
425+
monkeypatch.setattr(reuse, "pr_commit_shas", lambda *args: {"abc123"})
426+
427+
reuse.validate_reusable_run(
428+
"SemiAnalysisAI/InferenceX",
429+
"run-sweep.yml",
430+
1321,
431+
{
432+
"id": 25763404168,
433+
"event": "pull_request",
434+
"status": "completed",
435+
"conclusion": "cancelled",
436+
"path": ".github/workflows/run-sweep.yml",
437+
"head_sha": "abc123",
438+
},
439+
"token",
440+
allow_failed=True,
441+
)
442+
443+
444+
def test_validate_reusable_run_rejects_cancelled_run_by_default(monkeypatch) -> None:
445+
monkeypatch.setattr(reuse, "artifact_names", lambda *args: {"results_bmk"})
446+
monkeypatch.setattr(reuse, "pr_commit_shas", lambda *args: {"abc123"})
447+
448+
try:
449+
reuse.validate_reusable_run(
450+
"SemiAnalysisAI/InferenceX",
451+
"run-sweep.yml",
452+
1321,
453+
{
454+
"id": 25763404168,
455+
"event": "pull_request",
456+
"status": "completed",
457+
"conclusion": "cancelled",
458+
"path": ".github/workflows/run-sweep.yml",
459+
"head_sha": "abc123",
460+
},
461+
"token",
462+
)
463+
except RuntimeError as error:
464+
assert "expected success" in str(error)
465+
else:
466+
raise AssertionError("expected an unpinned cancelled run to be rejected")
467+
468+
416469
def test_validate_reusable_run_accepts_run_for_older_pr_commit(monkeypatch) -> None:
417470
"""Regression: pinned run survives an additional commit landing on the PR.
418471

0 commit comments

Comments
 (0)