This project uses automated coverage checks to prevent test coverage from decreasing.
Developers run coverage locally and commit the baseline file. CI validates both that the developer ran coverage correctly AND that coverage didn't decrease.
Triggers: Every pull request to main
What it does:
-
Fetches baseline from main branch - the current production baseline
-
Reads baseline from PR branch - the baseline you committed
-
Runs coverage fresh in CI - generates actual coverage from your code
-
Performs two validations:
Validation 1: Did you run coverage locally?
- ✅ PASS if
CI coverage === PR baseline(you ran coverage correctly) - ❌ FAIL if
CI coverage !== PR baseline(you forgot to run coverage or tampered with file)
Validation 2: Did coverage decrease?
- ✅ PASS if
CI coverage >= main baseline(coverage maintained or improved) - ❌ FAIL if
CI coverage < main baseline(coverage decreased)
- ✅ PASS if
Security Model:
- ✅ Can't skip running coverage - CI checks if your committed baseline matches actual coverage
- ✅ Can't decrease coverage - CI checks if your coverage is below main's baseline
- ✅ Can't cheat - CI regenerates coverage fresh and validates against both baselines
- ✅ Can't commit invalid baseline - CI validates JSON format before processing
- ✅ Can't skip baseline file - CI fails immediately if baseline file is missing
- ✅ Visible in PR - Baseline changes are visible in the PR diff
Effect: Coverage can only go up or stay the same, never down! This creates a "ratchet effect" where quality continuously improves. ✅
# Run coverage
npm run coverage
# Check if coverage maintained (compares against baseline)
npm run coverage:check
# Update baseline (after improving coverage)
npm run coverage:update-baselineIMPORTANT: You must run coverage locally and commit the baseline file with your PR.
Step-by-step:
- Make your code changes
- Run coverage locally:
npm run coverage
- Update the baseline file:
npm run coverage:update-baseline
- Commit the baseline file:
git add coverage-baseline.json git commit -m "chore: update coverage baseline" - Push your PR
What CI validates:
- ✅ Check 1: Your committed baseline matches CI coverage (proves you ran coverage)
- ✅ Check 2: Your coverage is >= main's baseline (proves coverage didn't drop)
If CI fails:
- "No coverage-baseline.json found in PR" → You forgot to commit the baseline file. Run steps 2-4 above and push.
- "coverage-baseline.json is not valid JSON" → The baseline file is corrupted. Run
npm run coverage:update-baselineand commit. - "CI coverage doesn't match PR baseline" → You forgot to update the baseline. Run steps 2-3 above and push.
- "Coverage decreased" → Add more tests to maintain or improve coverage.
No special maintenance needed! Developers commit their own baseline files.
The workflow only validates - it doesn't modify anything. When a PR merges, the updated baseline goes to main automatically.
Current baseline (as of initial setup):
- Lines: 96.03%
- Functions: 98.27%
- Branches: 86.19%
- Statements: 96.03%
- Uses Hardhat's built-in coverage tool (generates
coverage/lcov.info) - Parses LCOV format to extract: lines, functions, branches, statements
- Stores baseline in
coverage-baseline.jsonat repository root - Scripts:
scripts/check-coverage.ts- Local validation (compares coverage against baseline)scripts/get-coverage-percentage.ts- Extracts coverage percentage from lcov.info (used by CI)
The workflow copies .env.example to .env to enable fork tests with public RPC endpoints during coverage runs.
To enforce coverage checks, enable branch protection on main:
- GitHub Settings → Branches → Branch protection rules
- Add rule for
mainbranch - Enable "Require status checks to pass before merging"
- Select "coverage" as required check