@@ -105,24 +105,17 @@ jobs:
105105 - name : Typecheck
106106 run : npm run typecheck
107107
108- # Unit/integration suite + coverage, sharded across parallel runners to cut the
109- # critical-path wall-clock. Each shard publishes its PARTIAL lcov as a build
110- # artifact; the `coverage-upload` job merges all 3 and uploads to Codecov ONCE,
111- # so Codecov computes codecov/patch a single time on the complete report instead
112- # of flapping FAILURE->SUCCESS as each shard's partial upload arrived. vitest's
113- # local 90% backstop is disabled here (COVERAGE_NO_THRESHOLDS) because a single
114- # shard only exercises part of the tree; the backstop still runs on the full
115- # local `npm run test:coverage`.
108+ # Unit/integration suite + coverage. This used to fan out into 3 GitHub matrix
109+ # jobs because hosted-runner minutes were the bottleneck. On the self-hosted
110+ # runner, the extra checkout/install/artifact fan-in surface has become more
111+ # expensive and fragile than the saved wall-clock, so keep coverage in one job
112+ # and let Vitest use the runner's local worker concurrency.
116113 test :
117114 name : test
118115 needs : changes
119116 if : ${{ github.event_name == 'push' || needs.changes.outputs.backend == 'true' }}
120117 runs-on : ${{ fromJSON((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) && '["ubuntu-latest"]' || '["self-hosted","gittensory"]') }}
121118 timeout-minutes : 15
122- strategy :
123- fail-fast : false
124- matrix :
125- shard : [1, 2, 3]
126119 steps :
127120 - name : Checkout
128121 uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
@@ -148,104 +141,35 @@ jobs:
148141 exit 1
149142 - name : Prepare test reports dir
150143 run : mkdir -p reports/junit
151- - name : Test with coverage (shard ${{ matrix.shard }}/3)
144+ - name : Test with coverage
152145 id : coverage
153146 env :
154- COVERAGE_NO_THRESHOLDS : " 1"
155- VITEST_JUNIT_PATH : reports/junit/vitest-shard-${{ matrix.shard }}.xml
156- run : npm run test:coverage -- --shard=${{ matrix.shard }}/3
147+ VITEST_JUNIT_PATH : reports/junit/vitest.xml
148+ run : npm run test:coverage -- --maxWorkers=100%
157149 - name : Test failure guidance
158150 if : ${{ failure() && steps.coverage.conclusion == 'failure' }}
159151 run : |
160- echo "::error title=Tests::A test in shard ${{ matrix.shard }}/3 failed."
161- echo "Coverage itself is gated by Codecov on changed lines (codecov/patch), computed from the merged shards."
162- echo "Reproduce locally with the full suite: 'npm run test:coverage' (no sharding)."
163- - name : Upload partial coverage artifact (shard ${{ matrix.shard }}/3)
164- # A passing shard writes coverage/lcov.info; a FAILING shard writes none
165- # (vitest coverage.reportOnFailure defaults to false). Gate on success() so a
166- # red shard skips this (and fails the job → fails `validate`), and use
167- # if-no-files-found:error so a green shard always ships exactly one lcov and
168- # the merge job can assert all 3 arrived. The single Codecov upload happens
169- # in the coverage-upload job below.
152+ echo "::error title=Tests::The backend test coverage suite failed."
153+ echo "Coverage itself is gated by Codecov on changed lines (codecov/patch), computed from the complete lcov generated by this job."
154+ echo "Reproduce locally with: 'npm run test:coverage'."
155+ - name : Upload coverage to Codecov
170156 if : ${{ success() }}
171- uses : actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
172- with :
173- name : coverage-lcov-shard-${{ matrix.shard }}
174- path : ./coverage/lcov.info
175- if-no-files-found : error
176- retention-days : 1
177- - name : Upload Vitest results to Codecov
178- if : ${{ !cancelled() }}
179157 uses : codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
180158 with :
181159 token : ${{ secrets.CODECOV_TOKEN }}
182- files : ./reports/junit/vitest-shard-${{ matrix.shard }}.xml
183- report_type : test_results
160+ files : ./coverage/lcov.info
184161 disable_search : true
185162 override_branch : ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }}
186163 override_commit : ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
187164 override_pr : ${{ github.event_name == 'pull_request' && github.event.pull_request.number || '' }}
188165 fail_ci_if_error : false
189-
190- # Merge the 3 shard lcovs into one complete report and upload to Codecov ONCE.
191- # The shards no longer upload coverage directly: that made Codecov score
192- # codecov/patch on a partially-merged subset of shards as each upload arrived, so
193- # the status flapped FAILURE->SUCCESS until all 3 landed. A single upload of the
194- # merged report means Codecov computes patch exactly once on complete data. This
195- # job inherits `test`'s path filter and only runs when ALL shards passed, so a
196- # non-backend PR (which skips `test`) emits no codecov/patch context at all — and
197- # codecov/patch is intentionally NOT a required branch-protection context, so an
198- # absent status never strands the review gate at pending.
199- coverage-upload :
200- name : coverage-upload
201- needs : [changes, test]
202- if : ${{ (github.event_name == 'push' || needs.changes.outputs.backend == 'true') && needs.test.result == 'success' }}
203- runs-on : ${{ fromJSON((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) && '["ubuntu-latest"]' || '["self-hosted","gittensory"]') }}
204- timeout-minutes : 10
205- steps :
206- - name : Checkout
207- uses : actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
208- with :
209- # Full history so Codecov can resolve the merge base for patch coverage.
210- fetch-depth : 0
211- ref : ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
212- - name : Download shard coverage artifacts
213- uses : actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
214- with :
215- pattern : coverage-lcov-shard-*
216- path : shard-coverage
217- - name : Collect the shard lcovs into stable paths
218- # download-artifact nests each artifact under its own directory, and
219- # upload-artifact's internal layout for a single file is version-dependent, so
220- # find the lcovs wherever they landed and copy them to fixed names for an
221- # explicit Codecov upload. Assert exactly 3 — fewer means an upload/runner
222- # anomaly (needs.test.result already gated this job to all-shards-green), and a
223- # hard CI failure (one-shot auto-close, recoverable) beats silently scoring
224- # codecov/patch on a subset.
225- run : |
226- mkdir -p merged-lcov
227- n=0
228- for f in $(find shard-coverage -name lcov.info | sort); do
229- n=$((n + 1))
230- cp "$f" "merged-lcov/shard-${n}.info"
231- done
232- echo "Collected ${n} shard lcov file(s)."
233- if [ "${n}" -ne 3 ]; then
234- echo "::error title=Coverage::Expected 3 shard lcov files, found ${n}."
235- exit 1
236- fi
237- - name : Upload all shard coverage to Codecov in one report
238- # All 3 partial lcovs in a SINGLE codecov-action invocation = ONE upload;
239- # Codecov merges them server-side before computing codecov/patch (it "always
240- # merges report data" and holds notifications until merge completes), so the
241- # status is computed once on the complete report — eliminating the partial
242- # flap that 3 separate per-shard uploads caused. fail_ci_if_error stays false
243- # so a Codecov outage cannot fail CI / auto-close an honest PR (codecov/patch
244- # is simply absent, which the gate treats as not-a-FAILURE).
166+ - name : Upload Vitest results to Codecov
167+ if : ${{ !cancelled() }}
245168 uses : codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
246169 with :
247170 token : ${{ secrets.CODECOV_TOKEN }}
248- files : ./merged-lcov/shard-1.info,./merged-lcov/shard-2.info,./merged-lcov/shard-3.info
171+ files : ./reports/junit/vitest.xml
172+ report_type : test_results
249173 disable_search : true
250174 override_branch : ${{ github.event_name == 'pull_request' && github.event.pull_request.head.ref || github.ref_name }}
251175 override_commit : ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
@@ -406,7 +330,7 @@ jobs:
406330 # Path-filtered jobs report "skipped", which is treated as success.
407331 validate :
408332 name : validate
409- needs : [changes, lint, test, coverage-upload, workers, mcp, rees, ui, security]
333+ needs : [changes, lint, test, workers, mcp, rees, ui, security]
410334 if : ${{ always() }}
411335 runs-on : ${{ fromJSON((github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true) && '["ubuntu-latest"]' || '["self-hosted","gittensory"]') }}
412336 timeout-minutes : 2
0 commit comments