Run the undercoverage check in the rspec:coverage job
What does this MR do and why?
rspec:undercoverage runs after rspec:coverage at the end of every MR pipeline that changes code (#636505 (closed)). In 29 of these jobs from tier 3 pipelines on 2026-10-26 and 2026-10-27, it took a median of 91s (p80 103s), of which:
| Part | p50 | p80 |
|---|---|---|
Runner setup, cache, and downloading the repository and the rspec:coverage artifacts again |
63s | 65s |
git diff --stat, which only prints the diff |
7s | 9s |
scripts/undercoverage |
12s | 20s |
It also started 1.6s (median) after rspec:coverage finished.
This MR runs scripts/undercoverage in rspec:coverage, after it merges the coverage data, and removes rspec:undercoverage:
-
rspec:coveragesetsUNDERCOVERAGE_FAILURE: "block", and its rules override it withallowwhererspec:undercoveragehadallow_failure: true, so a failed check behaves as it did inrspec:undercoverage:Pipeline Before With this MR MR with pipeline:run-all-rspecblocks block: the job exits with 1Tier 3 MR that changes code blocks block: the job exits with 1Other MR that changes code allowed to fail allow: the job exits with 202, whichallow_failure: exit_codesallowsMR with pipeline:skip-undercoveragerspec:undercoveragedoesn't runthe check is skipped Scheduled maintenance and coverage export pipelines rspec:undercoveragedoesn't runthe check is skipped (not a merge request pipeline) -
rspec:coverageruns in the same pipelines as before. Only the order of its rules changed, so that the undercoverage rules match first. -
Its artifacts are now uploaded
when: always, so the coverage report is still available when the check fails. -
The docs describe the check as part of
rspec:coverage, and say thatpipeline:skip-undercoverageskips the check. It no longer lets a failing job pass, as they still said. Exit code 202 is listed next to the custom exit codes.
Trade-offs:
- A failed check makes
rspec:coveragefail (or fail with a warning below tier 3) instead ofrspec:undercoverage. - Retrying a failed check reruns the coverage merge too, about 1.5 minutes longer than retrying
rspec:undercoverage.
Results
Tier 3 pipeline 2997667689 of this MR, against the medians of 113 tier 3 MR pipelines from 2026-10-26 and 2026-10-27:
| Time from the last RSpec job to the end of the pipeline's coverage jobs | Before (median, p20–p80) | With this MR |
|---|---|---|
End of rspec:coverage |
274s (192–317s) | 328s |
End of rspec:undercoverage |
371s (294–445s) | (job removed) |
So the pipeline ends about 45s sooner. In job 17528110690, rspec:coverage took 274s, of which the undercoverage check took 55s:
| Step | Time |
|---|---|
git diff ... --stat |
40s |
scripts/undercoverage |
15s |
Both are slower than in rspec:undercoverage (7s and 12s), probably because they read every file in the checkout, which the coverage merge has pushed out of the page cache by then. In an earlier pipeline of this MR without git diff --stat, scripts/undercoverage took 60–66s instead (job 17524751143 and its retry), so this MR keeps it.
!265692 (merged) upgrades undercover to 0.9.0 and compares commits instead of the working tree, which takes the check to about 2s in CI. With both, the pipeline would end about 90s sooner. Whichever of the two merges last can then remove git diff --stat.
How to set up and validate locally
The script's decisions can be checked with a stub bundle that fails like scripts/undercoverage does:
-
Save the last
scriptentry ofrspec:coverageasscript.sh. -
Create a
bundlescript on yourPATHthat runsexit ${FAKE_UNDERCOVER_EXIT:-0}. -
Run
bash script.shwith these variables and check the exit code:Variables Expected exit code UNDERCOVERAGE_FAILURE=block FAKE_UNDERCOVER_EXIT=10 (not an MR pipeline) CI_MERGE_REQUEST_IID=1 UNDERCOVERAGE_FAILURE=block CI_MERGE_REQUEST_LABELS=pipeline:skip-undercoverage FAKE_UNDERCOVER_EXIT=10 CI_MERGE_REQUEST_IID=1 UNDERCOVERAGE_FAILURE=block0 CI_MERGE_REQUEST_IID=1 UNDERCOVERAGE_FAILURE=block FAKE_UNDERCOVER_EXIT=11 CI_MERGE_REQUEST_IID=1 UNDERCOVERAGE_FAILURE=allow FAKE_UNDERCOVER_EXIT=1202 CI_MERGE_REQUEST_IID=1 UNDERCOVERAGE_FAILURE=allow0
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist.