-
Notifications
You must be signed in to change notification settings - Fork 17
257 lines (244 loc) · 12.4 KB
/
Copy pathin-world-tests.yml
File metadata and controls
257 lines (244 loc) · 12.4 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
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
name: In-World Tests
# Dispatcher for the InWorld NUnit suite. The mechanics — Explorer install,
# AltTester, `mf explorer test`, Allure upload, PR comment — live in
# decentraland/explorer-automation's `run-inworld-suite.yml`. This file only
# decides when to run and against which build, and shares its build resolution
# with visual-regression.yml via .github/actions/resolve-explorer-build.
#
# Triggers:
# - Every PR into main from a `release/**` or `hotfix/**` branch. `pull_request`
# and not `workflow_run` on the build, because a workflow_run reports against
# the default branch's SHA: it never joins the PR's checks and so can never
# gate a merge. Here the check is on the PR from the moment it opens, and the
# first job waits out the ~40-minute Unity Cloud Build the same event started.
# - Manually, from the Actions tab, against any ref.
#
# To make a red suite block the merge, add `InWorld suite result` to main's
# required status checks. That job is the gate: it is the only name worth
# requiring, it stays stable when the reusable workflow's own job names change,
# and it passes for PRs into main that are not release or hotfix.
#
# Release PRs are opened by create-release-branch.yml with GITHUB_TOKEN, and
# events that token raises start no workflow runs — so that workflow dispatches
# this one itself. A dispatch attaches its checks to the dispatched ref's tip,
# which is the PR head, so the gate still lands on the PR.
#
# Required vars:
# - vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL Artifact bucket, and where the
# Allure report is published
#
# Inherited by the reusable workflow via `secrets: inherit`:
# - secrets.ALTTESTER_LICENSE
# - secrets.REPOS_READ_ONLY_TOKEN
# - secrets.EXPLORER_TEAM_S3_BUCKET
# - secrets.EXPLORER_TEAM_AWS_DEFAULT_REGION
# - secrets.EXPLORER_TEAM_AWS_ACCESS_KEY_ID
# - secrets.EXPLORER_TEAM_AWS_SECRET_ACCESS_KEY
on:
pull_request:
branches: [main]
types: [opened, reopened, synchronize, ready_for_review]
workflow_dispatch:
inputs:
filter:
description: 'NUnit filter. Empty runs the whole Category=InWorld suite, split across Windows runners; anything narrower runs on one.'
required: false
default: ''
type: string
build_url:
description: 'Artifact URL to test. Empty resolves the selected ref''s own Unity Cloud Build.'
required: false
default: ''
type: string
tests_ref:
description: 'explorer-automation ref for the tests. Empty matches this ref''s name there, else main.'
required: false
default: ''
type: string
record_perf:
description: 'Record fixture-level perf samples. Off by default — paravirt numbers are unreliable.'
required: false
default: false
type: boolean
# A newer commit supersedes the run in flight: the question is whether the branch
# is good now, and the newer commit contains the older one.
#
# Keyed on the branch, not the PR number, because both triggers have to land in
# one group: create-release-branch.yml dispatches this workflow against the same
# release branch a `pull_request` run also covers, and a PR-number key would put
# the two in groups that can never cancel each other — two gates on one commit,
# four shards on the self-hosted GPU pool, and whichever finishes last owning the
# required check. `github.head_ref` is the source branch on `pull_request` (where
# `ref_name` is the merge ref) and empty everywhere else, so the fallback is what
# a dispatch and a manual run against any ref key on.
#
# Keyed on the head repo as well, because a branch name is not ours to reserve:
# a fork PR into main may name its branch `release/2026-08-10` too, and
# `cancel-in-progress` is applied when the run is created — before any job's
# `if:` is evaluated, so the fork exclusion in `resolve`'s own `if:` (:113)
# only skips the jobs of a run that has already cancelled the real gate.
# Without this segment an outsider could red a release's required check at
# will, by guessing a name that release cuts make predictable.
# `head.repo.full_name` is the head repo on `pull_request` — ours on a same-repo
# PR, `attacker/whatever` on a fork — and absent on `workflow_dispatch`, whose
# payload carries only `inputs`, `ref` and `workflow`; the fallback therefore
# resolves to the same string a same-repo PR produces, which is what keeps the
# two triggers in one group. A fork cannot collide with us: everything before
# the first `/` is the head repo's owner, and only we can be `decentraland`.
concurrency:
group: in-world-tests-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true
jobs:
resolve:
name: Wait for the build
# Skipped rather than filtered at the trigger, so the gate job below still
# reports on every PR into main and a required check never hangs unreported.
#
# Drafts are excluded to match build-unitycloud.yml, which does not build
# them without a `force-build` label — waiting for a build that will never
# start just turns into a timeout. `ready_for_review` above picks the PR up
# the moment it leaves draft.
#
# Fork PRs are excluded because they get no secrets, and the suite needs the
# AltTester licence. Release and hotfix branches never come from a fork.
if: >-
github.event_name == 'workflow_dispatch' ||
((startsWith(github.head_ref, 'release/') || startsWith(github.head_ref, 'hotfix/')) &&
github.event.pull_request.draft == false &&
github.event.pull_request.head.repo.full_name == github.repository)
runs-on: ubuntu-latest
# Covers the wait below plus the artifact check. Builds measure ~40m.
timeout-minutes: 100
# `actions: read` for the build-unitycloud lookup, `contents: read` for the
# sparse checkout, `pull-requests: read` for the `gh pr list` below. Read
# access to public data survives whatever this block says while the repo is
# public, but the grant should not hinge on that. Nothing here writes.
permissions:
actions: read
contents: read
pull-requests: read
env:
HEAD_REF: ${{ github.head_ref || github.ref_name }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}
outputs:
build_url: ${{ steps.resolve.outputs.build-url }}
windows_build_url: ${{ steps.resolve.outputs.windows-build-url }}
commit_sha: ${{ steps.resolve.outputs.short-sha }}
tests_ref: ${{ inputs.tests_ref || steps.resolve.outputs.tests-ref }}
branch_label: ${{ github.head_ref || github.ref_name }}
pr_number: ${{ github.event.pull_request.number || steps.pr.outputs.number }}
steps:
# `uses: ./…` resolves against $GITHUB_WORKSPACE, so the action has to be
# on disk. Sparse, because the full tree is ~21k files and none of the
# rest is read here.
- name: Fetch the resolver action
uses: actions/checkout@v6
with:
sparse-checkout: .github/actions/resolve-explorer-build
- name: Resolve build URL and tests ref
id: resolve
uses: ./.github/actions/resolve-explorer-build
with:
head-ref: ${{ env.HEAD_REF }}
head-sha: ${{ env.HEAD_SHA }}
# A PR opens before its build exists, so wait one out. Manual runs
# pick a ref whose build is already there — or say which to use.
wait-minutes: ${{ github.event_name == 'pull_request' && 90 || 0 }}
# Both suites run, so both zips have to exist before either starts.
build-legs: macos,windows64
build-url-override: ${{ inputs.build_url }}
public-url-prefix: ${{ vars.EXPLORER_TEAM_S3_BUCKET_PUBLIC_URL }}
github-token: ${{ secrets.GITHUB_TOKEN }}
automation-token: ${{ secrets.REPOS_READ_ONLY_TOKEN }}
# A dispatched run carries no PR, and the release gate is started that way
# — create-release-branch.yml dispatches it, because the PR it opens with
# GITHUB_TOKEN raises no events. Without this the result reaches only the
# job summary, on the one path where the PR is the whole point.
# `--head` matches on branch name alone, hence the fork exclusion.
- name: Find the branch's open PR
id: pr
if: github.event_name == 'workflow_dispatch'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
NUMBER=$(gh pr list --repo "$GITHUB_REPOSITORY" --head "$HEAD_REF" --state open \
--json number,isCrossRepository \
--jq '[.[] | select(.isCrossRepository == false)] | .[0].number // ""')
if [ -n "$NUMBER" ]; then
echo "::notice::Results will be commented on PR #${NUMBER}."
fi
echo "number=${NUMBER}" >> "$GITHUB_OUTPUT"
run-suite:
name: InWorld suite
needs: resolve
# @main pins the merged version of the reusable, so PRs to
# explorer-automation that touch it cannot change release validation here.
uses: decentraland/explorer-automation/.github/workflows/run-inworld-suite.yml@main
# Must be a superset of run-inworld-suite.yml's own `permissions:`, or the
# call dies at startup. The write is the PR comment it posts; this is the
# only job that needs one, hence per job rather than at the top of the file.
permissions:
contents: read
pull-requests: write
with:
build_url: ${{ needs.resolve.outputs.build_url }}
filter: ${{ inputs.filter || '' }}
# From the event on a PR run, looked up on a dispatched one.
pr_number: ${{ needs.resolve.outputs.pr_number }}
tests_ref: ${{ needs.resolve.outputs.tests_ref }}
commit_sha: ${{ needs.resolve.outputs.commit_sha }}
branch_label: ${{ needs.resolve.outputs.branch_label }}
# `== true` rather than the raw input: a pull_request event supplies no
# inputs at all, and the reusable's parameter is typed boolean.
record_perf: ${{ inputs.record_perf == true }}
# Both platforms gate a release. `windows` defaults to true upstream, but
# say it here anyway: a default that changes underneath a caller pinned to
# @main is how this repo silently acquired a Windows leg it could not run
# (explorer-automation#55, fixed by #73).
windows: true
# `build_url` is the macOS zip; the Windows leg needs its own or it
# resolves the newest dev build and reports on the wrong commit.
windows_build_url: ${{ needs.resolve.outputs.windows_build_url }}
# What the Windows leg splits across runners. The gate runs everything, so
# say ALL — the same thing inworld-main.yml says for the same reason.
#
# The planner would infer it: an empty scope plus the whole-category filter
# reads as ALL. But that inference exists to stop callers who resolved
# nothing from running unsplit, and a gate should not lean on a fallback
# for its runtime. Empty here is reserved for the other case — a manual run
# that passed a `filter` at all. This repo cannot map an arbitrary NUnit
# filter to a scope, so the planner infers the split from the filter
# instead; a narrow one lands on one runner as `1/1`.
scope: ${{ (inputs.filter || '') == '' && 'ALL' || '' }}
secrets: inherit
result:
name: InWorld suite result
needs: [resolve, run-suite]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
# Reads its `needs` results and nothing else — no API call, no checkout.
permissions: {}
steps:
# The one check worth requiring on main. Everything above is either
# plumbing or a job name owned by another repository.
- name: Gate on the suite
env:
RESOLVE: ${{ needs.resolve.result }}
SUITE: ${{ needs.run-suite.result }}
run: |
set -euo pipefail
if [ "$RESOLVE" = "skipped" ]; then
echo "::notice::Not a release or hotfix PR — the InWorld suite does not gate this one."
exit 0
fi
if [ "$RESOLVE" = "success" ] && [ "$SUITE" = "success" ]; then
echo "::notice::InWorld suite passed."
exit 0
fi
# Cancelled counts as failure on purpose: a merge gate must not go
# green because the run was interrupted. The superseding run reports
# its own result.
echo "::error::InWorld gate failed — build: ${RESOLVE}, suite: ${SUITE}. See the Allure report linked in the suite's job summary."
exit 1