| Install | |
|---|---|
composer require faisalmirza/diff-coverage |
|
| Latest Version: | v1.0.2 |
| PHP: | ^8.1 |
diff-coverage
Ensure new code is tested. Check coverage for only the lines you changed.
diff-coverage runs your tests with coverage and validates that changed lines meet your coverage threshold. Stop debating overall coverage percentages—focus on what matters: new code should be tested.
./vendor/bin/diff-coverage
============================================
Diff Coverage Check
============================================
Branch: origin/main
Threshold: 100%
Coverage file: tests/coverage/clover.xml
============================================
Running tests with coverage...
✓ 142 tests passed
Checking diff coverage...
============================================
Coverage: 94.2% (threshold: 100%)
Uncovered lines:
src/Services/PaymentService.php:45
src/Services/PaymentService.php:46
| Traditional Coverage | Diff Coverage |
|---|---|
| "We need 80% overall coverage" | "New code must be tested" |
| Legacy code drags down metrics | Only measures what you changed |
| Hard to improve incrementally | Every PR can pass |
| Debates about thresholds | Clear, actionable feedback |
composer require --dev faisalmirza/diff-coverage
Note: If not yet on Packagist, install from GitHub:
{ "repositories": [ {"type": "vcs", "url": "https://github.com/faisalmirza/diff-coverage"} ], "require-dev": { "faisalmirza/diff-coverage": "dev-master" } }
For Laravel, Pest, or PHPUnit projects, just run:
./vendor/bin/diff-coverage
The tool auto-detects your test framework and runs tests with coverage automatically.
Configuration is loaded in layers (each layer overrides the previous):
Defaults → .diff-coverage.json → CLI arguments
diff-coverage auto-detects your project type and enables parallel testing if paratest is installed:
| Framework | Detection | Source Path | Coverage Path |
|---|---|---|---|
| Laravel | artisan file |
app, src |
tests/coverage/clover.xml |
| Symfony | bin/console + composer.json |
src |
var/coverage/clover.xml |
| CakePHP | bin/cake file |
src |
tmp/coverage/clover.xml |
| CodeIgniter | spark file |
app |
build/coverage/clover.xml |
| Yii | yii file |
src |
tests/coverage/clover.xml |
| Laminas | config/application.config.php |
module, src |
data/coverage/clover.xml |
| Pest | vendor/bin/pest |
src, app |
coverage/clover.xml |
| PHPUnit | phpunit.xml or vendor/bin/phpunit |
src, app |
coverage/clover.xml |
Parallel Testing: Automatically enabled when brianium/paratest is installed.
Create .diff-coverage.json in your project root:
{
"branch": "origin/main",
"threshold": 80,
"coverage_file": "tests/coverage/clover.xml",
"test_cmd": "php artisan test --parallel --coverage-clover=tests/coverage/clover.xml",
"source_paths": ["app", "src"]
}
All fields are optional. Only specify what you want to override.
| Field | Type | Default | Description |
|---|---|---|---|
branch |
string | origin/main |
Branch to compare against |
threshold |
integer | 100 |
Minimum coverage percentage for changed lines |
coverage_file |
string | (auto-detected) | Path to Clover XML coverage file |
test_cmd |
string | (auto-detected) | Command to run tests with coverage |
source_paths |
array | ["app", "src"] |
Directories to check for freshness |
Override any setting via command line:
./vendor/bin/diff-coverage [OPTIONS]
| Option | Description | Example |
|---|---|---|
-b, --branch |
Branch to compare | -b origin/develop |
-t, --threshold |
Coverage threshold | -t 80 |
-c, --coverage |
Coverage file path | -c build/coverage.xml |
-T, --test-cmd |
Test command | -T "phpunit --coverage-clover=cov.xml" |
-f, --force |
Force re-run tests | -f |
-s, --skip-tests |
Use existing coverage | -s |
-h, --help |
Show help | -h |
# Use auto-detected defaults
./vendor/bin/diff-coverage
# Compare against develop branch with 80% threshold
./vendor/bin/diff-coverage -b origin/develop -t 80
# Skip running tests (use existing coverage file)
./vendor/bin/diff-coverage -s
# Force re-run tests even if coverage file is fresh
./vendor/bin/diff-coverage -f
# Custom test command
./vendor/bin/diff-coverage -T "vendor/bin/phpunit --testsuite=unit --coverage-clover=coverage.xml"
diff-coverage automatically skips running tests if your coverage file is newer than your source files. This speeds up repeated runs during development.
$ ./vendor/bin/diff-coverage
Coverage file is fresh, skipping tests
(use --force to re-run tests)
To always run tests: ./vendor/bin/diff-coverage -f
To never run tests: ./vendor/bin/diff-coverage -s
name: Tests
on:
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Required for git diff
- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: xdebug
- name: Install dependencies
run: composer install
- name: Check diff coverage
run: ./vendor/bin/diff-coverage -t 80
diff-coverage:
stage: test
script:
- composer install
- ./vendor/bin/diff-coverage -b origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME -t 80
only:
- merge_requests
pipelines:
pull-requests:
'**':
- step:
name: Diff Coverage
script:
- composer install
- ./vendor/bin/diff-coverage -b origin/main -t 80
| Code | Meaning |
|---|---|
0 |
Coverage meets threshold (or no changes detected) |
1 |
Coverage below threshold, tests failed, or configuration error |
Under the hood, diff-coverage uses exussum12/coverage-checker for the diff filtering logic.
Your branch has no diff against the target branch. This is normal for the main branch.
Tests may have failed, or the coverage file path is incorrect. Check:
Ensure you're comparing against the correct branch:
./vendor/bin/diff-coverage -b origin/main # not just 'main'
The freshness check compares timestamps. If your CI always clones fresh, tests will always run. This is expected behavior in CI.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT License. See LICENSE for details.