Blame | Last modification | View Log | Download
<?php/** 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.*/use SebastianBergmann\CodeCoverage\CodeCoverage;use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException;use SebastianBergmann\CodeCoverage\CoveredCodeNotExecutedException;use SebastianBergmann\CodeCoverage\MissingCoversAnnotationException;use SebastianBergmann\CodeCoverage\UnintentionallyCoveredCodeException;use SebastianBergmann\ResourceOperations\ResourceOperations;/*** A TestResult collects the results of executing a test case.*/class PHPUnit_Framework_TestResult implements Countable{/*** @var array*/protected $passed = [];/*** @var array*/protected $errors = [];/*** @var array*/protected $failures = [];/*** @var array*/protected $warnings = [];/*** @var array*/protected $notImplemented = [];/*** @var array*/protected $risky = [];/*** @var array*/protected $skipped = [];/*** @var array*/protected $listeners = [];/*** @var int*/protected $runTests = 0;/*** @var float*/protected $time = 0;/*** @var PHPUnit_Framework_TestSuite*/protected $topTestSuite = null;/*** Code Coverage information.** @var CodeCoverage*/protected $codeCoverage;/*** @var bool*/protected $convertErrorsToExceptions = true;/*** @var bool*/protected $stop = false;/*** @var bool*/protected $stopOnError = false;/*** @var bool*/protected $stopOnFailure = false;/*** @var bool*/protected $stopOnWarning = false;/*** @var bool*/protected $beStrictAboutTestsThatDoNotTestAnything = false;/*** @var bool*/protected $beStrictAboutOutputDuringTests = false;/*** @var bool*/protected $beStrictAboutTodoAnnotatedTests = false;/*** @var bool*/protected $beStrictAboutResourceUsageDuringSmallTests = false;/*** @var bool*/protected $enforceTimeLimit = false;/*** @var int*/protected $timeoutForSmallTests = 1;/*** @var int*/protected $timeoutForMediumTests = 10;/*** @var int*/protected $timeoutForLargeTests = 60;/*** @var bool*/protected $stopOnRisky = false;/*** @var bool*/protected $stopOnIncomplete = false;/*** @var bool*/protected $stopOnSkipped = false;/*** @var bool*/protected $lastTestFailed = false;/*** @var bool*/private $registerMockObjectsFromTestArgumentsRecursively = false;/*** Registers a TestListener.** @param PHPUnit_Framework_TestListener*/public function addListener(PHPUnit_Framework_TestListener $listener){$this->listeners[] = $listener;}/*** Unregisters a TestListener.** @param PHPUnit_Framework_TestListener $listener*/public function removeListener(PHPUnit_Framework_TestListener $listener){foreach ($this->listeners as $key => $_listener) {if ($listener === $_listener) {unset($this->listeners[$key]);}}}/*** Flushes all flushable TestListeners.*/public function flushListeners(){foreach ($this->listeners as $listener) {if ($listener instanceof PHPUnit_Util_Printer) {$listener->flush();}}}/*** Adds an error to the list of errors.** @param PHPUnit_Framework_Test $test* @param Throwable $t* @param float $time*/public function addError(PHPUnit_Framework_Test $test, $t, $time){if ($t instanceof PHPUnit_Framework_RiskyTest) {$this->risky[] = new PHPUnit_Framework_TestFailure($test, $t);$notifyMethod = 'addRiskyTest';if ($test instanceof PHPUnit_Framework_TestCase) {$test->markAsRisky();}if ($this->stopOnRisky) {$this->stop();}} elseif ($t instanceof PHPUnit_Framework_IncompleteTest) {$this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $t);$notifyMethod = 'addIncompleteTest';if ($this->stopOnIncomplete) {$this->stop();}} elseif ($t instanceof PHPUnit_Framework_SkippedTest) {$this->skipped[] = new PHPUnit_Framework_TestFailure($test, $t);$notifyMethod = 'addSkippedTest';if ($this->stopOnSkipped) {$this->stop();}} else {$this->errors[] = new PHPUnit_Framework_TestFailure($test, $t);$notifyMethod = 'addError';if ($this->stopOnError || $this->stopOnFailure) {$this->stop();}}// @see https://github.com/sebastianbergmann/phpunit/issues/1953if ($t instanceof Error) {$t = new PHPUnit_Framework_ExceptionWrapper($t);}foreach ($this->listeners as $listener) {$listener->$notifyMethod($test, $t, $time);}$this->lastTestFailed = true;$this->time += $time;}/*** Adds a warning to the list of warnings.* The passed in exception caused the warning.** @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){if ($this->stopOnWarning) {$this->stop();}$this->warnings[] = new PHPUnit_Framework_TestFailure($test, $e);foreach ($this->listeners as $listener) {// @todo Remove check for PHPUnit 6.0.0// @see https://github.com/sebastianbergmann/phpunit/pull/1840#issuecomment-162535997if (method_exists($listener, 'addWarning')) {$listener->addWarning($test, $e, $time);}}$this->time += $time;}/*** Adds a failure to the list of failures.* The passed in exception caused the failure.** @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){if ($e instanceof PHPUnit_Framework_RiskyTest ||$e instanceof PHPUnit_Framework_OutputError) {$this->risky[] = new PHPUnit_Framework_TestFailure($test, $e);$notifyMethod = 'addRiskyTest';if ($test instanceof PHPUnit_Framework_TestCase) {$test->markAsRisky();}if ($this->stopOnRisky) {$this->stop();}} elseif ($e instanceof PHPUnit_Framework_IncompleteTest) {$this->notImplemented[] = new PHPUnit_Framework_TestFailure($test, $e);$notifyMethod = 'addIncompleteTest';if ($this->stopOnIncomplete) {$this->stop();}} elseif ($e instanceof PHPUnit_Framework_SkippedTest) {$this->skipped[] = new PHPUnit_Framework_TestFailure($test, $e);$notifyMethod = 'addSkippedTest';if ($this->stopOnSkipped) {$this->stop();}} else {$this->failures[] = new PHPUnit_Framework_TestFailure($test, $e);$notifyMethod = 'addFailure';if ($this->stopOnFailure) {$this->stop();}}foreach ($this->listeners as $listener) {$listener->$notifyMethod($test, $e, $time);}$this->lastTestFailed = true;$this->time += $time;}/*** Informs the result that a testsuite will be started.** @param PHPUnit_Framework_TestSuite $suite*/public function startTestSuite(PHPUnit_Framework_TestSuite $suite){if ($this->topTestSuite === null) {$this->topTestSuite = $suite;}foreach ($this->listeners as $listener) {$listener->startTestSuite($suite);}}/*** Informs the result that a testsuite was completed.** @param PHPUnit_Framework_TestSuite $suite*/public function endTestSuite(PHPUnit_Framework_TestSuite $suite){foreach ($this->listeners as $listener) {$listener->endTestSuite($suite);}}/*** Informs the result that a test will be started.** @param PHPUnit_Framework_Test $test*/public function startTest(PHPUnit_Framework_Test $test){$this->lastTestFailed = false;$this->runTests += count($test);foreach ($this->listeners as $listener) {$listener->startTest($test);}}/*** Informs the result that a test was completed.** @param PHPUnit_Framework_Test $test* @param float $time*/public function endTest(PHPUnit_Framework_Test $test, $time){foreach ($this->listeners as $listener) {$listener->endTest($test, $time);}if (!$this->lastTestFailed && $test instanceof PHPUnit_Framework_TestCase) {$class = get_class($test);$key = $class . '::' . $test->getName();$this->passed[$key] = ['result' => $test->getResult(),'size' => PHPUnit_Util_Test::getSize($class,$test->getName(false))];$this->time += $time;}}/*** Returns true if no risky test occurred.** @return bool*/public function allHarmless(){return $this->riskyCount() == 0;}/*** Gets the number of risky tests.** @return int*/public function riskyCount(){return count($this->risky);}/*** Returns true if no incomplete test occurred.** @return bool*/public function allCompletelyImplemented(){return $this->notImplementedCount() == 0;}/*** Gets the number of incomplete tests.** @return int*/public function notImplementedCount(){return count($this->notImplemented);}/*** Returns an Enumeration for the risky tests.** @return array*/public function risky(){return $this->risky;}/*** Returns an Enumeration for the incomplete tests.** @return array*/public function notImplemented(){return $this->notImplemented;}/*** Returns true if no test has been skipped.** @return bool*/public function noneSkipped(){return $this->skippedCount() == 0;}/*** Gets the number of skipped tests.** @return int*/public function skippedCount(){return count($this->skipped);}/*** Returns an Enumeration for the skipped tests.** @return array*/public function skipped(){return $this->skipped;}/*** Gets the number of detected errors.** @return int*/public function errorCount(){return count($this->errors);}/*** Returns an Enumeration for the errors.** @return array*/public function errors(){return $this->errors;}/*** Gets the number of detected failures.** @return int*/public function failureCount(){return count($this->failures);}/*** Returns an Enumeration for the failures.** @return array*/public function failures(){return $this->failures;}/*** Gets the number of detected warnings.** @return int*/public function warningCount(){return count($this->warnings);}/*** Returns an Enumeration for the warnings.** @return array*/public function warnings(){return $this->warnings;}/*** Returns the names of the tests that have passed.** @return array*/public function passed(){return $this->passed;}/*** Returns the (top) test suite.** @return PHPUnit_Framework_TestSuite*/public function topTestSuite(){return $this->topTestSuite;}/*** Returns whether code coverage information should be collected.** @return bool If code coverage should be collected*/public function getCollectCodeCoverageInformation(){return $this->codeCoverage !== null;}/*** Runs a TestCase.** @param PHPUnit_Framework_Test $test*/public function run(PHPUnit_Framework_Test $test){PHPUnit_Framework_Assert::resetCount();$coversNothing = false;if ($test instanceof PHPUnit_Framework_TestCase) {$test->setRegisterMockObjectsFromTestArgumentsRecursively($this->registerMockObjectsFromTestArgumentsRecursively);$annotations = $test->getAnnotations();if (isset($annotations['class']['coversNothing']) || isset($annotations['method']['coversNothing'])) {$coversNothing = true;}}$error = false;$failure = false;$warning = false;$incomplete = false;$risky = false;$skipped = false;$this->startTest($test);$errorHandlerSet = false;if ($this->convertErrorsToExceptions) {$oldErrorHandler = set_error_handler(['PHPUnit_Util_ErrorHandler', 'handleError'],E_ALL | E_STRICT);if ($oldErrorHandler === null) {$errorHandlerSet = true;} else {restore_error_handler();}}$collectCodeCoverage = $this->codeCoverage !== null &&!$test instanceof PHPUnit_Framework_WarningTestCase &&!$coversNothing;if ($collectCodeCoverage) {$this->codeCoverage->start($test);}$monitorFunctions = $this->beStrictAboutResourceUsageDuringSmallTests &&!$test instanceof PHPUnit_Framework_WarningTestCase &&$test->getSize() == PHPUnit_Util_Test::SMALL &&function_exists('xdebug_start_function_monitor');if ($monitorFunctions) {xdebug_start_function_monitor(ResourceOperations::getFunctions());}PHP_Timer::start();try {if (!$test instanceof PHPUnit_Framework_WarningTestCase &&$test->getSize() != PHPUnit_Util_Test::UNKNOWN &&$this->enforceTimeLimit &&extension_loaded('pcntl') && class_exists('PHP_Invoker')) {switch ($test->getSize()) {case PHPUnit_Util_Test::SMALL:$_timeout = $this->timeoutForSmallTests;break;case PHPUnit_Util_Test::MEDIUM:$_timeout = $this->timeoutForMediumTests;break;case PHPUnit_Util_Test::LARGE:$_timeout = $this->timeoutForLargeTests;break;}$invoker = new PHP_Invoker;$invoker->invoke([$test, 'runBare'], [], $_timeout);} else {$test->runBare();}} catch (PHP_Invoker_TimeoutException $e) {$this->addFailure($test,new PHPUnit_Framework_RiskyTestError($e->getMessage()),$_timeout);$risky = true;} catch (PHPUnit_Framework_MockObject_Exception $e) {$e = new PHPUnit_Framework_Warning($e->getMessage());$warning = true;} catch (PHPUnit_Framework_AssertionFailedError $e) {$failure = true;if ($e instanceof PHPUnit_Framework_RiskyTestError) {$risky = true;} elseif ($e instanceof PHPUnit_Framework_IncompleteTestError) {$incomplete = true;} elseif ($e instanceof PHPUnit_Framework_SkippedTestError) {$skipped = true;}} catch (PHPUnit_Framework_Warning $e) {$warning = true;} catch (PHPUnit_Framework_Exception $e) {$error = true;} catch (Throwable $e) {// @see https://github.com/sebastianbergmann/phpunit/issues/2394if (PHP_MAJOR_VERSION === 7 && $e instanceof \AssertionError) {$test->addToAssertionCount(1);$failure = true;$frame = $e->getTrace()[0];$e = new PHPUnit_Framework_AssertionFailedError(sprintf('%s in %s:%s',$e->getMessage(),$frame['file'],$frame['line']));} else {$e = new PHPUnit_Framework_ExceptionWrapper($e);$error = true;}} catch (Exception $e) {$e = new PHPUnit_Framework_ExceptionWrapper($e);$error = true;}$time = PHP_Timer::stop();$test->addToAssertionCount(PHPUnit_Framework_Assert::getCount());if ($monitorFunctions) {$blacklist = new PHPUnit_Util_Blacklist;$functions = xdebug_get_monitored_functions();xdebug_stop_function_monitor();foreach ($functions as $function) {if (!$blacklist->isBlacklisted($function['filename'])) {$this->addFailure($test,new PHPUnit_Framework_RiskyTestError(sprintf('%s() used in %s:%s',$function['function'],$function['filename'],$function['lineno'])),$time);}}}if ($this->beStrictAboutTestsThatDoNotTestAnything &&$test->getNumAssertions() == 0) {$risky = true;}if ($collectCodeCoverage) {$append = !$risky && !$incomplete && !$skipped;$linesToBeCovered = [];$linesToBeUsed = [];if ($append && $test instanceof PHPUnit_Framework_TestCase) {try {$linesToBeCovered = PHPUnit_Util_Test::getLinesToBeCovered(get_class($test),$test->getName(false));$linesToBeUsed = PHPUnit_Util_Test::getLinesToBeUsed(get_class($test),$test->getName(false));} catch (PHPUnit_Framework_InvalidCoversTargetException $cce) {$this->addWarning($test,new PHPUnit_Framework_Warning($cce->getMessage()),$time);}}try {$this->codeCoverage->stop($append,$linesToBeCovered,$linesToBeUsed);} catch (UnintentionallyCoveredCodeException $cce) {$this->addFailure($test,new PHPUnit_Framework_UnintentionallyCoveredCodeError('This test executed code that is not listed as code to be covered or used:' .PHP_EOL . $cce->getMessage()),$time);} catch (CoveredCodeNotExecutedException $cce) {$this->addFailure($test,new PHPUnit_Framework_CoveredCodeNotExecutedException('This test did not execute all the code that is listed as code to be covered:' .PHP_EOL . $cce->getMessage()),$time);} catch (MissingCoversAnnotationException $cce) {if ($linesToBeCovered !== false) {$this->addFailure($test,new PHPUnit_Framework_MissingCoversAnnotationException('This test does not have a @covers annotation but is expected to have one'),$time);}} catch (CodeCoverageException $cce) {$error = true;if (!isset($e)) {$e = $cce;}}}if ($errorHandlerSet === true) {restore_error_handler();}if ($error === true) {$this->addError($test, $e, $time);} elseif ($failure === true) {$this->addFailure($test, $e, $time);} elseif ($warning === true) {$this->addWarning($test, $e, $time);} elseif ($this->beStrictAboutTestsThatDoNotTestAnything &&!$test->doesNotPerformAssertions() &&$test->getNumAssertions() == 0) {$this->addFailure($test,new PHPUnit_Framework_RiskyTestError('This test did not perform any assertions'),$time);} elseif ($this->beStrictAboutOutputDuringTests && $test->hasOutput()) {$this->addFailure($test,new PHPUnit_Framework_OutputError(sprintf('This test printed output: %s',$test->getActualOutput())),$time);} elseif ($this->beStrictAboutTodoAnnotatedTests && $test instanceof PHPUnit_Framework_TestCase) {$annotations = $test->getAnnotations();if (isset($annotations['method']['todo'])) {$this->addFailure($test,new PHPUnit_Framework_RiskyTestError('Test method is annotated with @todo'),$time);}}$this->endTest($test, $time);}/*** Gets the number of run tests.** @return int*/public function count(){return $this->runTests;}/*** Checks whether the test run should stop.** @return bool*/public function shouldStop(){return $this->stop;}/*** Marks that the test run should stop.*/public function stop(){$this->stop = true;}/*** Returns the code coverage object.** @return CodeCoverage*/public function getCodeCoverage(){return $this->codeCoverage;}/*** Sets the code coverage object.** @param CodeCoverage $codeCoverage*/public function setCodeCoverage(CodeCoverage $codeCoverage){$this->codeCoverage = $codeCoverage;}/*** Enables or disables the error-to-exception conversion.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function convertErrorsToExceptions($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->convertErrorsToExceptions = $flag;}/*** Returns the error-to-exception conversion setting.** @return bool*/public function getConvertErrorsToExceptions(){return $this->convertErrorsToExceptions;}/*** Enables or disables the stopping when an error occurs.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnError($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnError = $flag;}/*** Enables or disables the stopping when a failure occurs.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnFailure($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnFailure = $flag;}/*** Enables or disables the stopping when a warning occurs.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnWarning($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnWarning = $flag;}/*** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function beStrictAboutTestsThatDoNotTestAnything($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->beStrictAboutTestsThatDoNotTestAnything = $flag;}/*** @return bool*/public function isStrictAboutTestsThatDoNotTestAnything(){return $this->beStrictAboutTestsThatDoNotTestAnything;}/*** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function beStrictAboutOutputDuringTests($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->beStrictAboutOutputDuringTests = $flag;}/*** @return bool*/public function isStrictAboutOutputDuringTests(){return $this->beStrictAboutOutputDuringTests;}/*** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function beStrictAboutResourceUsageDuringSmallTests($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->beStrictAboutResourceUsageDuringSmallTests = $flag;}/*** @return bool*/public function isStrictAboutResourceUsageDuringSmallTests(){return $this->beStrictAboutResourceUsageDuringSmallTests;}/*** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function enforceTimeLimit($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->enforceTimeLimit = $flag;}/*** @return bool*/public function enforcesTimeLimit(){return $this->enforceTimeLimit;}/*** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function beStrictAboutTodoAnnotatedTests($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->beStrictAboutTodoAnnotatedTests = $flag;}/*** @return bool*/public function isStrictAboutTodoAnnotatedTests(){return $this->beStrictAboutTodoAnnotatedTests;}/*** Enables or disables the stopping for risky tests.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnRisky($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnRisky = $flag;}/*** Enables or disables the stopping for incomplete tests.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnIncomplete($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnIncomplete = $flag;}/*** Enables or disables the stopping for skipped tests.** @param bool $flag** @throws PHPUnit_Framework_Exception*/public function stopOnSkipped($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->stopOnSkipped = $flag;}/*** Returns the time spent running the tests.** @return float*/public function time(){return $this->time;}/*** Returns whether the entire test was successful or not.** @param bool $includeWarnings** @return bool*/public function wasSuccessful($includeWarnings = true){if ($includeWarnings) {return empty($this->errors) && empty($this->failures) && empty($this->warnings);} else {return empty($this->errors) && empty($this->failures);}}/*** Sets the timeout for small tests.** @param int $timeout** @throws PHPUnit_Framework_Exception*/public function setTimeoutForSmallTests($timeout){if (!is_int($timeout)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');}$this->timeoutForSmallTests = $timeout;}/*** Sets the timeout for medium tests.** @param int $timeout** @throws PHPUnit_Framework_Exception*/public function setTimeoutForMediumTests($timeout){if (!is_int($timeout)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');}$this->timeoutForMediumTests = $timeout;}/*** Sets the timeout for large tests.** @param int $timeout** @throws PHPUnit_Framework_Exception*/public function setTimeoutForLargeTests($timeout){if (!is_int($timeout)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'integer');}$this->timeoutForLargeTests = $timeout;}/*** Returns the set timeout for large tests.** @return int*/public function getTimeoutForLargeTests(){return $this->timeoutForLargeTests;}/*** @param bool $flag*/public function setRegisterMockObjectsFromTestArgumentsRecursively($flag){if (!is_bool($flag)) {throw PHPUnit_Util_InvalidArgumentHelper::factory(1, 'boolean');}$this->registerMockObjectsFromTestArgumentsRecursively = $flag;}/*** Returns the class hierarchy for a given class.** @param string $className* @param bool $asReflectionObjects** @return array*/protected function getHierarchy($className, $asReflectionObjects = false){if ($asReflectionObjects) {$classes = [new ReflectionClass($className)];} else {$classes = [$className];}$done = false;while (!$done) {if ($asReflectionObjects) {$class = new ReflectionClass($classes[count($classes) - 1]->getName());} else {$class = new ReflectionClass($classes[count($classes) - 1]);}$parent = $class->getParentClass();if ($parent !== false) {if ($asReflectionObjects) {$classes[] = $parent;} else {$classes[] = $parent->getName();}} else {$done = true;}}return $classes;}}