-
Notifications
You must be signed in to change notification settings - Fork 0
226 lines (211 loc) Β· 8.92 KB
/
Copy pathpr-integration-on-demand.yml
File metadata and controls
226 lines (211 loc) Β· 8.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
name: Integration tests on demand
# Manually triggered by commenting on an open PR with a slash-command on
# the FIRST line of the comment body (everything after the first line is
# ignored):
# /integrationtest β JDK 17 only
# /integrationtestfull β full matrix {17, 21, 25}
#
# The slash + first-line constraint prevents accidental triggers from
# review comments that mention the workflow by name, quoted replies
# (`> /integrationtest`), pasted documentation, or stack traces. The
# Guard job below filters on `startsWith(... '/integrationtest')` and
# then a strict bash `case` validates the exact command β anything that
# slips through is rejected before any live-API request is made.
#
# Integration tests hit the live Market Data API, so we don't run them
# automatically on every PR open/sync (saves API quota + CI minutes).
# They ARE required for merge β branch protection on `main` should list
# "Integration tests pass" as a required status check, which is the
# aggregator job below. PRs cannot merge until a reviewer comments one
# of the two slash-commands AND the resulting run is green.
#
# Important security note: workflows triggered by `issue_comment` always
# run from the *default branch's* version of the workflow file, not from
# the PR. Adding/changing this file on a feature branch has no effect
# until it lands on main.
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write # to react and post the result comment
# Multiple trigger comments on the same PR cancel earlier runs.
concurrency:
group: pr-integration-on-demand-${{ github.event.issue.number }}
cancel-in-progress: true
jobs:
guard:
name: Guard
runs-on: ubuntu-latest
# Only fire on PR comments whose body starts with `/integrationtest`.
# `startsWith` rejects comments that merely mention the command in
# passing (quoted replies start with `>`, prose with anything else,
# so they don't match). The strict `case` in the matrix step below
# rejects anything that slips through (e.g. `/integrationtest-foo`)
# before any live-API request fires.
if: |
github.event.issue.pull_request != null &&
startsWith(github.event.comment.body, '/integrationtest')
outputs:
head_sha: ${{ steps.pr.outputs.head_sha }}
jdks: ${{ steps.matrix.outputs.jdks }}
mode: ${{ steps.matrix.outputs.mode }}
steps:
- name: Verify commenter has write permission
uses: actions/github-script@v7
with:
script: |
const { data: perm } = await github.rest.repos.getCollaboratorPermissionLevel({
owner: context.repo.owner,
repo: context.repo.repo,
username: context.payload.comment.user.login,
});
const allowed = ['write', 'maintain', 'admin'].includes(perm.permission);
if (!allowed) {
core.setFailed(
`@${context.payload.comment.user.login} (${perm.permission}) ` +
`cannot trigger integration tests; write access required.`
);
}
- name: React π to the trigger comment
uses: actions/github-script@v7
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'eyes',
});
- name: Resolve PR head SHA
id: pr
uses: actions/github-script@v7
with:
script: |
const { data: pr } = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number,
});
if (pr.state !== 'open') {
core.setFailed(`PR #${pr.number} is ${pr.state}; refusing to run.`);
return;
}
core.setOutput('head_sha', pr.head.sha);
- name: Decide JDK matrix from comment body
id: matrix
env:
BODY: ${{ github.event.comment.body }}
run: |
# The if: filter above only guarantees the body starts with
# '/integrationtest'. We still need to disambiguate single vs
# full and reject anything that just shares the prefix
# (e.g. '/integrationtest-foo' or '/integrationtestlong').
# Match on the first line only β trailing context in the
# comment body is ignored.
first_line=$(printf '%s' "$BODY" | head -n 1 | tr -d '[:space:]')
case "$first_line" in
/integrationtest)
echo 'jdks=["17"]' >> "$GITHUB_OUTPUT"
echo 'mode=single' >> "$GITHUB_OUTPUT"
echo "Trigger: /integrationtest β JDK 17"
;;
/integrationtestfull)
echo 'jdks=["17","21","25"]' >> "$GITHUB_OUTPUT"
echo 'mode=full' >> "$GITHUB_OUTPUT"
echo "Trigger: /integrationtestfull β matrix {17, 21, 25}"
;;
*)
echo "::error::Unrecognized command on first line: '$first_line' (expected '/integrationtest' or '/integrationtestfull')"
exit 1
;;
esac
integration-tests:
name: Integration tests (JDK ${{ matrix.java }})
needs: guard
runs-on: ubuntu-latest
strategy:
# Don't cancel siblings: if JDK 21 fails, we still want 17 and 25
# results to surface.
fail-fast: false
matrix:
java: ${{ fromJSON(needs.guard.outputs.jdks) }}
steps:
# Check out exactly the PR's HEAD commit so we test the proposed
# change, not the merge ref.
- name: Checkout PR head
uses: actions/checkout@v4
with:
ref: ${{ needs.guard.outputs.head_sha }}
# Order matters: JDK 17 must be last so JAVA_HOME=17 (Gradle 8.12
# only supports JDKs up to 23 as its daemon runtime). The matrix
# JDK is still installed and registered as a toolchain target.
- name: Set up JDKs (test=${{ matrix.java }}, compile/daemon=17)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: |
${{ matrix.java }}
17
- name: Set up Gradle
uses: gradle/actions/setup-gradle@v4
- name: Run integration tests against live API
env:
MARKETDATA_TOKEN: ${{ secrets.MARKETDATA_TOKEN }}
MARKETDATA_RUN_INTEGRATION_TESTS: 'true'
run: |
if [ -z "$MARKETDATA_TOKEN" ]; then
echo "::error::MARKETDATA_TOKEN secret missing β cannot run integration tests."
exit 1
fi
./gradlew integrationTest -PtestJdk=${{ matrix.java }} --stacktrace
- name: Upload integration-test reports on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: integration-test-reports-jdk${{ matrix.java }}
path: |
build/reports/tests/integrationTest/
build/test-results/integrationTest/
retention-days: 14
# Aggregator job. Branch protection on `main` should require this
# check name ("Integration tests pass") so a single required check
# covers both `integrationtest` (matrix=[17]) and `integrationtestfull`
# (matrix=[17,21,25]) modes uniformly. Without this, branch protection
# would have to list the per-matrix-entry check names which only exist
# in the `full` mode.
required-check:
name: Integration tests pass
needs: [guard, integration-tests]
if: always() && needs.guard.result == 'success'
runs-on: ubuntu-latest
steps:
- name: Aggregate matrix outcome
env:
MATRIX_RESULT: ${{ needs.integration-tests.result }}
MODE: ${{ needs.guard.outputs.mode }}
run: |
echo "Mode: $MODE"
echo "Matrix outcome: $MATRIX_RESULT"
if [[ "$MATRIX_RESULT" != "success" ]]; then
echo "::error::One or more integration-test JDK entries failed."
exit 1
fi
echo "All integration tests passed."
- name: Comment outcome on the PR
if: always()
uses: actions/github-script@v7
with:
script: |
const ok = '${{ needs.integration-tests.result }}' === 'success';
const mode = '${{ needs.guard.outputs.mode }}';
const emoji = ok ? 'β
' : 'β';
const status = ok ? 'passed' : 'failed';
const matrix = mode === 'full' ? '`{17, 21, 25}`' : '`17`';
const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `${emoji} On-demand integration tests on JDK ${matrix} ${status}. [View run](${runUrl}).`,
});