Skip to content

Commit 284fa7e

Browse files
27Bslash6Mark S
andauthored
ci(coverage): enforce PR coverage floor in pytest, not via Codecov (LAB-3609) (#291)
Add --cov-fail-under=82 to the final PR pytest invocation so the combined unit+critical total is gated inside the job, independent of the Codecov upload succeeding. A dropped/degraded upload (fork-PR tokenless 429, Codecov outage) can no longer answer the total-coverage question with a stale carryforward. Codecov keeps the finer-grained new-code (patch) status. - .github/workflows/ci.yml: floor on the --cov-append final run; residual-risk comment rewritten (total floor now local; Codecov owns patch). - Makefile: test-cov floor 60 -> 82, mirrors CI (full suite clears it). - CONTRIBUTING.md: document the enforced total floor. 82 = current main PR-path cumulative TOTAL 84.20% (py3.12, only PR matrix version) minus codecov.yml declared 2% tolerance, floored. Co-authored-by: Mark S <ray@insighttimer.com>
1 parent 15a24de commit 284fa7e

3 files changed

Lines changed: 29 additions & 9 deletions

File tree

‎.github/workflows/ci.yml‎

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,18 @@ jobs:
136136
--cov-report=term \
137137
--junitxml=junit-unit.xml \
138138
-o junit_family=legacy
139+
# --cov-fail-under evaluates the *combined* unit+critical total (the
140+
# --cov-append accumulation), so it gates this final invocation only, and
141+
# reddens the job in pytest independent of the Codecov upload (rationale:
142+
# residual-risk note below). 82 = lowest same-repo PR cumulative TOTAL
143+
# across the PR matrix minus codecov.yml's declared 2% tolerance, floored;
144+
# keep it in step with the Makefile test-cov floor.
139145
uv run pytest tests/critical/ -m "not slow" \
140146
--cov=src/cachekit \
141147
--cov-append \
142148
--cov-report=xml \
143149
--cov-report=term \
150+
--cov-fail-under=82 \
144151
--junitxml=junit.xml \
145152
-o junit_family=legacy
146153
@@ -192,17 +199,22 @@ jobs:
192199
# what this ticket is hardening. The flag therefore applies exactly where OIDC
193200
# really authenticates.
194201
#
195-
# ACCEPTED RESIDUAL RISK, stated because the scoping creates it: on a fork PR a
196-
# dropped tokenless upload is still silent, and carryforward then answers the
197-
# patch question with an earlier commit's numbers — a stale green on exactly the
198-
# least-trusted contributions. The wrapper's CLI signature check is likewise
202+
# RESIDUAL, now narrowed to the PATCH status: the TOTAL-coverage floor no
203+
# longer depends on the upload — the final PR pytest run enforces it with
204+
# `--cov-fail-under` (above), so a dropped or degraded upload can no longer
205+
# answer the total-coverage question with an earlier commit's carryforward;
206+
# the job reddens inside pytest first, on same-repo and fork PRs alike. What
207+
# Codecov still solely owns is the new-code PATCH status: it is diff-level,
208+
# stays `carryforward: true`, and is not enforced locally, so on a fork PR
209+
# whose tokenless upload is silently dropped it can still answer "is this
210+
# PR's new code 80% covered?" with a previous run's numbers. Accepted: that
211+
# status is a reporting signal, not a merge floor, and the local total floor
212+
# already catches a gross coverage regression on the same run.
213+
# The wrapper's CLI signature check is likewise
199214
# unenforced there (fail_ci_if_error is its switch; see the pin note below).
200215
# Accepted because a fork PR cannot reach the self-hosted `cachekit` runner
201216
# without a maintainer approving the run, no fork PR has run here to date, and
202-
# a tampered binary reaches same-repo runs first, where it fails closed. The real
203-
# fix is to stop depending on an external upload for the floor (a local
204-
# `--cov-fail-under` on the PR pytest invocation); that is a coverage-policy
205-
# change, tracked separately rather than smuggled into this diff.
217+
# a tampered binary reaches same-repo runs first, where it fails closed.
206218
#
207219
# Do not re-pin below v7.0.0 (fb8b3582): releases published before Codecov's
208220
# June 2026 keybase migration fetch the CLI signing key from a deleted account,

‎CONTRIBUTING.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ make test-cov
194194
- Aim for >85% coverage for new code
195195
- All public APIs must have tests
196196
- Edge cases and error conditions must be tested
197+
- CI enforces a **total-coverage floor** inside pytest on every PR
198+
(`--cov-fail-under`, value in `.github/workflows/ci.yml`), independent of the
199+
Codecov upload — a PR whose combined coverage falls below it fails the `Tests`
200+
job. `make test-cov` enforces the same floor locally. Codecov still reports the
201+
finer-grained new-code (patch) coverage on top of this.
197202

198203
**Rust Test Coverage**:
199204
- ByteStorage module: 82% coverage (measured via LLVM source-based coverage)

‎Makefile‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,17 @@ test-quick: setup-logs ## Run fast tests (skip slow tests)
125125
fi
126126
@echo "$(GREEN)✓ Fast tests completed$(RESET)"
127127

128+
# --cov-fail-under mirrors the PR coverage floor in .github/workflows/ci.yml.
129+
# This target runs the full suite (>= the PR path's unit+critical coverage), so
130+
# it clears the floor with margin. Keep the two values in step.
128131
test-cov: setup-logs ## Run tests with coverage
129132
@echo "$(BLUE)Running tests with coverage...$(RESET)"
130133
@echo "$(YELLOW)Logging to $(LOG_TEST_DIR)/cov_$(TIMESTAMP).log$(RESET)"
131134
@if ! uv run pytest $(TEST_DIR) \
132135
--cov=$(PACKAGE_NAME) \
133136
--cov-report=term-missing \
134137
--cov-report=html:reports/htmlcov \
135-
--cov-fail-under=60 \
138+
--cov-fail-under=82 \
136139
-q \
137140
--tb=short \
138141
--basetemp=$(TEST_BASETEMP) 2>&1 | tee $(LOG_TEST_DIR)/cov_$(TIMESTAMP).log; then \

0 commit comments

Comments
 (0)