* * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ use SebastianBergmann\Comparator\ComparisonFailure; /** * A TestListener that generates a logfile of the test execution using the * TeamCity format (for use with PhpStorm, for instance). */ class PHPUnit_Util_Log_TeamCity extends PHPUnit_TextUI_ResultPrinter { /** * @var bool */ private $isSummaryTestCountPrinted = false; /** * @var string */ private $startedTestName; /** * @var string */ private $flowId; /** * @param string $progress */ protected function writeProgress($progress) { } /** * @param PHPUnit_Framework_TestResult $result */ public function printResult(PHPUnit_Framework_TestResult $result) { $this->printHeader(); $this->printFooter($result); } /** * An error occurred. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->printEvent( 'testFailed', [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), ] ); } /** * A warning occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_Warning $e * @param float $time */ public function addWarning(PHPUnit_Framework_Test $test, PHPUnit_Framework_Warning $e, $time) { $this->printEvent( 'testFailed', [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e) ] ); } /** * A failure occurred. * * @param PHPUnit_Framework_Test $test * @param PHPUnit_Framework_AssertionFailedError $e * @param float $time */ public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { $parameters = [ 'name' => $test->getName(), 'message' => self::getMessage($e), 'details' => self::getDetails($e), ]; if ($e instanceof PHPUnit_Framework_ExpectationFailedException) { $comparisonFailure = $e->getComparisonFailure(); if ($comparisonFailure instanceof ComparisonFailure) { $expectedString = $comparisonFailure->getExpectedAsString(); if (is_null($expectedString) || empty($expectedString)) { $expectedString = self::getPrimitiveValueAsString($comparisonFailure->getExpected()); } $actualString = $comparisonFailure->getActualAsString(); if (is_null($actualString) || empty($actualString)) { $actualString = self::getPrimitiveValueAsString($comparisonFailure->getActual()); } if (!is_null($actualString) && !is_null($expectedString)) { $parameters['type'] = 'comparisonFailure'; $parameters['actual'] = $actualString; $parameters['expected'] = $expectedString; } } } $this->printEvent('testFailed', $parameters); } /** * Incomplete test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->printIgnoredTest($test->getName(), $e); } /** * Risky test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $this->addError($test, $e, $time); } /** * Skipped test. * * @param PHPUnit_Framework_Test $test * @param Exception $e * @param float $time */ public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { $testName = $test->getName(); if ($this->startedTestName != $testName) { $this->startTest($test); $this->printIgnoredTest($testName, $e); $this->endTest($test, $time); } else { $this->printIgnoredTest($testName, $e); } } public function printIgnoredTest($testName, Exception $e) { $this->printEvent( 'testIgnored', [ 'name' => $testName, 'message' => self::getMessage($e), 'details' => self::getDetails($e), ] ); } /** * A testsuite started. * * @param PHPUnit_Framework_TestSuite $suite */ public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { 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 = preg_split('/::/', $suiteName); if (count($split) == 2 && 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. * * @param PHPUnit_Framework_TestSuite $suite */ public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { $suiteName = $suite->getName(); if (empty($suiteName)) { return; } $parameters = ['name' => $suiteName]; if (!class_exists($suiteName, false)) { $split = preg_split('/::/', $suiteName); if (count($split) == 2 && method_exists($split[0], $split[1])) { $parameters['name'] = $split[1]; } } $this->printEvent('testSuiteFinished', $parameters); } /** * A test started. * * @param PHPUnit_Framework_Test $test */ public function startTest(PHPUnit_Framework_Test $test) { $testName = $test->getName(); $this->startedTestName = $testName; $params = ['name' => $testName]; if ($test instanceof PHPUnit_Framework_TestCase) { $className = get_class($test); $fileName = self::getFileName($className); $params['locationHint'] = "php_qn://$fileName::\\$className::$testName"; } $this->printEvent('testStarted', $params); } /** * A test ended. * * @param PHPUnit_Framework_Test $test * @param float $time */ public function endTest(PHPUnit_Framework_Test $test, $time) { parent::endTest($test, $time); $this->printEvent( 'testFinished', [ 'name' => $test->getName(), 'duration' => (int) (round($time, 2) * 1000) ] ); } /** * @param string $eventName * @param array $params */ private function printEvent($eventName, $params = []) { $this->write("\n##teamcity[$eventName"); if ($this->flowId) { $params['flowId'] = $this->flowId; } foreach ($params as $key => $value) { $escapedValue = self::escapeValue($value); $this->write(" $key='$escapedValue'"); } $this->write("]\n"); } /** * @param Exception $e * * @return string */ private static function getMessage(Exception $e) { $message = ''; if ($e instanceof PHPUnit_Framework_ExceptionWrapper) { if (strlen($e->getClassName()) != 0) { $message = $message . $e->getClassName(); } if (strlen($message) != 0 && strlen($e->getMessage()) != 0) { $message = $message . ' : '; } } return $message . $e->getMessage(); } /** * @param Exception $e * * @return string */ private static function getDetails(Exception $e) { $stackTrace = PHPUnit_Util_Filter::getFilteredStacktrace($e); $previous = $e->getPrevious(); while ($previous) { $stackTrace .= "\nCaused by\n" . PHPUnit_Framework_TestFailure::exceptionToString($previous) . "\n" . PHPUnit_Util_Filter::getFilteredStacktrace($previous); $previous = $previous->getPrevious(); } return ' ' . str_replace("\n", "\n ", $stackTrace); } /** * @param mixed $value * * @return string */ private static function getPrimitiveValueAsString($value) { if (is_null($value)) { return 'null'; } elseif (is_bool($value)) { return $value == true ? 'true' : 'false'; } elseif (is_scalar($value)) { return print_r($value, true); } return; } /** * @param $text * * @return string */ private static function escapeValue($text) { $text = str_replace('|', '||', $text); $text = str_replace("'", "|'", $text); $text = str_replace("\n", '|n', $text); $text = str_replace("\r", '|r', $text); $text = str_replace(']', '|]', $text); $text = str_replace('[', '|[', $text); return $text; } /** * @param string $className * * @return string */ private static function getFileName($className) { $reflectionClass = new ReflectionClass($className); $fileName = $reflectionClass->getFileName(); return $fileName; } }