-
Notifications
You must be signed in to change notification settings - Fork 0
282 lines (257 loc) · 11.1 KB
/
run_tests.yml
File metadata and controls
282 lines (257 loc) · 11.1 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
name: Run Tests
# Per-provider integration matrix: each Couchbase/Postgres/MongoDB/etc. job
# spawns ONLY its own provider's containers (via HYPERBEE_TESTS_PROVIDERS_ONLY)
# and runs ONLY tests targeting that provider (via FullyQualifiedName filter).
# This eliminates the resource pressure that came from one job spinning up
# all 5 provider containers simultaneously on a 4-CPU / 16 GB GitHub-hosted
# runner -- the pressure was amplifying eventual-consistency races inside
# Couchbase Server (CREATE SCOPE/COLLECTION/INDEX vs N1QL planner-catalog)
# and surfacing them as test flakiness. Unit tests run separately because
# they need no containers at all.
on:
workflow_run:
workflows: [Create Release]
types: [requested]
branches: [main]
workflow_dispatch:
pull_request:
types: [opened, edited, synchronize, reopened]
branches: [main]
permissions:
contents: read
actions: read
jobs:
discover:
runs-on: ubuntu-latest
outputs:
branch_name: ${{ steps.set_branch.outputs.branch_name }}
configuration: ${{ steps.set_branch.outputs.configuration }}
steps:
- id: set_branch
shell: bash
run: |
# Pick the ref to test for each trigger type.
# For pull_request, test the PR head SHA (the incoming changes),
# not the base branch. Using the SHA is deterministic even if
# the PR branch is later force-pushed.
if [[ "${{ github.event_name }}" == "workflow_run" ]]; then
RAW='${{ github.event.workflow_run.head_sha }}'
elif [[ "${{ github.event_name }}" == "pull_request" ]]; then
RAW='${{ github.event.pull_request.head.sha }}'
else
RAW='${{ github.ref }}'
fi
# Strip the refs/heads/ prefix if present.
CLEAN="${RAW#refs/heads/}"
echo "Detected ref: $CLEAN"
echo "branch_name=$CLEAN" >> "$GITHUB_OUTPUT"
if [[ "$CLEAN" == "main" ]]; then
echo "configuration=Release" >> "$GITHUB_OUTPUT"
else
echo "configuration=Debug" >> "$GITHUB_OUTPUT"
fi
# ------------------------------------------------------------------ unit
unit-tests:
needs: discover
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
framework: [net8.0, net9.0, net10.0]
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.discover.outputs.branch_name }}
fetch-depth: 0
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.0.x
9.0.x
8.0.x
- name: Restore + Build (Hyperbee.Migrations.sln)
run: |
dotnet restore ${{ vars.SOLUTION_NAME }}
dotnet build ${{ vars.SOLUTION_NAME }} \
--no-restore \
-c ${{ needs.discover.outputs.configuration }}
- name: Unit tests (${{ matrix.framework }})
run: |
shopt -s globstar nullglob
mkdir -p ./TestResults
# Run every test project EXCEPT the integration project. Each is
# container-free and runs in seconds.
for proj in tests/Hyperbee.Migrations.Tests/Hyperbee.Migrations.Tests.csproj \
tests/Hyperbee.Migrations.Squash.Tests/Hyperbee.Migrations.Squash.Tests.csproj \
tests/Hyperbee.Migrations.Cli.Tests/Hyperbee.Migrations.Cli.Tests.csproj; do
echo "::group::Unit: $proj (${{ matrix.framework }})"
dotnet test "$proj" \
--no-build \
-c ${{ needs.discover.outputs.configuration }} \
-f ${{ matrix.framework }} \
--logger "trx;LogFileName=$(basename "$proj" .csproj)-${{ matrix.framework }}.trx" \
--results-directory ./TestResults
echo "::endgroup::"
done
- name: Upload unit test results
uses: actions/upload-artifact@v6
if: always()
with:
name: test-results-unit-${{ matrix.framework }}
path: ./TestResults/
if-no-files-found: warn
# ------------------------------------------------------------------ integration
# Per-provider matrix. Each matrix entry runs ONLY one provider's
# containers + ONLY that provider's test classes. Resource competition
# between provider containers is eliminated -- a CI runner sees one
# Couchbase OR one MongoDB OR one OpenSearch, not all five at once.
#
# The matrix-include shape (rather than a 5x3 cross product) lets each
# provider's `filter` and `providers-only` env strings be tailored;
# MultiProvider tests need both Postgres + MongoDB, CliBinary spawns its
# own throwaway Postgres independent of the suite-level Postgres
# container, etc.
integration-tests:
needs: discover
# Heavy container-based integration tests are LocalOnly for now: they
# are excluded from the gating matrix (every class is
# [TestCategory("LocalOnly")]) and do NOT gate the NuGet publish (the
# release pipeline runs unit tests only). With nothing left to run, this
# job is gated off so it does not fail on a zero-match `dotnet test`.
# Re-enable by setting repo variable RUN_HEAVY_INTEGRATION=true (no code
# change needed); run the suite locally / on demand in the meantime.
if: ${{ vars.RUN_HEAVY_INTEGRATION == 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
# Cap concurrent jobs so the 18-cell matrix does not fire ~36
# simultaneous mcr.microsoft.com base-image pulls from GitHub's
# egress at once. That synchronized burst trips Azure Front Door's
# WAF in front of MCR ("The request is blocked" / Ref A/B/C), which
# surfaced as an instant Testcontainers image-pull failure. Combined
# with the bounded pre-pull step below it removes the failure class.
max-parallel: 4
matrix:
framework: [net8.0, net9.0, net10.0]
provider: [postgres, aerospike, mongodb, opensearch, couchbase, multi-provider]
include:
- provider: postgres
providers-only: Postgres
filter: "FullyQualifiedName~Postgres|FullyQualifiedName~CliBinary"
- provider: aerospike
providers-only: Aerospike
filter: "FullyQualifiedName~Aerospike"
- provider: mongodb
providers-only: MongoDb
filter: "FullyQualifiedName~MongoDB"
- provider: opensearch
providers-only: OpenSearch
filter: "FullyQualifiedName~OpenSearch"
- provider: couchbase
providers-only: Couchbase
filter: "FullyQualifiedName~Couchbase"
- provider: multi-provider
providers-only: Postgres,MongoDb
filter: "FullyQualifiedName~MultiProviderHost"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: ${{ needs.discover.outputs.branch_name }}
fetch-depth: 0
- name: Setup .NET SDKs
uses: actions/setup-dotnet@v5
with:
dotnet-version: |
10.0.x
9.0.x
8.0.x
- name: Warm MCR base images (bounded retry, off the test hot path)
# The per-provider runner Dockerfiles begin with
# `FROM mcr.microsoft.com/dotnet/{sdk,runtime}:10.0`, which
# Testcontainers builds at test time. GitHub-hosted runners do not
# pre-cache these images, so the pull happens inside the test and a
# transient Azure-Front-Door block becomes an instant, unrecoverable
# test failure. Pull them here with bounded exponential backoff so
# (a) a throttle window is ridden out as a retryable workflow step,
# and (b) the subsequent Testcontainers build hits the local Docker
# cache instead of re-pulling. Pair with strategy.max-parallel above.
run: |
set -u
for img in mcr.microsoft.com/dotnet/sdk:10.0 mcr.microsoft.com/dotnet/runtime:10.0; do
n=0
until docker pull "$img"; do
n=$((n+1))
if [ "$n" -ge 5 ]; then
echo "::error::Failed to pull $img after $n attempts (MCR/AFD block?)."
exit 1
fi
sleep $((n * 15))
done
done
- name: Restore + Build solution (EnableIntegrationTests)
# Whole-solution build is required: CliBinaryEndToEndTests spawns
# runners/Hyperbee.Migrations.Cli/bin/<cfg>/<tfm>/hyperbee-migrations
# as a child process and walks the sample assemblies in their own
# bin folders. Building only the test project misses those. With
# /p:EnableIntegrationTests=true the integration project includes
# its INTEGRATIONS-gated source.
run: |
dotnet restore ${{ vars.SOLUTION_NAME }}
dotnet build ${{ vars.SOLUTION_NAME }} \
--no-restore \
-c ${{ needs.discover.outputs.configuration }} \
/p:EnableIntegrationTests=true
- name: Integration tests -- ${{ matrix.provider }} (${{ matrix.framework }})
# HYPERBEE_TESTS_PROVIDERS_ONLY restricts the [AssemblyInitialize]
# to only the named provider(s). The --filter then selects only
# the test classes for those providers. LocalOnly + MultiNode
# categories are excluded across the board (the Couchbase
# host-side cluster-map race is gated behind LocalOnly per F-1
# v3.0.1 follow-up; MultiNode OpenSearch tests need 3 JVMs and
# have their own manual-trigger workflow).
env:
HYPERBEE_TESTS_PROVIDERS_ONLY: ${{ matrix.providers-only }}
run: |
mkdir -p ./TestResults
dotnet test \
tests/Hyperbee.Migrations.Integration.Tests/Hyperbee.Migrations.Integration.Tests.csproj \
--no-build \
-c ${{ needs.discover.outputs.configuration }} \
-f ${{ matrix.framework }} \
--filter "(${{ matrix.filter }})&TestCategory!=LocalOnly&TestCategory!=MultiNode" \
--logger "trx;LogFileName=integration-${{ matrix.provider }}-${{ matrix.framework }}.trx" \
--logger "console;verbosity=normal" \
--results-directory ./TestResults \
/p:EnableIntegrationTests=true
- name: Upload integration test results
uses: actions/upload-artifact@v6
if: always()
with:
name: test-results-integration-${{ matrix.provider }}-${{ matrix.framework }}
path: ./TestResults/
if-no-files-found: warn
# ------------------------------------------------------------------ merge
merge-results:
runs-on: ubuntu-latest
needs: [unit-tests, integration-tests]
if: always()
steps:
- name: Download All Test Results
uses: actions/download-artifact@v8
with:
pattern: test-results-*
path: ./TestResults
merge-multiple: true
- name: Upload Combined Results
uses: actions/upload-artifact@v6
with:
name: test-results
path: ./TestResults/
if-no-files-found: warn
- name: Cleanup Individual Artifacts
uses: geekyeggo/delete-artifact@v6
with:
name: test-results-*