diff --git a/src/Logging/TeamCity/Converter.php b/src/Logging/TeamCity/Converter.php index 89bdc334..336dc76b 100644 --- a/src/Logging/TeamCity/Converter.php +++ b/src/Logging/TeamCity/Converter.php @@ -11,6 +11,12 @@ use Pest\Support\Str; 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 @@ final class Converter */ 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 f8c4d1a4..5bbde518 100644 --- a/src/Logging/TeamCity/TeamCityLogger.php +++ b/src/Logging/TeamCity/TeamCityLogger.php @@ -16,6 +16,7 @@ use Pest\Logging\TeamCity\Subscriber\TestPreparedSubscriber; 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 @@ final class TeamCityLogger 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 @@ final class TeamCityLogger */ 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 @@ final class TeamCityLogger */ public function testFailed(Failed $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()); - if ($event->hasComparisonFailure()) { - $comparison = $event->comparisonFailure(); - $message = ServiceMessage::comparisonFailure( - $testName, - $message, - $details, - $comparison->actual(), - $comparison->expected() - ); - } else { - $message = ServiceMessage::testFailed( - $testName, - $message, - $details, - ); - } + 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 @@ final class TeamCityLogger */ 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 @@ final class TeamCityLogger 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/tests/.snapshots/Failure.php.inc b/tests/.snapshots/Failure.php.inc index 62f9e09f..4050a989 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/.tests/Failure.php b/tests/.tests/Failure.php index 90d92593..7b00b8d6 100644 --- a/tests/.tests/Failure.php +++ b/tests/.tests/Failure.php @@ -18,9 +18,17 @@ it('can fail', function () { $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); +}); +