-
Notifications
You must be signed in to change notification settings - Fork 88
473 lines (405 loc) · 18.7 KB
/
Copy pathbuild.yml
File metadata and controls
473 lines (405 loc) · 18.7 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
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
name: build
on:
push:
branches: ["master", "release/*", "feature/*"]
pull_request_target:
branches: ["master", "release/*", "feature/*", "devops/*"]
workflow_dispatch:
permissions:
contents: read
id-token: write # needed by dorny/test-reporter
checks: write # needed by dorny/test-reporter
# Cancel any in-progress run for the same branch when a new push arrives
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
SOLUTION_NAME: SlimMessageBus.sln
SOLUTION_CONFIGURATION: Release
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# ──────────────────────────────────────────────────────────────────────────
# Build and unit-test (SonarCloud analysis runs separately in the sonar job)
# ──────────────────────────────────────────────────────────────────────────
build:
runs-on: ubuntu-latest
steps:
- name: Checkout (PR – use fork's head SHA)
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
cache: false
- name: Restore dependencies
run: dotnet restore $SOLUTION_NAME
working-directory: ./src
- name: Build
run: |
dotnet build $SOLUTION_NAME \
--configuration $SOLUTION_CONFIGURATION \
--no-restore
working-directory: ./src
- name: Unit Tests
run: |
dotnet test $SOLUTION_NAME \
--configuration $SOLUTION_CONFIGURATION \
--no-build \
--verbosity normal \
--logger "trx;LogFilePrefix=Unit" \
--collect:"XPlat Code Coverage;Format=opencover" \
--filter "Category!=Integration"
working-directory: ./src
- name: Collect unit test coverage
if: success() || failure()
run: |
mkdir -p ./coverage
find ./src -name "coverage.opencover.xml" | while read file; do
dir=$(basename $(dirname $(dirname "$file")))
guid=$(basename $(dirname "$file"))
cp "$file" "./coverage/${dir}_${guid}.opencover.xml"
done
- name: Upload unit test coverage
if: success() || failure()
uses: actions/upload-artifact@v6
with:
name: coverage-unit
path: ./coverage
- name: Collect unit test results
if: success() || failure()
run: |
mkdir -p ./test-results
find ./src -name "*.trx" | while read file; do
grandparent_dir=$(basename $(dirname $(dirname "$file")))
filename=$(basename "$file")
cp "$file" "./test-results/${grandparent_dir}_${filename}"
done
- name: Upload Unit Test Results
if: success() || failure()
uses: actions/upload-artifact@v6
with:
name: test-results-unit
path: ./test-results
- name: Copy NuGet packages
shell: bash
run: |
mkdir ./dist
find -name "*.nupkg" -exec cp {} ./dist \;
find -name "*.snupkg" -exec cp {} ./dist \;
find ./dist
- name: Archive NuGet packages
uses: actions/upload-artifact@v6
with:
name: nuget-packages
path: "./dist"
# ──────────────────────────────────────────────────────────────────────────
# Integration tests – one parallel runner per transport
# ──────────────────────────────────────────────────────────────────────────
integration-tests:
name: ${{ matrix.name }}
needs: build
# For fork PRs, this job waits for a maintainer to approve the "integration-tests"
# environment before secrets are exposed and the PR's code runs against cloud infra.
environment: ${{ (github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository) && 'integration-tests' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false # don't cancel sibling legs when one fails
matrix:
include:
# In-process transports (no external infrastructure required)
- transport: Memory
name: Memory
filter: "Category=Integration&Transport=Memory"
docker_services: ""
# Local broker transports (started via docker compose)
- transport: Kafka
name: Kafka
filter: "Category=Integration&Transport=Kafka"
docker_services: "zookeeper kafka kafka-init"
- transport: RabbitMQ
name: RabbitMQ
filter: "Category=Integration&Transport=RabbitMQ"
docker_services: "rabbitmq"
- transport: Redis
name: Redis
filter: "Category=Integration&Transport=Redis"
docker_services: "redis"
- transport: MQTT
name: MQTT
filter: "Category=Integration&Transport=MQTT"
docker_services: "mqtt"
# NATS connects to localhost:4222 via the nats_endpoint env var
- transport: NATS
name: NATS
filter: "Category=Integration&Transport=NATS"
docker_services: "nats"
# SQL transports use TestContainers directly
- transport: Sql
name: SQL Server
filter: "Category=Integration&Transport=Sql"
test_path: "Tests/SlimMessageBus.Host.Sql.Test"
docker_services: ""
- transport: PostgreSql
name: PostgreSQL
filter: "Category=Integration&Transport=PostgreSql"
test_path: "Tests/SlimMessageBus.Host.PostgreSql.Test"
docker_services: ""
# Outbox – four parallel sub-legs:
# 1a. Sql.DbContext OutboxTests – AzureSB + Kafka; SQL Server via local docker
- transport: Outbox.SqlDbContext.Tests
name: "Outbox (SqlDbContext Tests)"
filter: "Category=Integration&ClassName=SlimMessageBus.Host.Outbox.Sql.DbContext.Test.OutboxTests"
test_path: "Tests/SlimMessageBus.Host.Outbox.Sql.DbContext.Test"
docker_services: "sqldb zookeeper kafka kafka-init"
local_sql_connection: "Server=localhost;Initial Catalog=SlimMessageBus_Outbox;User ID=sa;Password=SuperSecretP@55word;TrustServerCertificate=true;MultipleActiveResultSets=true;"
# 1b. Sql.DbContext OutboxBenchmarkTests – AzureSB + RabbitMQ; SQL Server via local docker
- transport: Outbox.SqlDbContext.Benchmarks
name: "Outbox (SqlDbContext Benchmarks)"
filter: "Category=Integration&ClassName=SlimMessageBus.Host.Outbox.Sql.DbContext.Test.OutboxBenchmarkTests"
test_path: "Tests/SlimMessageBus.Host.Outbox.Sql.DbContext.Test"
docker_services: "sqldb rabbitmq"
local_sql_connection: "Server=localhost;Initial Catalog=SlimMessageBus_Outbox;User ID=sa;Password=SuperSecretP@55word;TrustServerCertificate=true;MultipleActiveResultSets=true;"
# 2. PostgreSql.DbContext – PostgreSQL, Kafka, RabbitMQ all via TestContainers
- transport: Outbox.PostgreSqlDbContext
name: "Outbox (PostgreSqlDbContext)"
filter: "Category=Integration"
test_path: "Tests/SlimMessageBus.Host.Outbox.PostgreSql.DbContext.Test"
docker_services: ""
# 3. Repository-layer tests – Sql.Test + PostgreSql.Test + MongoDb.Test, all via TestContainers
- transport: Outbox.Repositories
name: "Outbox (Repositories)"
filter: "Category=Integration&Transport=Outbox.Sql"
test_path: ""
docker_services: ""
# 4. MongoDb outbox repository tests – via TestContainers
- transport: Outbox.MongoDb
name: "Outbox (MongoDb)"
filter: "Category=Integration&Transport=Outbox.MongoDb"
test_path: "Tests/SlimMessageBus.Host.Outbox.MongoDb.Test"
docker_services: ""
# Cloud transports (require repository secrets)
- transport: AmazonSQS
name: AmazonSQS
filter: "Category=Integration&Transport=AmazonSQS"
docker_services: ""
- transport: AzureServiceBus
name: AzureServiceBus
filter: "Category=Integration&Transport=AzureServiceBus"
docker_services: ""
- transport: AzureEventHub
name: AzureEventHub
filter: "Category=Integration&Transport=AzureEventHub"
docker_services: ""
steps:
- name: Checkout (PR – use fork's head SHA)
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
cache: false
- name: Restore dependencies
run: dotnet restore $SOLUTION_NAME
working-directory: ./src
- name: Build
run: |
dotnet build $SOLUTION_NAME \
--configuration $SOLUTION_CONFIGURATION \
--no-restore
working-directory: ./src
- name: Start infrastructure services
if: matrix.docker_services != ''
run: |
docker compose -f src/Infrastructure/docker-compose.yml up --wait --force-recreate -V ${{ matrix.docker_services }}
- name: Integration Tests
run: |
TEST_TARGET="${{ matrix.test_path }}"
[ -z "$TEST_TARGET" ] && TEST_TARGET="$SOLUTION_NAME"
dotnet test "$TEST_TARGET" \
--configuration $SOLUTION_CONFIGURATION \
--no-build \
--verbosity normal \
--logger "trx;LogFilePrefix=Integration_${{ matrix.transport }}" \
--collect:"XPlat Code Coverage;Format=opencover" \
--filter "${{ matrix.filter }}"
working-directory: ./src
env:
# Azure cloud
azure_servicebus_connectionstring: ${{ secrets.azure_servicebus_connectionstring }}
azure_eventhub_connectionstring: ${{ secrets.azure_eventhub_connectionstring }}
azure_storagecontainer_connectionstring: ${{ secrets.azure_storagecontainer_connectionstring }}
# AWS cloud
amazon_access_key: ${{ secrets.amazon_access_key }}
amazon_secret_access_key: ${{ secrets.amazon_secret_access_key }}
# Cloud broker overrides (prefixed with _ so local values take precedence)
_kafka_brokers: ${{ secrets.kafka_brokers }}
_kafka_username: ${{ secrets.kafka_username }}
_kafka_password: ${{ secrets.kafka_password }}
_kafka_secure: ${{ secrets.kafka_secure }}
_mqtt_server: ${{ secrets.mqtt_server }}
_mqtt_port: ${{ secrets.mqtt_port }}
_mqtt_username: ${{ secrets.mqtt_username }}
_mqtt_password: ${{ secrets.mqtt_password }}
_mqtt_secure: ${{ secrets.mqtt_secure }}
_rabbitmq_connectionstring: ${{ secrets.rabbitmq_connectionstring }}
_redis_connectionstring: ${{ secrets.redis_connectionstring }}
# local_sql_connection takes priority (set per matrix leg that uses docker sqldb)
sqlserver_connectionstring: ${{ matrix.local_sql_connection || secrets.sqlserver_connectionstring }}
# Local TestContainers / docker compose values
kafka_brokers: localhost:9092
kafka_username: user
kafka_password: password
kafka_secure: false
mqtt_server: localhost
mqtt_port: 1883
mqtt_username: "(empty)"
mqtt_password: "(empty)"
mqtt_secure: false
rabbitmq_connectionstring: amqp://localhost
redis_connectionstring: localhost:6379
_sqlserver_connectionstring: "Server=localhost;Initial Catalog=SlimMessageBus_Outbox;User ID=sa;Password=SuperSecretP@55word;TrustServerCertificate=true;MultipleActiveResultSets=true;"
nats_endpoint: "nats://localhost:4222"
- name: Collect integration test results
if: success() || failure()
run: |
mkdir -p ./test-results
find ./src -name "*.trx" | while read file; do
grandparent_dir=$(basename $(dirname $(dirname "$file")))
filename=$(basename "$file")
cp "$file" "./test-results/${grandparent_dir}_${filename}"
done
- name: Upload Test Results
if: success() || failure()
uses: actions/upload-artifact@v6
with:
name: test-results-${{ matrix.transport }}
path: ./test-results
- name: Collect integration test coverage
if: success() || failure()
run: |
mkdir -p ./coverage
find ./src -name "coverage.opencover.xml" | while read file; do
dir=$(basename $(dirname $(dirname "$file")))
guid=$(basename $(dirname "$file"))
cp "$file" "./coverage/${dir}_${guid}.opencover.xml"
done
- name: Upload integration test coverage
if: success() || failure()
uses: actions/upload-artifact@v6
with:
name: coverage-${{ matrix.transport }}
path: ./coverage
# ──────────────────────────────────────────────────────────────────────────
# SonarCloud analysis – runs after all tests so full coverage is included
# ──────────────────────────────────────────────────────────────────────────
sonar:
needs: [build, integration-tests]
if: success() || failure()
runs-on: ubuntu-latest
steps:
- name: Checkout (PR – use fork's head SHA)
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Setup .NET
uses: actions/setup-dotnet@v5
with:
dotnet-version: 10.0.x
cache: false
- name: Restore dependencies
run: dotnet restore $SOLUTION_NAME
working-directory: ./src
- name: Setup Java17
uses: actions/setup-java@v5
with:
distribution: "adopt"
java-version: "17"
- name: Install SonarCloud scanner
run: dotnet tool update dotnet-sonarscanner --tool-path ./.sonar/scanner
- name: Download all coverage reports
uses: actions/download-artifact@v6
with:
pattern: coverage-*
path: ./coverage
merge-multiple: true
- name: SonarScanner Begin
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: |
params=""
pr_number="${{ github.event.pull_request.number }}"
if [[ -n "$pr_number" ]]; then
params="/d:sonar.pullrequest.key=${pr_number}"
fi
../.sonar/scanner/dotnet-sonarscanner begin /k:"zarusz_SlimMessageBus" /o:"zarusz" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="../coverage/*.opencover.xml" /d:sonar.exclusions="Samples/**/*,Tests/**/*" $params
working-directory: ./src
- name: Build
run: |
dotnet build $SOLUTION_NAME \
--configuration $SOLUTION_CONFIGURATION \
--no-restore
working-directory: ./src
- name: SonarScanner End
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: ../.sonar/scanner/dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}"
working-directory: ./src
# ──────────────────────────────────────────────────────────────────────────
# Collect all test results from every job and publish a single report
# ──────────────────────────────────────────────────────────────────────────
report:
needs: [build, integration-tests]
if: success() || failure()
runs-on: ubuntu-latest
steps:
- name: Checkout (PR – use fork's head SHA)
if: github.event_name == 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Checkout
if: github.event_name != 'pull_request_target'
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Download all test results
uses: actions/download-artifact@v6
with:
pattern: test-results-*
path: ./test-results
merge-multiple: true
- name: Publish Test Results
uses: dorny/test-reporter@v2
with:
name: .NET Tests
path: ./test-results/*.trx
reporter: dotnet-trx
fail-on-error: true