Blame | Letzte Änderung | Log anzeigen | RSS feed
<?php declare(strict_types=1);/** This file is part of PHPUnit.** (c) Sebastian Bergmann <sebastian@phpunit.de>** For the full copyright and license information, please view the LICENSE* file that was distributed with this source code.*/namespace PHPUnit\Util\Log;use function class_exists;use function count;use function explode;use function get_class;use function getmypid;use function ini_get;use function is_bool;use function is_scalar;use function method_exists;use function print_r;use function round;use function str_replace;use function stripos;use PHPUnit\Framework\AssertionFailedError;use PHPUnit\Framework\ExceptionWrapper;use PHPUnit\Framework\ExpectationFailedException;use PHPUnit\Framework\Test;use PHPUnit\Framework\TestCase;use PHPUnit\Framework\TestFailure;use PHPUnit\Framework\TestResult;use PHPUnit\Framework\TestSuite;use PHPUnit\Framework\Warning;use PHPUnit\TextUI\DefaultResultPrinter;use PHPUnit\Util\Exception;use PHPUnit\Util\Filter;use ReflectionClass;use ReflectionException;use SebastianBergmann\Comparator\ComparisonFailure;use Throwable;/*** @internal This class is not covered by the backward compatibility promise for PHPUnit*/final class TeamCity extends DefaultResultPrinter{/*** @var bool*/private $isSummaryTestCountPrinted = false;/*** @var string*/private $startedTestName;/*** @var false|int*/private $flowId;public function printResult(TestResult $result): void{$this->printHeader($result);$this->printFooter($result);}/*** An error occurred.*/public function addError(Test $test, Throwable $t, float $time): void{$this->printEvent('testFailed',['name' => $test->getName(),'message' => self::getMessage($t),'details' => self::getDetails($t),'duration' => self::toMilliseconds($time),]);}/*** A warning occurred.*/public function addWarning(Test $test, Warning $e, float $time): void{$this->write(self::getMessage($e) . PHP_EOL);}/*** A failure occurred.*/public function addFailure(Test $test, AssertionFailedError $e, float $time): void{$parameters = ['name' => $test->getName(),'message' => self::getMessage($e),'details' => self::getDetails($e),'duration' => self::toMilliseconds($time),];if ($e instanceof ExpectationFailedException) {$comparisonFailure = $e->getComparisonFailure();if ($comparisonFailure instanceof ComparisonFailure) {$expectedString = $comparisonFailure->getExpectedAsString();if ($expectedString === null || empty($expectedString)) {$expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected());}$actualString = $comparisonFailure->getActualAsString();if ($actualString === null || empty($actualString)) {$actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual());}if ($actualString !== null && $expectedString !== null) {$parameters['type'] = 'comparisonFailure';$parameters['actual'] = $actualString;$parameters['expected'] = $expectedString;}}}$this->printEvent('testFailed', $parameters);}/*** Incomplete test.*/public function addIncompleteTest(Test $test, Throwable $t, float $time): void{$this->printIgnoredTest($test->getName(), $t, $time);}/*** Risky test.*/public function addRiskyTest(Test $test, Throwable $t, float $time): void{$this->addError($test, $t, $time);}/*** Skipped test.*/public function addSkippedTest(Test $test, Throwable $t, float $time): void{$testName = $test->getName();if ($this->startedTestName !== $testName) {$this->startTest($test);$this->printIgnoredTest($testName, $t, $time);$this->endTest($test, $time);} else {$this->printIgnoredTest($testName, $t, $time);}}public function printIgnoredTest(string $testName, Throwable $t, float $time): void{$this->printEvent('testIgnored',['name' => $testName,'message' => self::getMessage($t),'details' => self::getDetails($t),'duration' => self::toMilliseconds($time),]);}/*** A testsuite started.*/public function startTestSuite(TestSuite $suite): void{if (stripos(ini_get('disable_functions'), 'getmypid') === false) {$this->flowId = getmypid();} else {$this->flowId = false;}if (!$this->isSummaryTestCountPrinted) {$this->isSummaryTestCountPrinted = true;$this->printEvent('testCount',['count' => count($suite)]);}$suiteName = $suite->getName();if (empty($suiteName)) {return;}$parameters = ['name' => $suiteName];if (class_exists($suiteName, false)) {$fileName = self::getFileName($suiteName);$parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}";} else {$split = explode('::', $suiteName);if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) {$fileName = self::getFileName($split[0]);$parameters['locationHint'] = "php_qn://{$fileName}::\\{$suiteName}";$parameters['name'] = $split[1];}}$this->printEvent('testSuiteStarted', $parameters);}/*** A testsuite ended.*/public function endTestSuite(TestSuite $suite): void{$suiteName = $suite->getName();if (empty($suiteName)) {return;}$parameters = ['name' => $suiteName];if (!class_exists($suiteName, false)) {$split = explode('::', $suiteName);if (count($split) === 2 && class_exists($split[0]) && method_exists($split[0], $split[1])) {$parameters['name'] = $split[1];}}$this->printEvent('testSuiteFinished', $parameters);}/*** A test started.*/public function startTest(Test $test): void{$testName = $test->getName();$this->startedTestName = $testName;$params = ['name' => $testName];if ($test instanceof TestCase) {$className = get_class($test);$fileName = self::getFileName($className);$params['locationHint'] = "php_qn://{$fileName}::\\{$className}::{$testName}";}$this->printEvent('testStarted', $params);}/*** A test ended.*/public function endTest(Test $test, float $time): void{parent::endTest($test, $time);$this->printEvent('testFinished',['name' => $test->getName(),'duration' => self::toMilliseconds($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 getMessage(Throwable $t): string{$message = '';if ($t instanceof ExceptionWrapper) {if ($t->getClassName() !== '') {$message .= $t->getClassName();}if ($message !== '' && $t->getMessage() !== '') {$message .= ' : ';}}return $message . $t->getMessage();}private static function getDetails(Throwable $t): string{$stackTrace = Filter::getFilteredStacktrace($t);$previous = $t instanceof ExceptionWrapper ? $t->getPreviousWrapped() : $t->getPrevious();while ($previous) {$stackTrace .= "\nCaused by\n" .TestFailure::exceptionToString($previous) . "\n" .Filter::getFilteredStacktrace($previous);$previous = $previous instanceof ExceptionWrapper ?$previous->getPreviousWrapped() : $previous->getPrevious();}return ' ' . str_replace("\n", "\n ", $stackTrace);}private static function getPrimitiveValueAsString($value): ?string{if ($value === null) {return 'null';}if (is_bool($value)) {return $value ? 'true' : 'false';}if (is_scalar($value)) {return print_r($value, true);}return null;}private static function escapeValue(string $text): string{return str_replace(['|', "'", "\n", "\r", ']', '['],['||', "|'", '|n', '|r', '|]', '|['],$text);}/*** @param string $className*/private static function getFileName($className): string{try {return (new ReflectionClass($className))->getFileName();// @codeCoverageIgnoreStart} catch (ReflectionException $e) {throw new Exception($e->getMessage(),$e->getCode(),$e);}// @codeCoverageIgnoreEnd}/*** @param float $time microseconds*/private static function toMilliseconds(float $time): int{return (int) round($time * 1000);}}