Skip to content

Commit b453509

Browse files
authored
ci: add Codecov coverage + Trunk flaky-test uploads (#2554)
## What `gotestsum` wraps `go test` so coverage and JUnit XML come from one invocation. Each test job uploads to: - **Codecov Coverage** — `coverage.txt` per package × arch. - **Codecov Test Analytics** — `junit.xml` per package × arch (failed-test reports in PR comments, dashboard). - **Trunk Flaky Tests** — same `junit.xml` (per-test history, flake detection, quarantine). Coverage is informational; nothing blocks merges. Trunk upload is `continue-on-error`. ## Required before this delivers value Add to repo secrets: - `CODECOV_TOKEN` - `TRUNK_API_TOKEN` Without them the workflow runs as before — every upload step skips itself. ## Out of scope Coverage on integration tests (would need a `tests/integration/Makefile` change). Codecov Test Analytics and Trunk both already get the integration JUnit.
1 parent 97aa71a commit b453509

6 files changed

Lines changed: 143 additions & 7 deletions

File tree

.github/workflows/integration_tests.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ on:
77
type: boolean
88
description: "Whether to publish the results"
99
required: true
10+
secrets:
11+
CODECOV_TOKEN: { required: false }
1012
jobs:
1113
integration_tests:
1214
runs-on: infra-tests
1315
timeout-minutes: 30
1416
permissions:
1517
contents: read
1618
id-token: write
19+
env:
20+
# Surfaced as env so upload steps can gate on presence (skipped on fork PRs).
21+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1722

1823
steps:
1924
- name: Checkout Code
@@ -78,6 +83,15 @@ jobs:
7883
name: Integration Tests Results
7984
path: ./tests/integration/test-results.xml
8085

86+
- name: Upload test results to Codecov
87+
if: ${{ !cancelled() && env.CODECOV_TOKEN != '' && hashFiles('tests/integration/test-results.xml') != '' }}
88+
uses: codecov/test-results-action@v1
89+
with:
90+
token: ${{ secrets.CODECOV_TOKEN }}
91+
files: tests/integration/test-results.xml
92+
flags: integration
93+
disable_search: true
94+
8195
- name: Upload Service Logs
8296
if: ${{ always() && inputs.publish == true }}
8397
uses: actions/upload-artifact@v6

.github/workflows/pr-tests-arm64.yml

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: ARM64 tests on PRs
22

3-
on: [workflow_call]
3+
on:
4+
workflow_call:
5+
secrets:
6+
CODECOV_TOKEN: { required: false }
47

58
permissions:
69
contents: read
@@ -47,28 +50,37 @@ jobs:
4750
# and race on `reaper_<hash>` creation. Disable Ryuk; t.Cleanup
4851
# handles normal exit, the prune step below handles abnormal exit.
4952
TESTCONTAINERS_RYUK_DISABLED: "true"
53+
# Surfaced as env so upload steps can gate on presence (skipped on fork PRs).
54+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
5055
strategy:
5156
matrix:
5257
include:
5358
- package: packages/api
59+
flag: arm64-api
5460
test_path: ./...
5561
sudo: false
5662
- package: packages/client-proxy
63+
flag: arm64-client-proxy
5764
test_path: ./...
5865
sudo: false
5966
- package: packages/db
67+
flag: arm64-db
6068
test_path: ./...
6169
sudo: false
6270
- package: packages/docker-reverse-proxy
71+
flag: arm64-docker-reverse-proxy
6372
test_path: ./...
6473
sudo: false
6574
- package: packages/envd
75+
flag: arm64-envd
6676
test_path: ./...
6777
sudo: true
6878
- package: packages/orchestrator
79+
flag: arm64-orchestrator
6980
test_path: ./...
7081
sudo: true
7182
- package: packages/shared
83+
flag: arm64-shared
7284
test_path: ./pkg/...
7385
sudo: false
7486
fail-fast: false
@@ -133,14 +145,23 @@ jobs:
133145
sudo udevadm trigger
134146
if: matrix.package == 'packages/orchestrator'
135147

148+
- name: Install gotestsum
149+
run: go install gotest.tools/gotestsum@v1.13.0
150+
136151
- name: Compile test binaries
137152
working-directory: ${{ matrix.package }}
138153
run: sudo -E `which go` test -run '^$' ${{ matrix.test_path }}
139154
if: matrix.sudo == true
140155

156+
# sudo strips PATH; pass it through so gotestsum/go resolve to the runner's
157+
# toolchain (1.25.9) instead of /usr/bin/go. Trap chowns reports back to
158+
# the runner so uploads can read them even when tests fail.
141159
- name: Run tests that require sudo
142160
working-directory: ${{ matrix.package }}
143-
run: sudo -E `which go` test -v -timeout 20m ${{ matrix.test_path }}
161+
run: |
162+
trap 'sudo chown "$(id -u):$(id -g)" coverage.txt junit.xml 2>/dev/null || true' EXIT
163+
sudo -E env "PATH=$PATH" gotestsum --junitfile=junit.xml --format=standard-verbose \
164+
-- -timeout 20m -coverprofile=coverage.txt -covermode=atomic ${{ matrix.test_path }}
144165
if: matrix.sudo == true
145166

146167
- name: Compile test binaries
@@ -150,5 +171,25 @@ jobs:
150171

151172
- name: Run tests
152173
working-directory: ${{ matrix.package }}
153-
run: go test -v -timeout 20m ${{ matrix.test_path }}
174+
run: |
175+
gotestsum --junitfile=junit.xml --format=standard-verbose \
176+
-- -timeout 20m -coverprofile=coverage.txt -covermode=atomic ${{ matrix.test_path }}
154177
if: matrix.sudo == false
178+
179+
- name: Upload coverage to Codecov
180+
if: ${{ !cancelled() && env.CODECOV_TOKEN != '' && hashFiles(format('{0}/coverage.txt', matrix.package)) != '' }}
181+
uses: codecov/codecov-action@v5
182+
with:
183+
token: ${{ secrets.CODECOV_TOKEN }}
184+
files: ${{ matrix.package }}/coverage.txt
185+
flags: ${{ matrix.flag }}
186+
disable_search: true
187+
188+
- name: Upload test results to Codecov
189+
if: ${{ !cancelled() && env.CODECOV_TOKEN != '' && hashFiles(format('{0}/junit.xml', matrix.package)) != '' }}
190+
uses: codecov/test-results-action@v1
191+
with:
192+
token: ${{ secrets.CODECOV_TOKEN }}
193+
files: ${{ matrix.package }}/junit.xml
194+
flags: ${{ matrix.flag }}
195+
disable_search: true

.github/workflows/pr-tests.yml

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
name: Run tests on PRs
22

3-
on: [ workflow_call ]
3+
on:
4+
workflow_call:
5+
secrets:
6+
CODECOV_TOKEN: { required: false }
47

58
permissions:
69
contents: read
@@ -15,28 +18,37 @@ jobs:
1518
# and race on `reaper_<hash>` creation. Disable Ryuk; t.Cleanup
1619
# handles normal exit, the prune step below handles abnormal exit.
1720
TESTCONTAINERS_RYUK_DISABLED: "true"
21+
# Surfaced as env so upload steps can gate on presence (skipped on fork PRs).
22+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
1823
strategy:
1924
matrix:
2025
include:
2126
- package: packages/api
27+
flag: unit-api
2228
test_path: ./...
2329
sudo: false
2430
- package: packages/client-proxy
31+
flag: unit-client-proxy
2532
test_path: ./...
2633
sudo: false
2734
- package: packages/db
35+
flag: unit-db
2836
test_path: ./...
2937
sudo: false
3038
- package: packages/docker-reverse-proxy
39+
flag: unit-docker-reverse-proxy
3140
test_path: ./...
3241
sudo: false
3342
- package: packages/envd
43+
flag: unit-envd
3444
test_path: ./...
3545
sudo: true
3646
- package: packages/orchestrator
47+
flag: unit-orchestrator
3748
test_path: ./...
3849
sudo: true
3950
- package: packages/shared
51+
flag: unit-shared
4052
test_path: ./pkg/...
4153
sudo: false
4254
fail-fast: false
@@ -100,18 +112,47 @@ jobs:
100112
101113
if: matrix.package == 'packages/orchestrator'
102114

115+
- name: Install gotestsum
116+
run: go install gotest.tools/gotestsum@v1.13.0
117+
118+
# sudo strips PATH; pass it through so gotestsum/go resolve to the runner's
119+
# toolchain (1.25.9) instead of /usr/bin/go. Trap chowns reports back to
120+
# the runner so uploads can read them even when tests fail.
103121
- name: Run tests that require sudo
104122
working-directory: ${{ matrix.package }}
105123
run: |
106-
# Run tests. The '-E' flag is required to allow root to use the correct cache path.
107-
sudo -E `which go` test -v -timeout 20m ${{ matrix.test_path }}
124+
trap 'sudo chown "$(id -u):$(id -g)" coverage.txt junit.xml 2>/dev/null || true' EXIT
125+
sudo -E env "PATH=$PATH" gotestsum --junitfile=junit.xml --format=standard-verbose \
126+
-- -timeout 20m -coverprofile=coverage.txt -covermode=atomic ${{ matrix.test_path }}
108127
if: matrix.sudo == true
109128

110129
- name: Run tests
111130
working-directory: ${{ matrix.package }}
112-
run: go test -v -timeout 20m ${{ matrix.test_path }}
131+
run: |
132+
gotestsum --junitfile=junit.xml --format=standard-verbose \
133+
-- -timeout 20m -coverprofile=coverage.txt -covermode=atomic ${{ matrix.test_path }}
113134
if: matrix.sudo == false
114135

136+
- name: Upload coverage to Codecov
137+
if: ${{ !cancelled() && env.CODECOV_TOKEN != '' &&
138+
hashFiles(format('{0}/coverage.txt', matrix.package)) != '' }}
139+
uses: codecov/codecov-action@v5
140+
with:
141+
token: ${{ secrets.CODECOV_TOKEN }}
142+
files: ${{ matrix.package }}/coverage.txt
143+
flags: ${{ matrix.flag }}
144+
disable_search: true
145+
146+
- name: Upload test results to Codecov
147+
if: ${{ !cancelled() && env.CODECOV_TOKEN != '' &&
148+
hashFiles(format('{0}/junit.xml', matrix.package)) != '' }}
149+
uses: codecov/test-results-action@v1
150+
with:
151+
token: ${{ secrets.CODECOV_TOKEN }}
152+
files: ${{ matrix.package }}/junit.xml
153+
flags: ${{ matrix.flag }}
154+
disable_search: true
155+
115156
validate-iac:
116157
name: Validate terraform
117158
runs-on: ubuntu-24.04

.github/workflows/pull-request.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,20 @@ jobs:
2828
uses: ./.github/workflows/out-of-order-migrations.yml
2929
unit-tests:
3030
uses: ./.github/workflows/pr-tests.yml
31+
secrets:
32+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
3133
arm64-tests:
3234
uses: ./.github/workflows/pr-tests-arm64.yml
35+
secrets:
36+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
3337
integration-tests:
3438
needs: [out-of-order-migrations]
3539
uses: ./.github/workflows/integration_tests.yml
3640
with:
3741
# Only publish the results for same-repo PRs
3842
publish: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
43+
secrets:
44+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
3945
publish-test-results:
4046
needs:
4147
- unit-tests

.github/workflows/push-main.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ jobs:
1717

1818
unit-tests:
1919
uses: ./.github/workflows/pr-tests.yml
20+
secrets:
21+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2022
integration-tests:
2123
uses: ./.github/workflows/integration_tests.yml
2224
with:
2325
publish: true
26+
secrets:
27+
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2428
publish-test-results:
2529
needs:
2630
- unit-tests

codecov.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Coverage signal only — never gates merges. Carries forward per-flag so
2+
# shards that didn't run keep their last-known number instead of dropping to 0%.
3+
4+
coverage:
5+
status:
6+
project:
7+
default: { informational: true }
8+
patch:
9+
default: { informational: true }
10+
11+
flag_management:
12+
default_rules:
13+
carryforward: true
14+
15+
component_management:
16+
individual_components:
17+
- { component_id: api, paths: ["packages/api/**"] }
18+
- { component_id: client-proxy, paths: ["packages/client-proxy/**"] }
19+
- { component_id: db, paths: ["packages/db/**"] }
20+
- { component_id: docker-reverse-proxy, paths: ["packages/docker-reverse-proxy/**"] }
21+
- { component_id: envd, paths: ["packages/envd/**"] }
22+
- { component_id: orchestrator, paths: ["packages/orchestrator/**"] }
23+
- { component_id: shared, paths: ["packages/shared/**"] }
24+
25+
ignore:
26+
- "**/*.gen.go" # oapi-codegen
27+
- "**/*.sql.go" # sqlc
28+
- "**/*.pb.go" # protoc (message + grpc)
29+
- "**/mocks/**" # mockery
30+
- "**/testdata/**"

0 commit comments

Comments
 (0)