Skip to content

Commit b28a8b9

Browse files
feat(ci): add test gating
1 parent 33da296 commit b28a8b9

7 files changed

Lines changed: 248 additions & 2 deletions

File tree

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# General: "test-robot-needed" and "test-robot-done" should be the default values of two inputs.
2+
# 1. It should check if "test-robot-needed" label is present.
3+
# 2. It should check if "test-robot-done" label is present.
4+
# 3. If "test-robot-needed" is present and "test-robot-done" is not present, it should fail with an error message saying "PR needs robot testing but is not marked as test-robot-done."
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
name: "Robot Test Label"
2+
description: "When the test-robot-done label is added, posts a comment confirming the robot test status."
3+
4+
inputs:
5+
label-test-robot-done:
6+
description: 'Label indicating the PR has been robot-tested'
7+
required: false
8+
default: 'test-robot-done'
9+
token:
10+
description: 'GitHub token or PAT used to post comments (defaults to github.token)'
11+
required: false
12+
default: ''
13+
14+
runs:
15+
using: "composite"
16+
steps:
17+
- name: Comment on label added
18+
shell: python3 {0}
19+
env:
20+
GH_TOKEN: ${{ inputs.token || github.token }}
21+
PR_NUMBER: ${{ github.event.pull_request.number }}
22+
REPO: ${{ github.repository }}
23+
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
24+
LABEL_DONE: ${{ inputs.label-test-robot-done }}
25+
run: |
26+
import os, json, sys, urllib.request, urllib.error
27+
28+
token = os.environ['GH_TOKEN']
29+
repo = os.environ['REPO']
30+
pr = os.environ['PR_NUMBER']
31+
sha = os.environ.get('COMMIT_SHA', '').strip()
32+
label = os.environ['LABEL_DONE']
33+
34+
headers = {
35+
'Authorization': f'Bearer {token}',
36+
'Accept': 'application/vnd.github+json',
37+
'X-GitHub-Api-Version': '2022-11-28',
38+
'Content-Type': 'application/json',
39+
}
40+
41+
def gh(method, path, body=None):
42+
req = urllib.request.Request(
43+
f'https://api.github.com{path}',
44+
data=json.dumps(body).encode() if body else None,
45+
headers=headers,
46+
method=method,
47+
)
48+
try:
49+
with urllib.request.urlopen(req) as r:
50+
return json.loads(r.read())
51+
except urllib.error.HTTPError as e:
52+
print(f'GitHub API error {e.code}: {e.read().decode()}', file=sys.stderr)
53+
sys.exit(1)
54+
55+
labels = {l['name'] for l in gh('GET', f'/repos/{repo}/issues/{pr}/labels')}
56+
57+
if label not in labels:
58+
print(f'Label `{label}` not found — nothing to do.')
59+
sys.exit(0)
60+
61+
sha_line = f'\n**Commit:** `{sha}`' if sha else ''
62+
body = (
63+
f'### \u2705 Robot Test Complete\n\n'
64+
f'This PR has been marked `{label}`, confirming robot testing has passed.{sha_line}'
65+
)
66+
gh('POST', f'/repos/{repo}/issues/{pr}/comments', {'body': body})
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: "Robot Test Unlabel"
2+
description: "On new commits, removes the test-robot-done label if present and posts a comment."
3+
4+
inputs:
5+
label-test-robot-needed:
6+
description: 'Label indicating the PR needs robot testing'
7+
required: false
8+
default: 'test-robot-needed'
9+
label-test-robot-done:
10+
description: 'Label indicating the PR has been robot-tested'
11+
required: false
12+
default: 'test-robot-done'
13+
token:
14+
description: 'GitHub token or PAT used to remove labels and post comments (defaults to github.token)'
15+
required: false
16+
default: ''
17+
18+
runs:
19+
using: "composite"
20+
steps:
21+
- name: Remove label and comment
22+
shell: python3 {0}
23+
env:
24+
GH_TOKEN: ${{ inputs.token || github.token }}
25+
PR_NUMBER: ${{ github.event.pull_request.number }}
26+
REPO: ${{ github.repository }}
27+
COMMIT_SHA: ${{ github.event.pull_request.head.sha }}
28+
LABEL_DONE: ${{ inputs.label-test-robot-done }}
29+
LABEL_NEEDED: ${{ inputs.label-test-robot-needed }}
30+
run: |
31+
import os, json, sys, urllib.request, urllib.error, urllib.parse
32+
33+
token = os.environ['GH_TOKEN']
34+
repo = os.environ['REPO']
35+
pr = os.environ['PR_NUMBER']
36+
sha = os.environ.get('COMMIT_SHA', '').strip()
37+
label_done = os.environ['LABEL_DONE']
38+
label_needed = os.environ['LABEL_NEEDED']
39+
40+
headers = {
41+
'Authorization': f'Bearer {token}',
42+
'Accept': 'application/vnd.github+json',
43+
'X-GitHub-Api-Version': '2022-11-28',
44+
'Content-Type': 'application/json',
45+
}
46+
47+
def gh(method, path, body=None):
48+
req = urllib.request.Request(
49+
f'https://api.github.com{path}',
50+
data=json.dumps(body).encode() if body else None,
51+
headers=headers,
52+
method=method,
53+
)
54+
try:
55+
with urllib.request.urlopen(req) as r:
56+
content = r.read()
57+
return json.loads(content) if content else None
58+
except urllib.error.HTTPError as e:
59+
print(f'GitHub API error {e.code}: {e.read().decode()}', file=sys.stderr)
60+
sys.exit(1)
61+
62+
labels = {l['name'] for l in gh('GET', f'/repos/{repo}/issues/{pr}/labels')}
63+
64+
if label_done not in labels:
65+
print(f'Label `{label_done}` not present — nothing to do.')
66+
sys.exit(0)
67+
68+
safe_label = urllib.parse.quote(label_done, safe='')
69+
gh('DELETE', f'/repos/{repo}/issues/{pr}/labels/{safe_label}')
70+
71+
sha_line = f'\n**Commit:** `{sha}`' if sha else ''
72+
warning = (
73+
f'\n\n> [!WARNING]\n'
74+
f'> `{label_done}` was removed but `{label_needed}` was not present —'
75+
f' this PR may not be queued for robot testing.'
76+
) if label_needed not in labels else ''
77+
78+
body = (
79+
f'### 🔄 Robot Test Label Removed\n\n'
80+
f'`{label_done}` has been removed due to new commits pushed to this PR.{sha_line}'
81+
f'{warning}'
82+
)
83+
gh('POST', f'/repos/{repo}/issues/{pr}/comments', {'body': body})
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: PR - Label
2+
3+
on:
4+
pull_request:
5+
types: [labeled]
6+
7+
concurrency:
8+
group: ${{ github.workflow }}-${{ github.ref }}
9+
cancel-in-progress: true
10+
11+
permissions:
12+
contents: read
13+
pull-requests: write
14+
15+
jobs:
16+
test-robot-label:
17+
if: github.event.label.name == 'test-robot-done'
18+
name: Job
19+
uses: ./.github/workflows/test-robot-label.yaml
20+
with:
21+
label-test-robot-done: test-robot-done

.github/workflows/pull-request.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,19 @@ concurrency:
1111
permissions:
1212
contents: read
1313
packages: write
14+
pull-requests: write
1415

1516
jobs:
16-
build:
17+
test-robot-unlabel:
18+
if: github.event.action == 'synchronize'
1719
name: Job
18-
uses: ./.github/workflows/build.yaml
20+
uses: ./.github/workflows/test-robot-unlabel.yaml
21+
with:
22+
label-test-robot-done: test-robot-done
23+
label-test-robot-needed: test-robot-needed
24+
25+
# build:
26+
# needs: test-robot-unlabel
27+
# if: always() && needs.test-robot-unlabel.result != 'failure'
28+
# name: Job
29+
# uses: ./.github/workflows/build.yaml
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Robot Test Label
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
label-test-robot-done:
7+
description: 'Label name indicating the PR has been test-robot-done'
8+
type: string
9+
required: false
10+
default: 'test-robot-done'
11+
12+
permissions:
13+
contents: read
14+
pull-requests: write
15+
16+
jobs:
17+
test-robot-label:
18+
if: github.event.action == 'labeled' &&
19+
github.event.label.name == inputs['label-test-robot-done']
20+
name: Robot Test Label
21+
runs-on: ubuntu-latest
22+
steps:
23+
- uses: actions/checkout@v4
24+
25+
- name: Robot Test Label
26+
uses: ./.github/actions/test-robot-label
27+
with:
28+
label-test-robot-done: ${{ inputs.label-test-robot-done }}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
name: Robot Test Unlabel
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
label-test-robot-done:
7+
description: 'Label name indicating the PR has been test-robot-done'
8+
type: string
9+
required: false
10+
default: 'test-robot-done'
11+
label-test-robot-needed:
12+
description: 'Label name indicating the PR needs robot testing'
13+
type: string
14+
required: false
15+
default: 'test-robot-needed'
16+
17+
permissions:
18+
contents: read
19+
pull-requests: write
20+
21+
jobs:
22+
test-robot-unlabel:
23+
if: contains(github.event.pull_request.labels.*.name, inputs.label-test-robot-done)
24+
name: Robot Test Unlabel
25+
runs-on: ubuntu-latest
26+
steps:
27+
- uses: actions/checkout@v4
28+
29+
- name: Robot Test Unlabel
30+
uses: ./.github/actions/test-robot-unlabel
31+
with:
32+
label-test-robot-done: ${{ inputs.label-test-robot-done }}
33+
label-test-robot-needed: ${{ inputs.label-test-robot-needed }}

0 commit comments

Comments
 (0)