From a75dbc0b562a32ca31f3ab06c35cf7ef34c25cac Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 1 Dec 2025 14:57:53 +0100 Subject: [PATCH 1/2] CoverageGuard: enforce 70% coverage for all methods; add missing tests --- composer.json | 9 ++- composer.lock | 67 ++++++++++++++++++- coverage-guard.php | 12 ++++ phpunit.xml.dist | 6 ++ ...rbidCheckedExceptionInCallableRuleTest.php | 3 +- .../code.php | 13 ++++ 6 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 coverage-guard.php diff --git a/composer.json b/composer.json index d7234a6..d8d3dc1 100644 --- a/composer.json +++ b/composer.json @@ -22,6 +22,7 @@ "phpunit/phpunit": "^9.6.22", "shipmonk/coding-standard": "^0.2.0", "shipmonk/composer-dependency-analyser": "^1.8.1", + "shipmonk/coverage-guard": "dev-master", "shipmonk/dead-code-detector": "^0.9.0", "shipmonk/name-collision-detector": "^2.1.1", "shipmonk/phpstan-dev": "^0.1.2" @@ -59,7 +60,7 @@ "@check:ec", "@check:cs", "@check:types", - "@check:tests", + "@check:coverage", "@check:dependencies", "@check:collisions", "@check:ignores" @@ -69,11 +70,15 @@ "composer normalize --dry-run --no-check-lock --no-update-lock", "composer validate --strict" ], + "check:coverage": [ + "XDEBUG_MODE=coverage phpunit tests --coverage-clover cache/clover.xml", + "coverage-guard check cache/clover.xml" + ], "check:cs": "phpcs", "check:dependencies": "composer-dependency-analyser", "check:ec": "ec src tests", "check:ignores": "php bin/verify-inline-ignore.php", - "check:tests": "phpunit -vvv tests", + "check:tests": "phpunit tests", "check:types": "phpstan analyse -vv --ansi", "fix:cs": "phpcbf" } diff --git a/composer.lock b/composer.lock index bc4eadb..2d1ffe3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e45e2400e8fe0857f5bf825391d73768", + "content-hash": "41d8331fa766c41e8117616a6f039fab", "packages": [ { "name": "phpstan/phpstan", @@ -3086,6 +3086,67 @@ }, "time": "2025-11-25T14:38:16+00:00" }, + { + "name": "shipmonk/coverage-guard", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/shipmonk-rnd/coverage-guard.git", + "reference": "2f1281f7883ce7933ee029241fd517bb14cafcd6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/shipmonk-rnd/coverage-guard/zipball/2f1281f7883ce7933ee029241fd517bb14cafcd6", + "reference": "2f1281f7883ce7933ee029241fd517bb14cafcd6", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^4.19.1 || ^5.0", + "php": "^8.1" + }, + "require-dev": { + "editorconfig-checker/editorconfig-checker": "10.7.0", + "ergebnis/composer-normalize": "2.48.2", + "phpstan/phpstan": "2.1.32", + "phpstan/phpstan-phpunit": "2.0.7", + "phpstan/phpstan-strict-rules": "2.0.7", + "phpunit/php-code-coverage": "^10.1", + "phpunit/phpunit": "~10.5.58", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0 || ^7.0", + "shipmonk/coding-standard": "^0.2.0", + "shipmonk/composer-dependency-analyser": "1.8.3", + "shipmonk/dead-code-detector": "0.13.3", + "shipmonk/name-collision-detector": "2.1.1", + "shipmonk/phpstan-rules": "4.2.1" + }, + "default-branch": true, + "bin": [ + "bin/coverage-guard" + ], + "type": "library", + "autoload": { + "psr-4": { + "ShipMonk\\CoverageGuard\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Enforce code coverage in your CI. Not by percentage, but target core methods. No more untested Facades, Controllers, or Repositories. Allows you to start enforcing coverage for new code only!", + "keywords": [ + "code coverage", + "git diff", + "patch", + "phpunit", + "testing" + ], + "support": { + "issues": "https://github.com/shipmonk-rnd/coverage-guard/issues", + "source": "https://github.com/shipmonk-rnd/coverage-guard/tree/master" + }, + "time": "2025-11-28T14:42:05+00:00" + }, { "name": "shipmonk/dead-code-detector", "version": "0.9.2", @@ -3464,7 +3525,9 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": { + "shipmonk/coverage-guard": 20 + }, "prefer-stable": false, "prefer-lowest": false, "platform": { diff --git a/coverage-guard.php b/coverage-guard.php new file mode 100644 index 0000000..e48976f --- /dev/null +++ b/coverage-guard.php @@ -0,0 +1,12 @@ +addRule(new EnforceCoverageForMethodsRule( + requiredCoveragePercentage: 70, + minExecutableLines: 5, +)); + +return $config; diff --git a/phpunit.xml.dist b/phpunit.xml.dist index b12349e..caa06c0 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -14,4 +14,10 @@ + + + + src + + diff --git a/tests/Rule/ForbidCheckedExceptionInCallableRuleTest.php b/tests/Rule/ForbidCheckedExceptionInCallableRuleTest.php index 086f750..be6364e 100644 --- a/tests/Rule/ForbidCheckedExceptionInCallableRuleTest.php +++ b/tests/Rule/ForbidCheckedExceptionInCallableRuleTest.php @@ -49,7 +49,8 @@ protected function getRule(): Rule 'ForbidCheckedExceptionInCallableRule\FirstClassCallableTest::allowThrow' => [1], 'ForbidCheckedExceptionInCallableRule\ArrowFunctionTest::allowThrow' => [0], 'ForbidCheckedExceptionInCallableRule\ArrowFunctionTest::__construct' => [0], - 'allowed_function' => [0], + 'ForbidCheckedExceptionInCallableRule\allowed_function' => [0], // not really needed as functions are always considered immediately invoked (https://phpstan.org/writing-php-code/phpdocs-basics#callables) + 'ForbidCheckedExceptionInCallableRule\allowed_function_not_immediate' => [0], ], ); } diff --git a/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php b/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php index 786ba72..16e5786 100644 --- a/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php +++ b/tests/Rule/data/ForbidCheckedExceptionInCallableRule/code.php @@ -11,6 +11,11 @@ function throwing_function() {} function allowed_function(callable $callable) {} +/** + * @param-later-invoked-callable $callable + */ +function allowed_function_not_immediate(callable $callable) {} + interface CallableTest { public function allowThrowInInterface(callable $callable): void; @@ -96,6 +101,12 @@ public function testPassedCallbacksA5(): void $this->allowThrow(42, throwing_function(...)); } + public function testPassedCallbacksA6(): void + { + allowed_function(throwing_function(...)); + allowed_function_not_immediate(throwing_function(...)); + } + public function testPassedCallbacksA6(): void { $this->immediateThrow( @@ -510,3 +521,5 @@ private function throws(): void throw new CheckedException(); } } + + From dd59c424e6ac3a1018a77d5baa9fbecc02477b1b Mon Sep 17 00:00:00 2001 From: Jan Nedbal Date: Mon, 1 Dec 2025 15:40:26 +0100 Subject: [PATCH 2/2] No need for coverage-guard to run tests on old PHP --- .github/workflows/checks.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index c405ce3..92750b1 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -47,7 +47,9 @@ jobs: php-version: ${{ matrix.php-version }} - name: Update dependencies - run: composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction + run: | + composer remove --dev shipmonk/coverage-guard --no-update + composer update --no-progress --${{ matrix.dependency-version }} --prefer-dist --no-interaction - name: Run tests run: composer check:tests