Enforce branch-coverage gate on pre-push (mirror CI locally) (#40)
## What Makes the local **pre-push** git hook enforce the same branch-coverage gate as CI, so contributors catch coverage regressions before they push (not just in CI). Two files change: the hook (`.pre-commit-config.yaml`) and a doc note (`CONTRIBUTING.md`). ## The hook (`.pre-commit-config.yaml`, `run-tests` pre-push hook) Mirrors `.github/workflows/test.yml`: 1. Runs the full suite with **real torch** + branch coverage: `pytest --cov=NRS --cov-branch --cov-report=xml`. 2. Then the diff gate: `diff-cover coverage.xml --compare-branch=origin/main --branch-coverage --fail-under=90` — **blocks the push if changed code drops below 90% branch coverage**. 3. **Graceful skip** with a warning if `uv` isn't installed (unchanged behavior); **fails loudly** if `origin/main` can't be resolved (nothing to diff against). 4. Cleans up `coverage.xml`/`.coverage` on both success and failure paths, while preserving the exit code so a failing gate still blocks. All existing hooks are untouched: `prevent-push-to-main`, `prevent-commit-to-main`, ruff, and the standard hooks. Only the `run-tests` entry/name changed. Coverage stays on the **pre-push** stage only — commits remain fast. ### Uses `uvx`, not `uv run` (important) The invocations use `uvx --with torch --with pytest-cov --with diff-cover ...`. An earlier `uv run` version was verified to **break the push**: `uv run` syncs the project env and mutates `uv.lock` mid-hook, which conflicts with pre-commit's stash/restore and aborts the push (`Stashed changes conflicted with hook auto-fixes... Rolling back`). `uvx` runs in an ephemeral env and never touches `uv.lock` — matching how the project runs tests locally everywhere else. ## Docs (`CONTRIBUTING.md`) Added a short note right after the `pre-commit install --hook-type pre-push` instruction explaining the gate, the `uv` requirement (skips with a warning otherwise), and that `origin/main` must be fetched for the diff. ## Validation - Ran the hook's exact command: **45 tests pass**, diff-cover passes; `git status` after confirms `uv.lock` is **not** mutated (the stash-conflict path can't recur). - `uvx pre-commit run --hook-stage pre-push` completes with no stash-conflict rollback. - **This very PR's push exercised the hook live** — it ran the gate and pushed cleanly. - Only `.pre-commit-config.yaml` and `CONTRIBUTING.md` changed; `pyproject.toml`/`uv.lock`/tests/`NRS/` untouched. ## Out-of-scope observation The `ruff-format` hook has no `stages:` restriction, so a manual `--all-files` dry-run reformats files broadly. Pre-existing; not addressed here.
This commit is contained in:
@@ -36,8 +36,8 @@ repos:
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: run-tests
|
||||
name: Run pytest tests
|
||||
entry: bash -c 'if command -v uv > /dev/null 2>&1; then uv run pytest tests/ || exit 1; else echo "WARNING - uv not found, skipping tests"; fi'
|
||||
name: Run pytest with branch-coverage gate
|
||||
entry: bash -c 'if ! command -v uv > /dev/null 2>&1; then echo "WARNING - uv not found, skipping tests and coverage gate"; exit 0; fi; if ! git rev-parse --verify --quiet origin/main > /dev/null 2>&1; then echo "ERROR - origin/main not resolvable locally; fetch origin main and retry"; exit 1; fi; uvx --with torch --with pytest-cov --with diff-cover pytest --cov=NRS --cov-branch --cov-report=xml --cov-report=term-missing tests/; status=$?; if [ $status -ne 0 ]; then rm -f coverage.xml .coverage; exit $status; fi; uvx --with diff-cover diff-cover coverage.xml --compare-branch=origin/main --branch-coverage --fail-under=90; cov_status=$?; rm -f coverage.xml .coverage; exit $cov_status'
|
||||
language: system
|
||||
stages: [pre-push]
|
||||
always_run: true
|
||||
|
||||
@@ -54,6 +54,12 @@ Thank you for your interest in contributing! This document provides guidelines f
|
||||
pre-commit install --hook-type pre-push
|
||||
```
|
||||
|
||||
The pre-push hook runs the full test suite with branch coverage and blocks
|
||||
the push if changed code drops below 90% branch coverage (via pytest-cov +
|
||||
diff-cover, mirroring CI). It needs `uv` installed — if `uv` isn't found,
|
||||
the check is skipped with a warning — and `origin/main` fetched locally so
|
||||
there's something to diff against.
|
||||
|
||||
6. **Verify setup**:
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user