[CI] Improve comment-triggered authorization and retries (#50414)
Signed-off-by: khluu <khluu000@gmail.com> Co-authored-by: OpenAI Codex <noreply@openai.com>
This commit is contained in:
co-authored by
OpenAI Codex
parent
e04a30a77c
commit
48a077e4cf
@@ -6,6 +6,7 @@ import unittest
|
||||
from typing import Any
|
||||
|
||||
from run_ci_command import (
|
||||
CI_AUTHORIZED_COMMENT_MARKER,
|
||||
COMMAND_RETRY_FAILED,
|
||||
COMMAND_RUN_CI,
|
||||
RETRY_STATES,
|
||||
@@ -15,6 +16,7 @@ from run_ci_command import (
|
||||
has_trusted_approval,
|
||||
is_active_build,
|
||||
is_build_for_pr,
|
||||
notify_authorized,
|
||||
parse_command,
|
||||
parse_trusted_users,
|
||||
run,
|
||||
@@ -63,8 +65,9 @@ class FakeGitHub:
|
||||
pr: dict[str, Any] | None = None,
|
||||
review_decision: str = "REVIEW_REQUIRED",
|
||||
reviews: list[dict[str, Any]] | None = None,
|
||||
comments: list[str] | None = None,
|
||||
) -> None:
|
||||
self.comments: list[str] = []
|
||||
self.comments = comments or []
|
||||
self.permission = permission
|
||||
self.permissions = permissions or {}
|
||||
self.pr = pr or make_pr()
|
||||
@@ -84,6 +87,15 @@ class FakeGitHub:
|
||||
def list_reviews(self, number: int) -> list[dict[str, Any]]:
|
||||
return self.reviews
|
||||
|
||||
def list_issue_comments(self, number: int) -> list[dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"body": body,
|
||||
"user": {"login": "github-actions[bot]"},
|
||||
}
|
||||
for body in self.comments
|
||||
]
|
||||
|
||||
def list_reactions(self, comment_id: int) -> list[dict[str, Any]]:
|
||||
return []
|
||||
|
||||
@@ -303,7 +315,7 @@ class RunCiCommandTest(unittest.TestCase):
|
||||
},
|
||||
)
|
||||
|
||||
def test_ci_run_dispatches_build_with_current_pr_metadata(self) -> None:
|
||||
def test_write_reviewer_runs_ci_without_delegation(self) -> None:
|
||||
github = FakeGitHub()
|
||||
buildkite = FakeBuildkite([[], []])
|
||||
run(make_event(COMMAND_RUN_CI), github, buildkite)
|
||||
@@ -314,6 +326,7 @@ class RunCiCommandTest(unittest.TestCase):
|
||||
"PR #42 /ci run by @reviewer",
|
||||
)
|
||||
self.assertEqual(github.reactions, ["eyes", "rocket"])
|
||||
self.assertTrue(github.comments[0].startswith("✅ "))
|
||||
self.assertIn("Buildkite CI #123", github.comments[0])
|
||||
|
||||
def test_unapproved_authors_are_denied_without_buildkite(self) -> None:
|
||||
@@ -326,10 +339,171 @@ class RunCiCommandTest(unittest.TestCase):
|
||||
run(make_event(COMMAND_RUN_CI, "author"), github, buildkite)
|
||||
|
||||
self.assertEqual(buildkite.list_calls, [])
|
||||
self.assertEqual(github.reactions, ["eyes", "-1"])
|
||||
self.assertEqual(github.reactions, ["eyes"])
|
||||
self.assertTrue(github.comments[0].startswith("❌ "))
|
||||
self.assertIn("approve the PR", github.comments[0])
|
||||
|
||||
def test_ci_retry_uses_latest_current_sha_build(self) -> None:
|
||||
run(make_event(COMMAND_RUN_CI, "author"), github, buildkite)
|
||||
self.assertEqual(len(github.comments), 1)
|
||||
|
||||
def test_untrusted_approval_cannot_launch_ci(self) -> None:
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
pr=make_pr(),
|
||||
review_decision="APPROVED",
|
||||
reviews=[
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "untrusted-reviewer"},
|
||||
}
|
||||
],
|
||||
)
|
||||
buildkite = FakeBuildkite()
|
||||
|
||||
run(make_event(COMMAND_RUN_CI, "author"), github, buildkite)
|
||||
|
||||
self.assertEqual(buildkite.list_calls, [])
|
||||
self.assertEqual(github.reactions, ["eyes"])
|
||||
self.assertTrue(github.comments[0].startswith("❌ "))
|
||||
|
||||
def test_ready_label_notifies_author_once(self) -> None:
|
||||
pr = make_pr(labels=[{"name": "ready"}])
|
||||
event = {
|
||||
"action": "labeled",
|
||||
"label": {"name": "ready"},
|
||||
"pull_request": pr,
|
||||
}
|
||||
github = FakeGitHub(permission="read", pr=pr)
|
||||
|
||||
notify_authorized(event, github)
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(len(github.comments), 1)
|
||||
self.assertTrue(github.comments[0].startswith("✅ @author"))
|
||||
self.assertIn("`/ci run`", github.comments[0])
|
||||
self.assertIn("`/ci retry`", github.comments[0])
|
||||
self.assertIn(CI_AUTHORIZED_COMMENT_MARKER, github.comments[0])
|
||||
|
||||
def test_ready_label_does_not_notify_after_trusted_approval(self) -> None:
|
||||
pr = make_pr(labels=[{"name": "ready"}])
|
||||
event = {
|
||||
"action": "labeled",
|
||||
"label": {"name": "ready"},
|
||||
"pull_request": pr,
|
||||
}
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
permissions={"reviewer": "write"},
|
||||
pr=pr,
|
||||
review_decision="APPROVED",
|
||||
reviews=[
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "reviewer"},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(github.comments, [])
|
||||
|
||||
def test_trusted_approval_notifies_author_once(self) -> None:
|
||||
event = {
|
||||
"action": "submitted",
|
||||
"pull_request": make_pr(),
|
||||
"review": {"state": "approved"},
|
||||
}
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
permissions={"reviewer": "write"},
|
||||
review_decision="APPROVED",
|
||||
reviews=[
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "reviewer"},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
notify_authorized(event, github)
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(len(github.comments), 1)
|
||||
self.assertIn("@author", github.comments[0])
|
||||
|
||||
def test_approval_does_not_notify_when_ready_label_exists(self) -> None:
|
||||
pr = make_pr(labels=[{"name": "ready"}])
|
||||
event = {
|
||||
"action": "submitted",
|
||||
"pull_request": pr,
|
||||
"review": {"state": "approved"},
|
||||
}
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
permissions={"reviewer": "write"},
|
||||
pr=pr,
|
||||
review_decision="APPROVED",
|
||||
reviews=[
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "reviewer"},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(github.comments, [])
|
||||
|
||||
def test_untrusted_approval_does_not_notify_author(self) -> None:
|
||||
event = {
|
||||
"action": "submitted",
|
||||
"pull_request": make_pr(),
|
||||
"review": {"state": "approved"},
|
||||
}
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
review_decision="APPROVED",
|
||||
reviews=[
|
||||
{
|
||||
"state": "APPROVED",
|
||||
"user": {"login": "reviewer"},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(github.comments, [])
|
||||
|
||||
def test_notification_skips_authors_who_already_have_write(self) -> None:
|
||||
pr = make_pr(labels=[{"name": "ready"}])
|
||||
event = {
|
||||
"action": "labeled",
|
||||
"label": {"name": "ready"},
|
||||
"pull_request": pr,
|
||||
}
|
||||
github = FakeGitHub(permission="write", pr=pr)
|
||||
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(github.comments, [])
|
||||
|
||||
def test_notification_skips_draft_prs(self) -> None:
|
||||
pr = make_pr(draft=True, labels=[{"name": "ready"}])
|
||||
event = {
|
||||
"action": "labeled",
|
||||
"label": {"name": "ready"},
|
||||
"pull_request": pr,
|
||||
}
|
||||
github = FakeGitHub(permission="read", pr=pr)
|
||||
|
||||
notify_authorized(event, github)
|
||||
|
||||
self.assertEqual(github.comments, [])
|
||||
|
||||
def test_ci_retry_retries_failed_jobs_while_build_is_running(self) -> None:
|
||||
github = FakeGitHub(
|
||||
permission="read",
|
||||
pr=make_pr(labels=[{"name": "ready"}]),
|
||||
@@ -339,10 +513,9 @@ class RunCiCommandTest(unittest.TestCase):
|
||||
[
|
||||
{
|
||||
"created_at": "2026-07-28T01:00:00Z",
|
||||
"finished_at": "2026-07-28T02:00:00Z",
|
||||
"number": 123,
|
||||
"pull_request": {"id": 42},
|
||||
"state": "failed",
|
||||
"state": "failing",
|
||||
"web_url": "https://buildkite.example/builds/123",
|
||||
}
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user