Skip to content

Commit c82351f

Browse files
authored
fix(ci): accept unbound /test e2e comments and post replies (#7267)
Signed-off-by: Zujian Zhang <zhangzujian.7@gmail.com>
1 parent e7cd361 commit c82351f

3 files changed

Lines changed: 64 additions & 9 deletions

File tree

.github/workflows/x86-e2e-dispatcher.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ permissions:
2828
contents: read
2929
actions: write
3030
checks: read
31-
pull-requests: read
31+
pull-requests: write
3232
issues: write
3333

3434
jobs:
@@ -393,6 +393,11 @@ jobs:
393393
github.event.issue.pull_request &&
394394
(startsWith(github.event.comment.body, '/test e2e') ||
395395
startsWith(github.event.comment.body, '/retest e2e-failed'))
396+
permissions:
397+
actions: write
398+
contents: read
399+
issues: write
400+
pull-requests: write
396401
outputs:
397402
accepted: ${{ steps.decision.outputs.accepted }}
398403
action: ${{ steps.decision.outputs.action }}

hack/e2e_control.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,34 @@ def parseCommand(body):
5858
rf"/test e2e ({groupPattern}(?:,{groupPattern})*){binding}",
5959
body,
6060
)
61+
if match:
62+
return {
63+
"action": "dispatch",
64+
"requestedGroups": sorted(set(match.group(1).split(","))),
65+
"full": False,
66+
"headSHA": match.group(2),
67+
"nonce": match.group(3),
68+
}
69+
unbound = {
70+
"/test e2e": {"action": "dispatch", "requestedGroups": [], "full": False},
71+
"/test e2e-all": {"action": "dispatch", "requestedGroups": [], "full": True},
72+
"/retest e2e-failed": {
73+
"action": "rerun-failed",
74+
"requestedGroups": [],
75+
"full": False,
76+
},
77+
}
78+
if body in unbound:
79+
return {**unbound[body], "headSHA": "", "nonce": ""}
80+
match = re.fullmatch(rf"/test e2e ({groupPattern}(?:,{groupPattern})*)", body)
6181
if not match:
6282
raise ValueError("invalid E2E command")
6383
return {
6484
"action": "dispatch",
6585
"requestedGroups": sorted(set(match.group(1).split(","))),
6686
"full": False,
67-
"headSHA": match.group(2),
68-
"nonce": match.group(3),
87+
"headSHA": "",
88+
"nonce": "",
6989
}
7090

7191

@@ -151,11 +171,13 @@ def decideDispatch(
151171
if not re.fullmatch(r"[0-9a-f]{40}", baseSHA or ""):
152172
return rejectedDecision(command, "pull request base revision is invalid")
153173
prNumber = pullRequest.get("number")
154-
if command["headSHA"] != headSHA:
155-
return rejectedDecision(command, "comment is bound to another pull request HEAD")
156174
expectedNonce = requestNonce(prNumber, headSHA, baseSHA, catalogRevision)
157-
if command["nonce"] != expectedNonce:
158-
return rejectedDecision(command, "comment binding nonce is stale or invalid")
175+
if command.get("headSHA") or command.get("nonce"):
176+
if command["headSHA"] != headSHA:
177+
return rejectedDecision(command, "comment is bound to another pull request HEAD")
178+
if command["nonce"] != expectedNonce:
179+
return rejectedDecision(command, "comment binding nonce is stale or invalid")
180+
command = {**command, "headSHA": headSHA, "nonce": expectedNonce}
159181
approvalGeneration = event.get("comment", {}).get("id")
160182
if not isinstance(approvalGeneration, int) or approvalGeneration <= 0:
161183
return rejectedDecision(command, "comment identifier is invalid")

hack/test_e2e_control.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,25 @@ def testParsesSupportedCommentCommands(self):
4444
self.assertEqual(command["headSHA"], headSHA)
4545
self.assertEqual(command["nonce"], nonce)
4646

47+
def testParsesUnboundCommentCommands(self):
48+
cases = {
49+
"/test e2e": ("dispatch", [], False),
50+
"/test e2e core": ("dispatch", ["core"], False),
51+
"/test e2e-all": ("dispatch", [], True),
52+
"/retest e2e-failed": ("rerun-failed", [], False),
53+
}
54+
for body, expected in cases.items():
55+
with self.subTest(body=body):
56+
command = e2eControl.parseCommand(body)
57+
self.assertEqual(
58+
(command["action"], command["requestedGroups"], command["full"]),
59+
expected,
60+
)
61+
self.assertEqual(command["headSHA"], "")
62+
self.assertEqual(command["nonce"], "")
63+
4764
def testRejectsMalformedOrInjectedCommands(self):
4865
for body in [
49-
"/test e2e",
5066
"/test e2e policy bogus",
5167
"/test e2e policy;echo-owned",
5268
"/test e2e policy\n/test e2e-all",
@@ -69,14 +85,15 @@ def dispatchDecision(
6985
state="open",
7086
baseRef="master",
7187
controlledLabels=(),
88+
bindHead=True,
7289
):
7390
catalog = json.loads((repoRoot / ".github/e2e-selection.json").read_text())
7491
catalogRevision = e2eSelector.catalogRevision(catalog)
7592
baseSHA = "b" * 40
7693
if body is None:
7794
nonce = e2eControl.requestNonce(7231, observedHeadSHA, baseSHA, catalogRevision)
7895
body = f"/test e2e policy --head {observedHeadSHA} --nonce {nonce}"
79-
elif " --head " not in body:
96+
elif bindHead and " --head " not in body:
8097
nonce = e2eControl.requestNonce(7231, observedHeadSHA, baseSHA, catalogRevision)
8198
body = f"{body} --head {observedHeadSHA} --nonce {nonce}"
8299
event = {
@@ -102,6 +119,16 @@ def dispatchDecision(
102119
controlledLabels=controlledLabels,
103120
)
104121

122+
def testUnboundCommandBindsToLiveHead(self):
123+
decision = self.dispatchDecision(body="/test e2e core", bindHead=False)
124+
125+
self.assertTrue(decision["accepted"])
126+
self.assertEqual(decision["action"], "dispatch")
127+
self.assertEqual(decision["requestedGroups"], ["core"])
128+
self.assertEqual(decision["headSHA"], "a" * 40)
129+
self.assertEqual(decision["baseSHA"], "b" * 40)
130+
self.assertRegex(decision["nonce"], r"^[0-9a-f]{16}$")
131+
105132
def testAuthorizedCommandIsBoundToCurrentHead(self):
106133
decision = self.dispatchDecision()
107134

@@ -842,6 +869,7 @@ def testDispatcherWorkflowUsesOnlyTrustedWritePermissions(self):
842869
self.assertIn("checks: read", workflow)
843870
self.assertNotIn("checks: write", workflow)
844871
self.assertIn("pull-requests: read", workflow)
872+
self.assertIn("pull-requests: write", workflow)
845873
self.assertIn("issues: write", workflow)
846874
self.assertIn("--catalog trusted-catalog.json", workflow)
847875
self.assertIn("--live-comment-file live-comment.json", workflow)

0 commit comments

Comments
 (0)