diff --git a/CHANGELOG.md b/CHANGELOG.md index aaf682c1e..ffb3ae5f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,19 @@ ## Unreleased +## [v2.6.1 (2023-04-12)](https://github.com/pestphp/pest/compare/v2.6.0...v2.6.1) + +### Fixes +- PHPStorm issue output problem for tests throwing an exception before the first assertion ([#809](https://github.com/pestphp/pest/pull/809)) + +### Chore +- Bumps PHPUnit to `^10.1.3` ([c993252](https://github.com/pestphp/pest/commit/c99325275acf1fd3759b487b93ec50473f706709)) + +## [v2.6.0 (2023-04-05)](https://github.com/pestphp/pest/compare/v2.5.2...v2.6.0) + +### Adds +- Allows `toThrow` to be used against an exception instance ([#797](https://github.com/pestphp/pest/pull/797)) + ## [v2.5.2 (2023-04-19)](https://github.com/pestphp/pest/compare/v2.5.1...v2.5.2) ### Chore diff --git a/composer.json b/composer.json index fcc83117e..6dcf9a05b 100644 --- a/composer.json +++ b/composer.json @@ -18,16 +18,16 @@ ], "require": { "php": "^8.1.0", - "brianium/paratest": "^7.1.3", + "brianium/paratest": "^7.1.4", "nunomaduro/collision": "^7.5.2", "nunomaduro/termwind": "^1.15.1", "pestphp/pest-plugin": "^2.0.1", "pestphp/pest-plugin-arch": "^2.1.2", - "phpunit/phpunit": "^10.1.2" + "phpunit/phpunit": "^10.1.3" }, "conflict": { "webmozart/assert": "<1.11.0", - "phpunit/phpunit": ">10.1.2" + "phpunit/phpunit": ">10.1.3" }, "autoload": { "psr-4": { @@ -49,7 +49,7 @@ ] }, "require-dev": { - "pestphp/pest-dev-tools": "^2.8.0", + "pestphp/pest-dev-tools": "^2.9.0", "symfony/process": "^6.2.10" }, "minimum-stability": "stable", diff --git a/src/Logging/TeamCity/Converter.php b/src/Logging/TeamCity/Converter.php index 89bdc334c..336dc76b1 100644 --- a/src/Logging/TeamCity/Converter.php +++ b/src/Logging/TeamCity/Converter.php @@ -11,6 +11,12 @@ use PHPUnit\Event\Code\Test; use PHPUnit\Event\Code\TestMethod; use PHPUnit\Event\Code\Throwable; +use PHPUnit\Event\Test\BeforeFirstTestMethodErrored; +use PHPUnit\Event\Test\ConsideredRisky; +use PHPUnit\Event\Test\Errored; +use PHPUnit\Event\Test\Failed; +use PHPUnit\Event\Test\MarkedIncomplete; +use PHPUnit\Event\Test\Skipped; use PHPUnit\Event\TestSuite\TestSuite; use PHPUnit\Framework\Exception as FrameworkException; use PHPUnit\TestRunner\TestResult\TestResult as PhpUnitTestResult; @@ -188,12 +194,30 @@ private function toRelativePath(string $path): string */ public function getStateFromResult(PhpUnitTestResult $result): State { - $numberOfPassedTests = $result->numberOfTestsRun() - - $result->numberOfTestErroredEvents() - - $result->numberOfTestFailedEvents() - - $result->numberOfTestSkippedEvents() - - $result->numberOfTestsWithTestConsideredRiskyEvents() - - $result->numberOfTestMarkedIncompleteEvents(); + $events = [ + ...$result->testErroredEvents(), + ...$result->testFailedEvents(), + ...$result->testSkippedEvents(), + ...array_merge(...array_values($result->testConsideredRiskyEvents())), + ...$result->testMarkedIncompleteEvents(), + ]; + + $numberOfNotPassedTests = count( + array_unique( + array_map( + function (BeforeFirstTestMethodErrored|Errored|Failed|Skipped|ConsideredRisky|MarkedIncomplete $event): string { + if ($event instanceof BeforeFirstTestMethodErrored) { + return $event->testClassName(); + } + + return $this->getTestCaseLocation($event->test()); + }, + $events + ) + ) + ); + + $numberOfPassedTests = $result->numberOfTestsRun() - $numberOfNotPassedTests; return $this->stateGenerator->fromPhpUnitTestResult($numberOfPassedTests, $result); } diff --git a/src/Logging/TeamCity/TeamCityLogger.php b/src/Logging/TeamCity/TeamCityLogger.php index f8c4d1a46..5bbde5182 100644 --- a/src/Logging/TeamCity/TeamCityLogger.php +++ b/src/Logging/TeamCity/TeamCityLogger.php @@ -16,6 +16,7 @@ use Pest\Logging\TeamCity\Subscriber\TestSkippedSubscriber; use Pest\Logging\TeamCity\Subscriber\TestSuiteFinishedSubscriber; use Pest\Logging\TeamCity\Subscriber\TestSuiteStartedSubscriber; +use PHPUnit\Event\Code\Test; use PHPUnit\Event\EventFacadeIsSealedException; use PHPUnit\Event\Facade; use PHPUnit\Event\Telemetry\Duration; @@ -47,6 +48,11 @@ final class TeamCityLogger private bool $isSummaryTestCountPrinted = false; + /** + * @var array + */ + private array $testEvents = []; + /** * @throws EventFacadeIsSealedException * @throws UnknownSubscriberTypeException @@ -108,12 +114,14 @@ public function testMarkedIncomplete(MarkedIncomplete $event): never public function testSkipped(Skipped $event): void { - $message = ServiceMessage::testIgnored( - $this->converter->getTestCaseMethodName($event->test()), - 'This test was ignored.' - ); + $this->whenFirstEventForTest($event->test(), function () use ($event): void { + $message = ServiceMessage::testIgnored( + $this->converter->getTestCaseMethodName($event->test()), + 'This test was ignored.' + ); - $this->output($message); + $this->output($message); + }); } /** @@ -122,17 +130,19 @@ public function testSkipped(Skipped $event): void */ public function testErrored(Errored $event): void { - $testName = $this->converter->getTestCaseMethodName($event->test()); - $message = $this->converter->getExceptionMessage($event->throwable()); - $details = $this->converter->getExceptionDetails($event->throwable()); + $this->whenFirstEventForTest($event->test(), function () use ($event): void { + $testName = $this->converter->getTestCaseMethodName($event->test()); + $message = $this->converter->getExceptionMessage($event->throwable()); + $details = $this->converter->getExceptionDetails($event->throwable()); - $message = ServiceMessage::testFailed( - $testName, - $message, - $details, - ); + $message = ServiceMessage::testFailed( + $testName, + $message, + $details, + ); - $this->output($message); + $this->output($message); + }); } /** @@ -141,28 +151,30 @@ public function testErrored(Errored $event): void */ public function testFailed(Failed $event): void { - $testName = $this->converter->getTestCaseMethodName($event->test()); - $message = $this->converter->getExceptionMessage($event->throwable()); - $details = $this->converter->getExceptionDetails($event->throwable()); - - if ($event->hasComparisonFailure()) { - $comparison = $event->comparisonFailure(); - $message = ServiceMessage::comparisonFailure( - $testName, - $message, - $details, - $comparison->actual(), - $comparison->expected() - ); - } else { - $message = ServiceMessage::testFailed( - $testName, - $message, - $details, - ); - } + $this->whenFirstEventForTest($event->test(), function () use ($event): void { + $testName = $this->converter->getTestCaseMethodName($event->test()); + $message = $this->converter->getExceptionMessage($event->throwable()); + $details = $this->converter->getExceptionDetails($event->throwable()); + + if ($event->hasComparisonFailure()) { + $comparison = $event->comparisonFailure(); + $message = ServiceMessage::comparisonFailure( + $testName, + $message, + $details, + $comparison->actual(), + $comparison->expected() + ); + } else { + $message = ServiceMessage::testFailed( + $testName, + $message, + $details, + ); + } - $this->output($message); + $this->output($message); + }); } /** @@ -171,12 +183,14 @@ public function testFailed(Failed $event): void */ public function testConsideredRisky(ConsideredRisky $event): void { - $message = ServiceMessage::testIgnored( - $this->converter->getTestCaseMethodName($event->test()), - $event->message() - ); + $this->whenFirstEventForTest($event->test(), function () use ($event): void { + $message = ServiceMessage::testIgnored( + $this->converter->getTestCaseMethodName($event->test()), + $event->message() + ); - $this->output($message); + $this->output($message); + }); } public function testFinished(Finished $event): void @@ -264,4 +278,14 @@ private function setFlowId(): void ServiceMessage::setFlowId($this->flowId); } + + private function whenFirstEventForTest(Test $test, callable $callback): void + { + $testIdentifier = $this->converter->getTestCaseLocation($test); + + if (! isset($this->testEvents[$testIdentifier])) { + $this->testEvents[$testIdentifier] = true; + $callback(); + } + } } diff --git a/src/PendingCalls/TestCall.php b/src/PendingCalls/TestCall.php index 79885fbfd..a4c45a7bc 100644 --- a/src/PendingCalls/TestCall.php +++ b/src/PendingCalls/TestCall.php @@ -227,7 +227,7 @@ public function todo(): self public function covers(string ...$classesOrFunctions): self { foreach ($classesOrFunctions as $classOrFunction) { - $isClass = class_exists($classOrFunction); + $isClass = class_exists($classOrFunction) || trait_exists($classOrFunction); $isMethod = function_exists($classOrFunction); if (! $isClass && ! $isMethod) { diff --git a/src/Pest.php b/src/Pest.php index d5c414cd5..d37d02010 100644 --- a/src/Pest.php +++ b/src/Pest.php @@ -6,7 +6,7 @@ function version(): string { - return '2.6.0'; + return '2.6.1'; } function testDirectory(string $file = ''): string diff --git a/tests/.snapshots/Failure.php.inc b/tests/.snapshots/Failure.php.inc index 62f9e09ff..4050a989d 100644 --- a/tests/.snapshots/Failure.php.inc +++ b/tests/.snapshots/Failure.php.inc @@ -1,5 +1,5 @@ ##teamcity[testSuiteStarted name='Tests/tests/Failure' locationHint='file://tests/.tests/Failure.php' flowId='1234'] -##teamcity[testCount count='6' flowId='1234'] +##teamcity[testCount count='8' flowId='1234'] ##teamcity[testStarted name='it can fail with comparison' locationHint='pest_qn://tests/.tests/Failure.php::it can fail with comparison' flowId='1234'] ##teamcity[testFailed name='it can fail with comparison' message='Failed asserting that true matches expected false.' details='at src/Mixins/Expectation.php:343|nat src/Support/ExpectationPipeline.php:75|nat src/Support/ExpectationPipeline.php:79|nat src/Expectation.php:300|nat tests/.tests/Failure.php:6|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:302|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:302|nat src/Concerns/Testable.php:221|nat src/Kernel.php:86' type='comparisonFailure' actual='true' expected='false' flowId='1234'] ##teamcity[testFinished name='it can fail with comparison' duration='100000' flowId='1234'] @@ -12,14 +12,19 @@ ##teamcity[testStarted name='it can fail' locationHint='pest_qn://tests/.tests/Failure.php::it can fail' flowId='1234'] ##teamcity[testFailed name='it can fail' message='oh noo' details='at tests/.tests/Failure.php:18|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:302|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:302|nat src/Concerns/Testable.php:221|nat src/Kernel.php:86' flowId='1234'] ##teamcity[testFinished name='it can fail' duration='100000' flowId='1234'] +##teamcity[testStarted name='it throws exception' locationHint='pest_qn://tests/.tests/Failure.php::it throws exception' flowId='1234'] +##teamcity[testFailed name='it throws exception' message='Exception: test error' details='at tests/.tests/Failure.php:22|nat src/Factories/TestCaseMethodFactory.php:100|nat src/Concerns/Testable.php:302|nat src/Support/ExceptionTrace.php:28|nat src/Concerns/Testable.php:302|nat src/Concerns/Testable.php:221|nat src/Kernel.php:86' flowId='1234'] +##teamcity[testFinished name='it throws exception' duration='100000' flowId='1234'] ##teamcity[testStarted name='it is not done yet' locationHint='pest_qn://tests/.tests/Failure.php::it is not done yet' flowId='1234'] ##teamcity[testIgnored name='it is not done yet' message='This test was ignored.' details='' flowId='1234'] ##teamcity[testFinished name='it is not done yet' duration='100000' flowId='1234'] ##teamcity[testStarted name='build this one.' locationHint='pest_qn://tests/.tests/Failure.php::build this one.' flowId='1234'] ##teamcity[testIgnored name='build this one.' message='This test was ignored.' details='' flowId='1234'] ##teamcity[testFinished name='build this one.' duration='100000' flowId='1234'] +##teamcity[testStarted name='it is passing' locationHint='pest_qn://tests/.tests/Failure.php::it is passing' flowId='1234'] +##teamcity[testFinished name='it is passing' duration='100000' flowId='1234'] ##teamcity[testSuiteFinished name='Tests/tests/Failure' flowId='1234'] - Tests: 2 failed, 1 risky, 2 todos, 1 skipped (2 assertions) + Tests: 3 failed, 1 risky, 2 todos, 1 skipped, 1 passed (3 assertions) Duration: 1.00s diff --git a/tests/.snapshots/help-command.txt b/tests/.snapshots/help-command.txt index 12dd3a2ad..6f76d6bc6 100644 --- a/tests/.snapshots/help-command.txt +++ b/tests/.snapshots/help-command.txt @@ -1,5 +1,5 @@ - Pest Testing Framework 2.6.0. + Pest Testing Framework 2.6.1. USAGE: pest [options] diff --git a/tests/.snapshots/success.txt b/tests/.snapshots/success.txt index 75d6a29c1..7e2bfc17c 100644 --- a/tests/.snapshots/success.txt +++ b/tests/.snapshots/success.txt @@ -49,6 +49,7 @@ ✓ it uses the correct PHPUnit attribute for function ✓ it removes duplicated attributes ✓ it guesses if the given argument is a class or function + ✓ it uses the correct PHPUnit attribute for trait ✓ it appends CoversNothing to method attributes ✓ it does not append CoversNothing to other methods ✓ it throws exception if no class nor method has been found @@ -1034,4 +1035,4 @@ PASS Tests\Visual\Version ✓ visual snapshot of help command output - Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 17 skipped, 715 passed (1727 assertions) \ No newline at end of file + Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 17 skipped, 716 passed (1729 assertions) \ No newline at end of file diff --git a/tests/.snapshots/version-command.txt b/tests/.snapshots/version-command.txt index 7ab4eb8bc..8e7d74c53 100644 --- a/tests/.snapshots/version-command.txt +++ b/tests/.snapshots/version-command.txt @@ -1,3 +1,3 @@ - Pest Testing Framework 2.6.0. + Pest Testing Framework 2.6.1. diff --git a/tests/.tests/Failure.php b/tests/.tests/Failure.php index 90d92593d..7b00b8d67 100644 --- a/tests/.tests/Failure.php +++ b/tests/.tests/Failure.php @@ -18,9 +18,17 @@ $this->fail("oh noo"); }); +it('throws exception', function () { + throw new Exception('test error'); +}); + it('is not done yet', function () { })->todo(); todo("build this one."); +it('is passing', function () { + expect(true)->toEqual(true); +}); + diff --git a/tests/Features/Covers.php b/tests/Features/Covers.php index 3ee6ae817..12ec4ac5b 100644 --- a/tests/Features/Covers.php +++ b/tests/Features/Covers.php @@ -6,6 +6,7 @@ use Tests\Fixtures\Covers\CoversClass1; use Tests\Fixtures\Covers\CoversClass2; use Tests\Fixtures\Covers\CoversClass3; +use Tests\Fixtures\Covers\CoversTrait; $runCounter = 0; @@ -43,6 +44,13 @@ function testCoversFunction() expect($attributes[3]->getArguments()[0])->toBe(CoversClass3::class); })->covers(CoversClass3::class, 'testCoversFunction'); +it('uses the correct PHPUnit attribute for trait', function () { + $attributes = (new ReflectionClass($this))->getAttributes(); + + expect($attributes[4]->getName())->toBe('PHPUnit\Framework\Attributes\CoversClass'); + expect($attributes[4]->getArguments()[0])->toBe('Tests\Fixtures\Covers\CoversTrait'); +})->coversClass(CoversTrait::class); + it('appends CoversNothing to method attributes', function () { $phpDoc = (new ReflectionClass($this))->getMethod($this->name()); diff --git a/tests/Fixtures/Covers/CoversTrait.php b/tests/Fixtures/Covers/CoversTrait.php new file mode 100644 index 000000000..6de082b97 --- /dev/null +++ b/tests/Fixtures/Covers/CoversTrait.php @@ -0,0 +1,7 @@ +toContain('Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 14 skipped, 703 passed (1712 assertions)') + ->toContain('Tests: 1 deprecated, 3 warnings, 4 incomplete, 1 notice, 8 todos, 14 skipped, 704 passed (1714 assertions)') ->toContain('Parallel: 3 processes'); })->skipOnWindows();