Repository navigation
237 lines (227 loc) · 11.2 KB
/
Copy pathunit-tests.yml
File metadata and controls
237 lines (227 loc) · 11.2 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
name: Unit tests
on:
workflow_dispatch:
pull_request:
branches:
- master
- dev
- release/**
- hotfix/**
# No paths/paths-ignore here, ever. `Unit Tests Complete` below is a required
# check on dev, and a required check whose workflow never triggers sits as
# "Expected -- waiting for status to be reported" forever. A PR must pass its
# required checks BEFORE it can enter the merge queue, so a trigger-level
# docs/** filter left docs-only PRs unqueueable for anyone without ruleset
# bypass. Docs-only PRs skip the heavy jobs via the `changes` job instead,
# which keeps the gate context reporting on every PR.
# Run the suite against the speculative merge commit a merge queue builds, which
# is the only thing that tests what will actually land: two pull requests can
# each be green on their own and broken in combination, and nothing before this
# point ever evaluates their union.
#
# The queue that emits these events is the "Merge Queue" ruleset (id 20466211),
# active on dev: SQUASH, ALLGREEN grouping, max 3 entries building, and it
# requires `ruff-linting` and `Unit Tests Complete` on the merge group. Two
# operational notes recorded here so they survive the people who know them:
#
# - The queue applies ONE merge method (squash) to everything it lands.
# Release merge-backs must stay real merge commits, so they go around the
# queue via the ruleset's bypass actors (repo admins + the maintainer
# teams), exactly as they bypass branch protection today. Nothing in this
# repository's automation calls `gh pr merge`, and that is load-bearing:
# on a queue-enabled branch that command can exit 0 WITHOUT merging (it
# arms auto-merge and the queue lands the PR later, with the queue's
# method), so any future script must read the PR's merged state back
# rather than trusting the exit code.
#
# - Under the two-tier matrices below, pull requests run the light tier and
# the queue re-runs the full tier on the speculative merge commit, so a
# queued PR's checks taking longer than its own PR run is expected. The
# exception is a docs-only merge group, which skips the heavy jobs (see
# the `changes` job).
merge_group:
jobs:
# hard gate: linting must pass before we spend any time building images or running tests
ruff:
# Release PRs and merge-backs are merged without waiting for CI. Skip when the
# head is release/merge-* AND the PR has release-management.
if: ${{ !(startsWith(github.head_ref, 'release/merge-') && contains(github.event.pull_request.labels.*.name, 'release-management')) }}
uses: ./.github/workflows/ruff.yml
secrets: inherit
# Detect a docs-only change so the heavy jobs below can skip themselves while
# `Unit Tests Complete` still reports.
#
# - pull_request: the files of the PR.
# - merge_group: the diff between the queue's base and the speculative merge
# commit. That commit carries every entry queued ahead of this one, so the
# suite is skipped only when the whole union is docs-only. The code in that
# commit is then identical to the base, which already landed through this
# queue, so there is nothing new for the suite to test.
# - workflow_dispatch: the step is skipped, the output stays empty, and every
# `!= 'true'` guard runs the full suite.
#
# The parser docs under docs/content/supported_tools/parsers/ do NOT count as
# docs-only: unittests/test_parsers.py checks that every parser has a doc page
# and every doc page has a parser, so a rename or delete there can break the
# suite.
changes:
# Release PRs and merge-backs are merged without waiting for CI. Skip when the
# head is release/merge-* AND the PR has release-management.
if: ${{ !(startsWith(github.head_ref, 'release/merge-') && contains(github.event.pull_request.labels.*.name, 'release-management')) }}
runs-on: ubuntu-latest
outputs:
docs_only: ${{ steps.filter.outputs.docs_only }}
steps:
- name: Detect a docs-only change
id: filter
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
env:
GH_TOKEN: ${{ github.token }}
EVENT_NAME: ${{ github.event_name }}
PR_NUMBER: ${{ github.event.pull_request.number }}
MG_BASE_SHA: ${{ github.event.merge_group.base_sha }}
MG_HEAD_SHA: ${{ github.event.merge_group.head_sha }}
# Fail open: if the file listing errors, docs_only stays false and the
# full suite runs. This step must never fail the job -- a red `changes`
# job would skip the heavy chain and fail the gate for every PR.
run: |
docs_only=false
files=""
listed=false
if [ "${EVENT_NAME}" = "pull_request" ]; then
if files=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/files" --paginate --jq '.[].filename'); then
listed=true
fi
else
# git rather than the compare API: compare truncates its file list
# at 300 entries, git diff never does. Two depth-1 fetches are enough
# for a tree-to-tree diff.
git init -q mg
if git -C mg fetch -q --depth=1 --no-tags \
"https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" \
"${MG_BASE_SHA}" "${MG_HEAD_SHA}" \
&& files=$(git -C mg diff --name-only "${MG_BASE_SHA}" "${MG_HEAD_SHA}"); then
listed=true
fi
fi
if [ "${listed}" = "true" ] && [ -n "${files}" ] \
&& ! echo "${files}" | grep -qv '^docs/' \
&& ! echo "${files}" | grep -q '^docs/content/supported_tools/parsers/'; then
docs_only=true
fi
echo "changed files:"
echo "${files}"
echo "docs_only=${docs_only}" >> "${GITHUB_OUTPUT}"
build-docker-containers:
needs: [ ruff, changes ]
# Docs-only PRs and merge groups skip the whole heavy chain (the test-* jobs skip with this
# job via their needs). The gate below only accepts a skipped dependency
# when docs_only is true, so this can never silently under-test code.
if: needs.changes.outputs.docs_only != 'true'
strategy:
matrix:
# Two tiers, one workflow. Pull requests build amd64 only; the merge
# queue (and manual dispatch) builds both platforms and tests the
# speculative merge commit with everything. The queue is the gate, the
# pull-request run is feedback -- an arm64-only breakage surfaces when
# the queue tests the exact commit that would land, where it costs one
# queue ejection instead of a bad merge.
#
# Tiering shrinks MATRICES, it does not skip jobs: the
# unit-tests-complete gate treats a skipped dependency as a failure
# (except on a docs-only change, the one skip it explicitly allows).
# Same jobs, fewer cells.
platform: ${{ github.event_name == 'pull_request' && fromJSON('["linux/amd64"]') || fromJSON('["linux/amd64", "linux/arm64"]') }}
fail-fast: false
uses: ./.github/workflows/build-docker-images-for-testing.yml
secrets: inherit
with:
platform: ${{ matrix.platform }}
test-performance:
needs: build-docker-containers
uses: ./.github/workflows/performance-tests.yml
secrets: inherit
test-rest-framework:
strategy:
matrix:
# Light tier drops the arm64 cells, matching the build tiering above (a
# pull-request run has no arm64 image artifact to test anyway). Both
# v3_feature_locations values stay in BOTH tiers: the flag changes the
# API schema and endpoint behaviour, so the two runs are genuinely
# different suites, not a platform duplicate.
platform: ${{ github.event_name == 'pull_request' && fromJSON('["linux/amd64"]') || fromJSON('["linux/amd64", "linux/arm64"]') }}
v3_feature_locations: [ false, true ]
fail-fast: false
needs: build-docker-containers
uses: ./.github/workflows/rest-framework-tests.yml
secrets: inherit
with:
platform: ${{ matrix.platform }}
v3_feature_locations: ${{ matrix.v3_feature_locations }}
# only run integration tests for linux/amd64 (default)
test-user-interface:
needs: build-docker-containers
uses: ./.github/workflows/integration-tests.yml
secrets: inherit
# only run k8s tests for linux/amd64 (default)
test-k8s:
needs: build-docker-containers
uses: ./.github/workflows/k8s-tests.yml
secrets: inherit
# One stable context for a branch ruleset or merge queue to require.
#
# The jobs above report contexts like
# "test-rest-framework (linux/amd64, true) / Rest Framework Unit Tests (debian)"
# which change whenever a matrix entry is added, removed or renamed. Requiring
# those individually means the ruleset silently stops covering a job the moment
# the matrix moves; requiring this one job cannot drift, because its `needs`
# list is checked by the workflow itself.
#
# Why this matters more under a merge queue than it does today: right now a
# human reads the checks and decides to merge, so a failing-but-not-required
# job still gets seen. A queue merges as soon as the *required* checks pass, so
# anything not required stops being a gate at all. Requiring this context is
# what keeps the suite meaningful once merging is automatic.
unit-tests-complete:
name: Unit Tests Complete
# always() so this still reports when something upstream fails -- without it
# the job would be skipped, and a skipped required check blocks the queue
# instead of failing it.
# `changes` is skipped only by the release guard above. Skip this job too,
# so a release PR does not report a failure. Release PRs are admin-merged,
# so the skipped required check does not block them.
if: ${{ always() && needs.changes.result != 'skipped' }}
needs:
- changes
- ruff
- build-docker-containers
- test-performance
- test-rest-framework
- test-user-interface
- test-k8s
runs-on: ubuntu-latest
steps:
- name: Verify every job succeeded
# A skipped or cancelled dependency is not a pass, with one exception:
# on a docs-only pull request or merge group the heavy jobs skip by
# design, and this gate must still report success or the PR can never
# enter (or leave) the merge queue (a required check that never reports
# blocks it).
# Checking explicitly rather than relying on this job's own conclusion,
# because a job whose needs were skipped can otherwise report success
# by omission.
run: |
docs_only="${{ needs.changes.outputs.docs_only }}"
results="${{ join(needs.*.result, ' ') }}"
echo "dependency results: ${results} (docs_only: ${docs_only})"
for result in ${results}; do
if [ "${result}" = "success" ]; then
continue
fi
if [ "${result}" = "skipped" ] && [ "${docs_only}" = "true" ]; then
continue
fi
echo "::error::At least one job did not succeed (${results})"
exit 1
done
echo "All jobs succeeded."