From 512414a27f66855da55ede2c7d9f4af1c2b5d951 Mon Sep 17 00:00:00 2001 From: Gustavo Meireles Date: Sun, 18 Oct 2026 13:06:50 -0400 Subject: [PATCH] feat: restore `coversNothing()` --- src/PendingCalls/TestCall.php | 11 ++++ tests/.snapshots/success.txt | 8 ++- tests/Features/Covers/CoversNothing.php | 54 +++++++++++++++++++ tests/Fixtures/Suites/CoversNothing.php | 9 ++++ tests/Fixtures/Suites/CoversNothing.xml | 15 ++++++ .../Suites/CoversNothingWithCovers.php | 13 +++++ tests/Visual/Parallel.php | 2 +- 7 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 tests/Features/Covers/CoversNothing.php create mode 100644 tests/Fixtures/Suites/CoversNothing.php create mode 100644 tests/Fixtures/Suites/CoversNothing.xml create mode 100644 tests/Fixtures/Suites/CoversNothingWithCovers.php diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 2eecfe8c0..e1b49c153 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -24,6 +24,7 @@ use PHPUnit\Framework\AssertionFailedError; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\CoversFunction; +use PHPUnit\Framework\Attributes\CoversNothing; use PHPUnit\Framework\Attributes\CoversTrait; use PHPUnit\Framework\Attributes\Group; use PHPUnit\Framework\TestCase; @@ -571,6 +572,16 @@ public function coversFunction(string ...$functions): self return $this; } + public function coversNothing(): self + { + $this->testCaseMethod->attributes[] = new Attribute( + CoversNothing::class, + [], + ); + + return $this; + } + /** * @param array|class-string ...$classes */ diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index d3586c371..0ea8b3f6b 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -82,6 +82,12 @@ PASS Tests\Features\Covers\ClassCoverage ✓ it uses the correct PHPUnit attribute for class + PASS Tests\Features\Covers\CoversNothing + ✓ it uses the correct PHPUnit attribute for covers nothing + ✓ describe → it uses the correct PHPUnit attribute for covers nothing + ✓ it is not risky when coverage metadata is required + ✓ it does not conflict with covers in the same file + PASS Tests\Features\Covers\ExceptionHandling ✓ it throws exception if no class nor method has been found @@ -2265,4 +2271,4 @@ ✓ pass with dataset with ('my-datas-set-value') ✓ within describe → pass with dataset with ('my-datas-set-value') - Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1606 passed (3505 assertions) \ No newline at end of file + Tests: 2 deprecated, 4 warnings, 5 incomplete, 2 notices, 40 todos, 35 skipped, 1610 passed (3516 assertions) \ No newline at end of file diff --git a/tests/Features/Covers/CoversNothing.php b/tests/Features/Covers/CoversNothing.php new file mode 100644 index 000000000..349682eac --- /dev/null +++ b/tests/Features/Covers/CoversNothing.php @@ -0,0 +1,54 @@ + 'DefaultPrinter', 'COLLISION_IGNORE_DURATION' => 'true', 'PAO_DISABLE' => '1'], + ); + + $process->run(); + + return [ + 'output' => removeAnsiEscapeSequences($process->getOutput().$process->getErrorOutput()), + 'code' => $process->getExitCode(), + ]; +}; + +it('uses the correct PHPUnit attribute for covers nothing', function (): void { + $attributes = new ReflectionMethod($this, $this->name())->getAttributes(); + + expect($attributes[2]->getName())->toBe(CoversNothing::class) + ->and($attributes[2]->getArguments())->toBeEmpty(); +})->coversNothing(); + +describe('describe', function (): void { + it('uses the correct PHPUnit attribute for covers nothing', function (): void { + $attributes = new ReflectionMethod($this, $this->name())->getAttributes(); + + expect($attributes[2]->getName())->toBe(CoversNothing::class) + ->and($attributes[2]->getArguments())->toBeEmpty(); + }); +})->coversNothing(); + +it('is not risky when coverage metadata is required', function () use ($run): void { + $result = $run(); + + expect($result['output']) + ->toContain('✓ covers nothing') + ->toContain('! declares no coverage metadata → This test does not define a code coverage target but is expected to do so') + ->toContain('Tests: 1 risky, 1 passed (2 assertions)') + ->and($result['code'])->toBe(1); +})->skipOnWindows(); + +it('does not conflict with covers in the same file', function () use ($run): void { + $result = $run('tests/Fixtures/Suites/CoversNothingWithCovers.php'); + + expect($result['output']) + ->toContain('✓ covers nothing') + ->toContain('Tests: 2 passed (2 assertions)') + ->and($result['code'])->toBe(0); +})->skipOnWindows(); diff --git a/tests/Fixtures/Suites/CoversNothing.php b/tests/Fixtures/Suites/CoversNothing.php new file mode 100644 index 000000000..e1e00fe03 --- /dev/null +++ b/tests/Fixtures/Suites/CoversNothing.php @@ -0,0 +1,9 @@ +toBeTrue(); +})->coversNothing(); + +test('declares no coverage metadata', function (): void { + expect(true)->toBeTrue(); +}); diff --git a/tests/Fixtures/Suites/CoversNothing.xml b/tests/Fixtures/Suites/CoversNothing.xml new file mode 100644 index 000000000..6605c579a --- /dev/null +++ b/tests/Fixtures/Suites/CoversNothing.xml @@ -0,0 +1,15 @@ + + + + + CoversNothing.php + + + diff --git a/tests/Fixtures/Suites/CoversNothingWithCovers.php b/tests/Fixtures/Suites/CoversNothingWithCovers.php new file mode 100644 index 000000000..e1bd757f4 --- /dev/null +++ b/tests/Fixtures/Suites/CoversNothingWithCovers.php @@ -0,0 +1,13 @@ +toBeTrue(); +}); + +test('covers nothing', function (): void { + expect(true)->toBeTrue(); +})->coversNothing(); diff --git a/tests/Visual/Parallel.php b/tests/Visual/Parallel.php index 5ecf74b48..a775c9926 100644 --- a/tests/Visual/Parallel.php +++ b/tests/Visual/Parallel.php @@ -20,7 +20,7 @@ $output = $run('--exclude-group=integration'); $output = implode("\n", array_slice(explode("\n", (string) $output), -10)); - $expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1587 passed (3447 assertions)'; + $expected = '2 deprecated, 4 warnings, 5 incomplete, 3 notices, 40 todos, 27 skipped, 1591 passed (3458 assertions)'; expect($output) ->toContain("Tests: {$expected}")