diff options
Diffstat (limited to 'vendor/phpunit/phpunit/src/Util/Log/TeamCity.php')
-rw-r--r-- | vendor/phpunit/phpunit/src/Util/Log/TeamCity.php | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php b/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php new file mode 100644 index 000000000..f800ba72d --- /dev/null +++ b/vendor/phpunit/phpunit/src/Util/Log/TeamCity.php @@ -0,0 +1,383 @@ +<?php declare(strict_types=1); +/* + * This file is part of PHPUnit. + * + * (c) Sebastian Bergmann <[email protected]> + * + * 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(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + + /** + * @param float $time microseconds + */ + private static function toMilliseconds(float $time): int + { + return (int) round($time * 1000); + } +} |