faisalmirza / diff-coverage
Calculate code coverage for git diff (changed lines only)
Installs: 56
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
Language:Shell
pkg:composer/faisalmirza/diff-coverage
Requires
- php: ^8.1
- exussum12/coverage-checker: ^1.0
This package is auto-updated.
Last update: 2026-01-25 17:16:09 UTC
README
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
Why diff-coverage?
| 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 |
Requirements
- PHP 8.1+
- Git
- Xdebug or PCOV (for coverage)
Installation
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" } }
Quick Start
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
Configuration is loaded in layers (each layer overrides the previous):
Defaults → .diff-coverage.json → CLI arguments
Option 1: Zero Configuration (Recommended)
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.
Option 2: Configuration File
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 |
Option 3: CLI Arguments
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 |
Usage Examples
# 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"
Performance
Freshness Detection
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
CI Integration
GitHub Actions
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
GitLab CI
diff-coverage: stage: test script: - composer install - ./vendor/bin/diff-coverage -b origin/$CI_MERGE_REQUEST_TARGET_BRANCH_NAME -t 80 only: - merge_requests
Bitbucket Pipelines
pipelines: pull-requests: '**': - step: name: Diff Coverage script: - composer install - ./vendor/bin/diff-coverage -b origin/main -t 80
Exit Codes
| Code | Meaning |
|---|---|
0 |
Coverage meets threshold (or no changes detected) |
1 |
Coverage below threshold, tests failed, or configuration error |
How It Works
- Detect your test framework (Laravel/Pest/PHPUnit)
- Run tests with coverage (unless skipped or fresh)
- Generate git diff against the target branch
- Filter coverage report to only changed lines
- Calculate coverage percentage for those lines
- Exit with success/failure based on threshold
Under the hood, diff-coverage uses exussum12/coverage-checker for the diff filtering logic.
Troubleshooting
"No changes detected"
Your branch has no diff against the target branch. This is normal for the main branch.
"Coverage file not found"
Tests may have failed, or the coverage file path is incorrect. Check:
- Tests pass when run directly
- The coverage file path in your config matches where your tests write it
Coverage seems wrong
Ensure you're comparing against the correct branch:
./vendor/bin/diff-coverage -b origin/main # not just 'main'
Tests run every time
The freshness check compares timestamps. If your CI always clones fresh, tests will always run. This is expected behavior in CI.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License. See LICENSE for details.