diff --git a/src/Actions/AddsDefaults.php b/src/Actions/AddsDefaults.php index 799eda6e..b13cdc52 100644 --- a/src/Actions/AddsDefaults.php +++ b/src/Actions/AddsDefaults.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace Pest\Actions; use NunoMaduro\Collision\Adapters\Phpunit\Printer; +use Pest\TeamCity; /** * @internal @@ -24,6 +25,10 @@ final class AddsDefaults $arguments['printer'] = new Printer(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? 'always'); } + if ($arguments['printer'] === \PHPUnit\Util\Log\TeamCity::class) { + $arguments['printer'] = new TeamCity(null, $arguments['verbose'] ?? false, $arguments['colors'] ?? 'always'); + } + return $arguments; } } diff --git a/src/Concerns/TestCase.php b/src/Concerns/TestCase.php index 6c1d98ce..01b5b00a 100644 --- a/src/Concerns/TestCase.php +++ b/src/Concerns/TestCase.php @@ -64,6 +64,11 @@ trait TestCase return $this->__description; } + public static function __getFileName(): string + { + return self::$__filename; + } + /** * This method is called before the first test of this test class is run. */ diff --git a/src/TeamCity.php b/src/TeamCity.php new file mode 100644 index 00000000..e7e5acd1 --- /dev/null +++ b/src/TeamCity.php @@ -0,0 +1,192 @@ +phpunitTeamCity = new \PHPUnit\Util\Log\TeamCity( + $out, + $verbose, + $colors, + $debug, + $numberOfColumns, + $reverse + ); + } + + public function printResult(TestResult $result): void + { + $this->printHeader($result); + $this->printFooter($result); + } + + public function startTestSuite(TestSuite $suite): void + { + $this->flowId = getmypid(); + + if (!$this->isSummaryTestCountPrinted) { + $this->printEvent( + 'testCount', + ['count' => $suite->count()] + ); + $this->isSummaryTestCountPrinted = true; + } + + $suiteName = $suite->getName(); + + if (file_exists($suiteName)) { + $this->printEvent('testSuiteStarted', [ + 'name' => $suiteName, + 'locationHint' => self::PROTOCOL . $suiteName, + ]); + + return; + } + + $fileName = $suite->getName()::__getFileName(); + + $this->printEvent('testSuiteStarted', [ + 'name' => substr($suiteName, 2), + 'locationHint' => self::PROTOCOL . $fileName, + ]); + } + + public function endTestSuite(TestSuite $suite): void + { + $suiteName = $suite->getName(); + + if (file_exists($suiteName)) { + $this->printEvent('testSuiteFinished', [ + 'name' => $suiteName, + 'locationHint' => self::PROTOCOL . $suiteName, + ]); + + return; + } + + $this->printEvent('testSuiteFinished', [ + 'name' => substr($suiteName, 2), + ]); + } + + /** + * @param Test|TestCase $test + */ + public function startTest(Test $test): void + { + if (!TeamCity::isPestTest($test)) { + $this->phpunitTeamCity->startTest($test); + + return; + } + + $this->printEvent('testStarted', [ + 'name' => $test->getName(), + 'locationHint' => self::PROTOCOL . $test->toString(), + ]); + } + + /** + * @param Test|TestCase $test + */ + public function endTest(Test $test, float $time): void + { + if (!TeamCity::isPestTest($test)) { + $this->phpunitTeamCity->endTest($test, $time); + + return; + } + + $this->printEvent('testFinished', [ + 'name' => $test->getName(), + 'duration' => self::toMilliseconds($time), + ]); + } + + /** + * @param Test|TestCase $test + */ + public function addError(Test $test, \Throwable $t, float $time): void + { + $this->printEvent('testFailed', [ + 'name' => $test->getName(), + 'message' => $t->getMessage(), + 'details' => $t->getTraceAsString(), + 'duration' => self::toMilliseconds($time), + ]); + } + + public function addWarning(Test $test, Warning $e, float $time): void + { + $this->printEvent('testFailed', [ + 'name' => $test->getName(), + 'message' => $e->getMessage(), + 'details' => $e->getTraceAsString(), + 'duration' => self::toMilliseconds($time), + ]); + } + + public function addFailure(Test $test, AssertionFailedError $e, float $time): void + { + $this->phpunitTeamCity->addFailure($test, $e, $time); + } + + protected function writeProgress(string $progress): void + { + } + + private function printEvent(string $eventName, array $params = []): void + { + $this->write("\n##teamcity[{$eventName}"); + + if ($this->flowId) { + $params['flowId'] = $this->flowId; + } + + foreach ($params as $key => $value) { + $escapedValue = self::escapeValue((string) $value); + $this->write(" {$key}='{$escapedValue}'"); + } + + $this->write("]\n"); + } + + private static function escapeValue(string $text): string + { + return str_replace( + ['|', "'", "\n", "\r", ']', '['], + ['||', "|'", '|n', '|r', '|]', '|['], + $text + ); + } + + private static function toMilliseconds(float $time): int + { + return (int) round($time * 1000); + } + + private static function isPestTest($test): bool + { + return in_array(TestCase::class, class_uses($test)); + } +}