diff options
Diffstat (limited to 'vendor/phpunit/phpunit/src/TextUI/TestRunner.php')
-rw-r--r-- | vendor/phpunit/phpunit/src/TextUI/TestRunner.php | 1272 |
1 files changed, 1272 insertions, 0 deletions
diff --git a/vendor/phpunit/phpunit/src/TextUI/TestRunner.php b/vendor/phpunit/phpunit/src/TextUI/TestRunner.php new file mode 100644 index 000000000..dfda9359b --- /dev/null +++ b/vendor/phpunit/phpunit/src/TextUI/TestRunner.php @@ -0,0 +1,1272 @@ +<?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\TextUI; + +use const PHP_EOL; +use const PHP_SAPI; +use const PHP_VERSION; +use function array_diff; +use function array_map; +use function array_merge; +use function assert; +use function class_exists; +use function count; +use function dirname; +use function file_put_contents; +use function htmlspecialchars; +use function is_array; +use function is_int; +use function is_string; +use function mt_srand; +use function range; +use function realpath; +use function sprintf; +use function time; +use PHPUnit\Framework\Exception; +use PHPUnit\Framework\TestResult; +use PHPUnit\Framework\TestSuite; +use PHPUnit\Runner\AfterLastTestHook; +use PHPUnit\Runner\BaseTestRunner; +use PHPUnit\Runner\BeforeFirstTestHook; +use PHPUnit\Runner\DefaultTestResultCache; +use PHPUnit\Runner\Extension\ExtensionHandler; +use PHPUnit\Runner\Filter\ExcludeGroupFilterIterator; +use PHPUnit\Runner\Filter\Factory; +use PHPUnit\Runner\Filter\IncludeGroupFilterIterator; +use PHPUnit\Runner\Filter\NameFilterIterator; +use PHPUnit\Runner\Hook; +use PHPUnit\Runner\NullTestResultCache; +use PHPUnit\Runner\ResultCacheExtension; +use PHPUnit\Runner\StandardTestSuiteLoader; +use PHPUnit\Runner\TestHook; +use PHPUnit\Runner\TestListenerAdapter; +use PHPUnit\Runner\TestSuiteLoader; +use PHPUnit\Runner\TestSuiteSorter; +use PHPUnit\Runner\Version; +use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\FilterMapper; +use PHPUnit\TextUI\XmlConfiguration\Configuration; +use PHPUnit\TextUI\XmlConfiguration\Loader; +use PHPUnit\TextUI\XmlConfiguration\PhpHandler; +use PHPUnit\Util\Filesystem; +use PHPUnit\Util\Log\JUnit; +use PHPUnit\Util\Log\TeamCity; +use PHPUnit\Util\Printer; +use PHPUnit\Util\TestDox\CliTestDoxPrinter; +use PHPUnit\Util\TestDox\HtmlResultPrinter; +use PHPUnit\Util\TestDox\TextResultPrinter; +use PHPUnit\Util\TestDox\XmlResultPrinter; +use PHPUnit\Util\XdebugFilterScriptGenerator; +use PHPUnit\Util\Xml\SchemaDetector; +use ReflectionClass; +use ReflectionException; +use SebastianBergmann\CodeCoverage\CodeCoverage; +use SebastianBergmann\CodeCoverage\Driver\Selector; +use SebastianBergmann\CodeCoverage\Exception as CodeCoverageException; +use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter; +use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport; +use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport; +use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport; +use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlReport; +use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport; +use SebastianBergmann\CodeCoverage\Report\Text as TextReport; +use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlReport; +use SebastianBergmann\Comparator\Comparator; +use SebastianBergmann\Environment\Runtime; +use SebastianBergmann\Invoker\Invoker; +use SebastianBergmann\Timer\Timer; + +/** + * @internal This class is not covered by the backward compatibility promise for PHPUnit + */ +final class TestRunner extends BaseTestRunner +{ + public const SUCCESS_EXIT = 0; + + public const FAILURE_EXIT = 1; + + public const EXCEPTION_EXIT = 2; + + /** + * @var CodeCoverageFilter + */ + private $codeCoverageFilter; + + /** + * @var TestSuiteLoader + */ + private $loader; + + /** + * @var ResultPrinter + */ + private $printer; + + /** + * @var bool + */ + private $messagePrinted = false; + + /** + * @var Hook[] + */ + private $extensions = []; + + /** + * @var Timer + */ + private $timer; + + public function __construct(TestSuiteLoader $loader = null, CodeCoverageFilter $filter = null) + { + if ($filter === null) { + $filter = new CodeCoverageFilter; + } + + $this->codeCoverageFilter = $filter; + $this->loader = $loader; + $this->timer = new Timer; + } + + /** + * @throws \PHPUnit\Runner\Exception + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + */ + public function run(TestSuite $suite, array $arguments = [], array $warnings = [], bool $exit = true): TestResult + { + if (isset($arguments['configuration'])) { + $GLOBALS['__PHPUNIT_CONFIGURATION_FILE'] = $arguments['configuration']; + } + + $this->handleConfiguration($arguments); + + $warnings = array_merge($warnings, $arguments['warnings']); + + if (is_int($arguments['columns']) && $arguments['columns'] < 16) { + $arguments['columns'] = 16; + $tooFewColumnsRequested = true; + } + + if (isset($arguments['bootstrap'])) { + $GLOBALS['__PHPUNIT_BOOTSTRAP'] = $arguments['bootstrap']; + } + + if ($arguments['backupGlobals'] === true) { + $suite->setBackupGlobals(true); + } + + if ($arguments['backupStaticAttributes'] === true) { + $suite->setBackupStaticAttributes(true); + } + + if ($arguments['beStrictAboutChangesToGlobalState'] === true) { + $suite->setBeStrictAboutChangesToGlobalState(true); + } + + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + mt_srand($arguments['randomOrderSeed']); + } + + if ($arguments['cacheResult']) { + if (!isset($arguments['cacheResultFile'])) { + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $cacheLocation = $arguments['configurationObject']->filename(); + } else { + $cacheLocation = $_SERVER['PHP_SELF']; + } + + $arguments['cacheResultFile'] = null; + + $cacheResultFile = realpath($cacheLocation); + + if ($cacheResultFile !== false) { + $arguments['cacheResultFile'] = dirname($cacheResultFile); + } + } + + $cache = new DefaultTestResultCache($arguments['cacheResultFile']); + + $this->addExtension(new ResultCacheExtension($cache)); + } + + if ($arguments['executionOrder'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['executionOrderDefects'] !== TestSuiteSorter::ORDER_DEFAULT || $arguments['resolveDependencies']) { + $cache = $cache ?? new NullTestResultCache; + + $cache->load(); + + $sorter = new TestSuiteSorter($cache); + + $sorter->reorderTestsInSuite($suite, $arguments['executionOrder'], $arguments['resolveDependencies'], $arguments['executionOrderDefects']); + $originalExecutionOrder = $sorter->getOriginalExecutionOrder(); + + unset($sorter); + } + + if (is_int($arguments['repeat']) && $arguments['repeat'] > 0) { + $_suite = new TestSuite; + + /* @noinspection PhpUnusedLocalVariableInspection */ + foreach (range(1, $arguments['repeat']) as $step) { + $_suite->addTest($suite); + } + + $suite = $_suite; + + unset($_suite); + } + + $result = $this->createTestResult(); + + $listener = new TestListenerAdapter; + $listenerNeeded = false; + + foreach ($this->extensions as $extension) { + if ($extension instanceof TestHook) { + $listener->add($extension); + + $listenerNeeded = true; + } + } + + if ($listenerNeeded) { + $result->addListener($listener); + } + + unset($listener, $listenerNeeded); + + if ($arguments['convertDeprecationsToExceptions']) { + $result->convertDeprecationsToExceptions(true); + } + + if (!$arguments['convertErrorsToExceptions']) { + $result->convertErrorsToExceptions(false); + } + + if (!$arguments['convertNoticesToExceptions']) { + $result->convertNoticesToExceptions(false); + } + + if (!$arguments['convertWarningsToExceptions']) { + $result->convertWarningsToExceptions(false); + } + + if ($arguments['stopOnError']) { + $result->stopOnError(true); + } + + if ($arguments['stopOnFailure']) { + $result->stopOnFailure(true); + } + + if ($arguments['stopOnWarning']) { + $result->stopOnWarning(true); + } + + if ($arguments['stopOnIncomplete']) { + $result->stopOnIncomplete(true); + } + + if ($arguments['stopOnRisky']) { + $result->stopOnRisky(true); + } + + if ($arguments['stopOnSkipped']) { + $result->stopOnSkipped(true); + } + + if ($arguments['stopOnDefect']) { + $result->stopOnDefect(true); + } + + if ($arguments['registerMockObjectsFromTestArgumentsRecursively']) { + $result->setRegisterMockObjectsFromTestArgumentsRecursively(true); + } + + if ($this->printer === null) { + if (isset($arguments['printer'])) { + if ($arguments['printer'] instanceof ResultPrinter) { + $this->printer = $arguments['printer']; + } elseif (is_string($arguments['printer']) && class_exists($arguments['printer'], false)) { + try { + $reflector = new ReflectionClass($arguments['printer']); + + if ($reflector->implementsInterface(ResultPrinter::class)) { + $this->printer = $this->createPrinter($arguments['printer'], $arguments); + } + + // @codeCoverageIgnoreStart + } catch (ReflectionException $e) { + throw new Exception( + $e->getMessage(), + (int) $e->getCode(), + $e + ); + } + // @codeCoverageIgnoreEnd + } + } else { + $this->printer = $this->createPrinter(DefaultResultPrinter::class, $arguments); + } + } + + if (isset($originalExecutionOrder) && $this->printer instanceof CliTestDoxPrinter) { + assert($this->printer instanceof CliTestDoxPrinter); + + $this->printer->setOriginalExecutionOrder($originalExecutionOrder); + $this->printer->setShowProgressAnimation(!$arguments['noInteraction']); + } + + $this->printer->write( + Version::getVersionString() . "\n" + ); + + foreach ($arguments['listeners'] as $listener) { + $result->addListener($listener); + } + + $result->addListener($this->printer); + + $coverageFilterFromConfigurationFile = false; + $coverageFilterFromOption = false; + $codeCoverageReports = 0; + + if (isset($arguments['testdoxHTMLFile'])) { + $result->addListener( + new HtmlResultPrinter( + $arguments['testdoxHTMLFile'], + $arguments['testdoxGroups'], + $arguments['testdoxExcludeGroups'] + ) + ); + } + + if (isset($arguments['testdoxTextFile'])) { + $result->addListener( + new TextResultPrinter( + $arguments['testdoxTextFile'], + $arguments['testdoxGroups'], + $arguments['testdoxExcludeGroups'] + ) + ); + } + + if (isset($arguments['testdoxXMLFile'])) { + $result->addListener( + new XmlResultPrinter( + $arguments['testdoxXMLFile'] + ) + ); + } + + if (isset($arguments['teamcityLogfile'])) { + $result->addListener( + new TeamCity($arguments['teamcityLogfile']) + ); + } + + if (isset($arguments['junitLogfile'])) { + $result->addListener( + new JUnit( + $arguments['junitLogfile'], + $arguments['reportUselessTests'] + ) + ); + } + + if (isset($arguments['coverageClover'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageCobertura'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageCrap4J'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageHtml'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coveragePHP'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageText'])) { + $codeCoverageReports++; + } + + if (isset($arguments['coverageXml'])) { + $codeCoverageReports++; + } + + if ($codeCoverageReports > 0 || isset($arguments['xdebugFilterFile'])) { + if (isset($arguments['coverageFilter'])) { + if (!is_array($arguments['coverageFilter'])) { + $coverageFilterDirectories = [$arguments['coverageFilter']]; + } else { + $coverageFilterDirectories = $arguments['coverageFilter']; + } + + foreach ($coverageFilterDirectories as $coverageFilterDirectory) { + $this->codeCoverageFilter->includeDirectory($coverageFilterDirectory); + } + + $coverageFilterFromOption = true; + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + $coverageFilterFromConfigurationFile = true; + + (new FilterMapper)->map( + $this->codeCoverageFilter, + $codeCoverageConfiguration + ); + } + } + } + + if ($codeCoverageReports > 0) { + try { + if (isset($codeCoverageConfiguration) && + ($codeCoverageConfiguration->pathCoverage() || (isset($arguments['pathCoverage']) && $arguments['pathCoverage'] === true))) { + $codeCoverageDriver = (new Selector)->forLineAndPathCoverage($this->codeCoverageFilter); + } else { + $codeCoverageDriver = (new Selector)->forLineCoverage($this->codeCoverageFilter); + } + + $codeCoverage = new CodeCoverage( + $codeCoverageDriver, + $this->codeCoverageFilter + ); + + if (isset($codeCoverageConfiguration) && $codeCoverageConfiguration->hasCacheDirectory()) { + $codeCoverage->cacheStaticAnalysis($codeCoverageConfiguration->cacheDirectory()->path()); + } + + if (isset($arguments['coverageCacheDirectory'])) { + $codeCoverage->cacheStaticAnalysis($arguments['coverageCacheDirectory']); + } + + $codeCoverage->excludeSubclassesOfThisClassFromUnintentionallyCoveredCodeCheck(Comparator::class); + + if ($arguments['strictCoverage']) { + $codeCoverage->enableCheckForUnintentionallyCoveredCode(); + } + + if (isset($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'])) { + if ($arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage']) { + $codeCoverage->ignoreDeprecatedCode(); + } else { + $codeCoverage->doNotIgnoreDeprecatedCode(); + } + } + + if (isset($arguments['disableCodeCoverageIgnore'])) { + if ($arguments['disableCodeCoverageIgnore']) { + $codeCoverage->disableAnnotationsForIgnoringCode(); + } else { + $codeCoverage->enableAnnotationsForIgnoringCode(); + } + } + + if (isset($arguments['configurationObject'])) { + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if ($codeCoverageConfiguration->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) { + if ($codeCoverageConfiguration->includeUncoveredFiles()) { + $codeCoverage->includeUncoveredFiles(); + } else { + $codeCoverage->excludeUncoveredFiles(); + } + + if ($codeCoverageConfiguration->processUncoveredFiles()) { + $codeCoverage->processUncoveredFiles(); + } else { + $codeCoverage->doNotProcessUncoveredFiles(); + } + } + } + + if ($this->codeCoverageFilter->isEmpty()) { + if (!$coverageFilterFromConfigurationFile && !$coverageFilterFromOption) { + $warnings[] = 'No filter is configured, code coverage will not be processed'; + } else { + $warnings[] = 'Incorrect filter configuration, code coverage will not be processed'; + } + + unset($codeCoverage); + } + } catch (CodeCoverageException $e) { + $warnings[] = $e->getMessage(); + } + } + + if ($arguments['verbose']) { + if (PHP_SAPI === 'phpdbg') { + $this->writeMessage('Runtime', 'PHPDBG ' . PHP_VERSION); + } else { + $runtime = 'PHP ' . PHP_VERSION; + + if (isset($codeCoverageDriver)) { + $runtime .= ' with ' . $codeCoverageDriver->nameAndVersion(); + } + + $this->writeMessage('Runtime', $runtime); + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + $this->writeMessage( + 'Configuration', + $arguments['configurationObject']->filename() + ); + } + + foreach ($arguments['loadedExtensions'] as $extension) { + $this->writeMessage( + 'Extension', + $extension + ); + } + + foreach ($arguments['notLoadedExtensions'] as $extension) { + $this->writeMessage( + 'Extension', + $extension + ); + } + } + + if ($arguments['executionOrder'] === TestSuiteSorter::ORDER_RANDOMIZED) { + $this->writeMessage( + 'Random Seed', + (string) $arguments['randomOrderSeed'] + ); + } + + if (isset($tooFewColumnsRequested)) { + $warnings[] = 'Less than 16 columns requested, number of columns set to 16'; + } + + if ((new Runtime)->discardsComments()) { + $warnings[] = 'opcache.save_comments=0 set; annotations will not work'; + } + + if (isset($arguments['conflictBetweenPrinterClassAndTestdox'])) { + $warnings[] = 'Directives printerClass and testdox are mutually exclusive'; + } + + foreach ($warnings as $warning) { + $this->writeMessage('Warning', $warning); + } + + if (isset($arguments['configurationObject'])) { + assert($arguments['configurationObject'] instanceof Configuration); + + if ($arguments['configurationObject']->hasValidationErrors()) { + if ((new SchemaDetector)->detect($arguments['configurationObject']->filename())->detected()) { + $this->writeMessage('Warning', 'Your XML configuration validates against a deprecated schema.'); + $this->writeMessage('Suggestion', 'Migrate your XML configuration using "--migrate-configuration"!'); + } else { + $this->write( + "\n Warning - The configuration file did not pass validation!\n The following problems have been detected:\n" + ); + + $this->write($arguments['configurationObject']->validationErrors()); + + $this->write("\n Test results may not be as expected.\n\n"); + } + } + } + + if (isset($arguments['xdebugFilterFile'], $codeCoverageConfiguration)) { + $this->write(PHP_EOL . 'Please note that --dump-xdebug-filter and --prepend are deprecated and will be removed in PHPUnit 10.' . PHP_EOL); + + $script = (new XdebugFilterScriptGenerator)->generate($codeCoverageConfiguration); + + if ($arguments['xdebugFilterFile'] !== 'php://stdout' && $arguments['xdebugFilterFile'] !== 'php://stderr' && !Filesystem::createDirectory(dirname($arguments['xdebugFilterFile']))) { + $this->write(sprintf('Cannot write Xdebug filter script to %s ' . PHP_EOL, $arguments['xdebugFilterFile'])); + + exit(self::EXCEPTION_EXIT); + } + + file_put_contents($arguments['xdebugFilterFile'], $script); + + $this->write(sprintf('Wrote Xdebug filter script to %s ' . PHP_EOL . PHP_EOL, $arguments['xdebugFilterFile'])); + + exit(self::SUCCESS_EXIT); + } + + $this->printer->write("\n"); + + if (isset($codeCoverage)) { + $result->setCodeCoverage($codeCoverage); + } + + $result->beStrictAboutTestsThatDoNotTestAnything($arguments['reportUselessTests']); + $result->beStrictAboutOutputDuringTests($arguments['disallowTestOutput']); + $result->beStrictAboutTodoAnnotatedTests($arguments['disallowTodoAnnotatedTests']); + $result->beStrictAboutResourceUsageDuringSmallTests($arguments['beStrictAboutResourceUsageDuringSmallTests']); + + if ($arguments['enforceTimeLimit'] === true && !(new Invoker)->canInvokeWithTimeout()) { + $this->writeMessage('Error', 'PHP extension pcntl is required for enforcing time limits'); + } + + $result->enforceTimeLimit($arguments['enforceTimeLimit']); + $result->setDefaultTimeLimit($arguments['defaultTimeLimit']); + $result->setTimeoutForSmallTests($arguments['timeoutForSmallTests']); + $result->setTimeoutForMediumTests($arguments['timeoutForMediumTests']); + $result->setTimeoutForLargeTests($arguments['timeoutForLargeTests']); + + if (isset($arguments['forceCoversAnnotation']) && $arguments['forceCoversAnnotation'] === true) { + $result->forceCoversAnnotation(); + } + + $this->processSuiteFilters($suite, $arguments); + $suite->setRunTestInSeparateProcess($arguments['processIsolation']); + + foreach ($this->extensions as $extension) { + if ($extension instanceof BeforeFirstTestHook) { + $extension->executeBeforeFirstTest(); + } + } + + $testSuiteWarningsPrinted = false; + + foreach ($suite->warnings() as $warning) { + $this->writeMessage('Warning', $warning); + + $testSuiteWarningsPrinted = true; + } + + if ($testSuiteWarningsPrinted) { + $this->write(PHP_EOL); + } + + $suite->run($result); + + foreach ($this->extensions as $extension) { + if ($extension instanceof AfterLastTestHook) { + $extension->executeAfterLastTest(); + } + } + + $result->flushListeners(); + $this->printer->printResult($result); + + if (isset($codeCoverage)) { + if (isset($arguments['coverageClover'])) { + $this->codeCoverageGenerationStart('Clover XML'); + + try { + $writer = new CloverReport; + $writer->process($codeCoverage, $arguments['coverageClover']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageCobertura'])) { + $this->codeCoverageGenerationStart('Cobertura XML'); + + try { + $writer = new CoberturaReport; + $writer->process($codeCoverage, $arguments['coverageCobertura']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageCrap4J'])) { + $this->codeCoverageGenerationStart('Crap4J XML'); + + try { + $writer = new Crap4jReport($arguments['crap4jThreshold']); + $writer->process($codeCoverage, $arguments['coverageCrap4J']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageHtml'])) { + $this->codeCoverageGenerationStart('HTML'); + + try { + $writer = new HtmlReport( + $arguments['reportLowUpperBound'], + $arguments['reportHighLowerBound'], + sprintf( + ' and <a href="https://phpunit.de/">PHPUnit %s</a>', + Version::id() + ) + ); + + $writer->process($codeCoverage, $arguments['coverageHtml']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coveragePHP'])) { + $this->codeCoverageGenerationStart('PHP'); + + try { + $writer = new PhpReport; + $writer->process($codeCoverage, $arguments['coveragePHP']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + + if (isset($arguments['coverageText'])) { + if ($arguments['coverageText'] === 'php://stdout') { + $outputStream = $this->printer; + $colors = $arguments['colors'] && $arguments['colors'] !== DefaultResultPrinter::COLOR_NEVER; + } else { + $outputStream = new Printer($arguments['coverageText']); + $colors = false; + } + + $processor = new TextReport( + $arguments['reportLowUpperBound'], + $arguments['reportHighLowerBound'], + $arguments['coverageTextShowUncoveredFiles'], + $arguments['coverageTextShowOnlySummary'] + ); + + $outputStream->write( + $processor->process($codeCoverage, $colors) + ); + } + + if (isset($arguments['coverageXml'])) { + $this->codeCoverageGenerationStart('PHPUnit XML'); + + try { + $writer = new XmlReport(Version::id()); + $writer->process($codeCoverage, $arguments['coverageXml']); + + $this->codeCoverageGenerationSucceeded(); + + unset($writer); + } catch (CodeCoverageException $e) { + $this->codeCoverageGenerationFailed($e); + } + } + } + + if ($exit) { + if (isset($arguments['failOnEmptyTestSuite']) && $arguments['failOnEmptyTestSuite'] === true && count($result) === 0) { + exit(self::FAILURE_EXIT); + } + + if ($result->wasSuccessfulIgnoringWarnings()) { + if ($arguments['failOnRisky'] && !$result->allHarmless()) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnWarning'] && $result->warningCount() > 0) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnIncomplete'] && $result->notImplementedCount() > 0) { + exit(self::FAILURE_EXIT); + } + + if ($arguments['failOnSkipped'] && $result->skippedCount() > 0) { + exit(self::FAILURE_EXIT); + } + + exit(self::SUCCESS_EXIT); + } + + if ($result->errorCount() > 0) { + exit(self::EXCEPTION_EXIT); + } + + if ($result->failureCount() > 0) { + exit(self::FAILURE_EXIT); + } + } + + return $result; + } + + /** + * Returns the loader to be used. + */ + public function getLoader(): TestSuiteLoader + { + if ($this->loader === null) { + $this->loader = new StandardTestSuiteLoader; + } + + return $this->loader; + } + + public function addExtension(Hook $extension): void + { + $this->extensions[] = $extension; + } + + /** + * Override to define how to handle a failed loading of + * a test suite. + */ + protected function runFailed(string $message): void + { + $this->write($message . PHP_EOL); + + exit(self::FAILURE_EXIT); + } + + private function createTestResult(): TestResult + { + return new TestResult; + } + + private function write(string $buffer): void + { + if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') { + $buffer = htmlspecialchars($buffer); + } + + if ($this->printer !== null) { + $this->printer->write($buffer); + } else { + print $buffer; + } + } + + /** + * @throws \PHPUnit\TextUI\XmlConfiguration\Exception + * @throws Exception + */ + private function handleConfiguration(array &$arguments): void + { + if (!isset($arguments['configurationObject']) && isset($arguments['configuration'])) { + $arguments['configurationObject'] = (new Loader)->load($arguments['configuration']); + } + + if (!isset($arguments['warnings'])) { + $arguments['warnings'] = []; + } + + $arguments['debug'] = $arguments['debug'] ?? false; + $arguments['filter'] = $arguments['filter'] ?? false; + $arguments['listeners'] = $arguments['listeners'] ?? []; + + if (isset($arguments['configurationObject'])) { + (new PhpHandler)->handle($arguments['configurationObject']->php()); + + $codeCoverageConfiguration = $arguments['configurationObject']->codeCoverage(); + + if (!isset($arguments['noCoverage'])) { + if (!isset($arguments['coverageClover']) && $codeCoverageConfiguration->hasClover()) { + $arguments['coverageClover'] = $codeCoverageConfiguration->clover()->target()->path(); + } + + if (!isset($arguments['coverageCobertura']) && $codeCoverageConfiguration->hasCobertura()) { + $arguments['coverageCobertura'] = $codeCoverageConfiguration->cobertura()->target()->path(); + } + + if (!isset($arguments['coverageCrap4J']) && $codeCoverageConfiguration->hasCrap4j()) { + $arguments['coverageCrap4J'] = $codeCoverageConfiguration->crap4j()->target()->path(); + + if (!isset($arguments['crap4jThreshold'])) { + $arguments['crap4jThreshold'] = $codeCoverageConfiguration->crap4j()->threshold(); + } + } + + if (!isset($arguments['coverageHtml']) && $codeCoverageConfiguration->hasHtml()) { + $arguments['coverageHtml'] = $codeCoverageConfiguration->html()->target()->path(); + + if (!isset($arguments['reportLowUpperBound'])) { + $arguments['reportLowUpperBound'] = $codeCoverageConfiguration->html()->lowUpperBound(); + } + + if (!isset($arguments['reportHighLowerBound'])) { + $arguments['reportHighLowerBound'] = $codeCoverageConfiguration->html()->highLowerBound(); + } + } + + if (!isset($arguments['coveragePHP']) && $codeCoverageConfiguration->hasPhp()) { + $arguments['coveragePHP'] = $codeCoverageConfiguration->php()->target()->path(); + } + + if (!isset($arguments['coverageText']) && $codeCoverageConfiguration->hasText()) { + $arguments['coverageText'] = $codeCoverageConfiguration->text()->target()->path(); + $arguments['coverageTextShowUncoveredFiles'] = $codeCoverageConfiguration->text()->showUncoveredFiles(); + $arguments['coverageTextShowOnlySummary'] = $codeCoverageConfiguration->text()->showOnlySummary(); + } + + if (!isset($arguments['coverageXml']) && $codeCoverageConfiguration->hasXml()) { + $arguments['coverageXml'] = $codeCoverageConfiguration->xml()->target()->path(); + } + } + + $phpunitConfiguration = $arguments['configurationObject']->phpunit(); + + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? $phpunitConfiguration->backupGlobals(); + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? $phpunitConfiguration->backupStaticAttributes(); + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? $phpunitConfiguration->beStrictAboutChangesToGlobalState(); + $arguments['cacheResult'] = $arguments['cacheResult'] ?? $phpunitConfiguration->cacheResult(); + $arguments['colors'] = $arguments['colors'] ?? $phpunitConfiguration->colors(); + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? $phpunitConfiguration->convertDeprecationsToExceptions(); + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? $phpunitConfiguration->convertErrorsToExceptions(); + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? $phpunitConfiguration->convertNoticesToExceptions(); + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? $phpunitConfiguration->convertWarningsToExceptions(); + $arguments['processIsolation'] = $arguments['processIsolation'] ?? $phpunitConfiguration->processIsolation(); + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? $phpunitConfiguration->stopOnDefect(); + $arguments['stopOnError'] = $arguments['stopOnError'] ?? $phpunitConfiguration->stopOnError(); + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? $phpunitConfiguration->stopOnFailure(); + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? $phpunitConfiguration->stopOnWarning(); + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? $phpunitConfiguration->stopOnIncomplete(); + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? $phpunitConfiguration->stopOnRisky(); + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? $phpunitConfiguration->stopOnSkipped(); + $arguments['failOnEmptyTestSuite'] = $arguments['failOnEmptyTestSuite'] ?? $phpunitConfiguration->failOnEmptyTestSuite(); + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? $phpunitConfiguration->failOnIncomplete(); + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? $phpunitConfiguration->failOnRisky(); + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? $phpunitConfiguration->failOnSkipped(); + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? $phpunitConfiguration->failOnWarning(); + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? $phpunitConfiguration->enforceTimeLimit(); + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? $phpunitConfiguration->defaultTimeLimit(); + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? $phpunitConfiguration->timeoutForSmallTests(); + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? $phpunitConfiguration->timeoutForMediumTests(); + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? $phpunitConfiguration->timeoutForLargeTests(); + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? $phpunitConfiguration->beStrictAboutTestsThatDoNotTestAnything(); + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? $phpunitConfiguration->beStrictAboutCoversAnnotation(); + $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $arguments['ignoreDeprecatedCodeUnitsFromCodeCoverage'] ?? $codeCoverageConfiguration->ignoreDeprecatedCodeUnits(); + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? $phpunitConfiguration->beStrictAboutOutputDuringTests(); + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? $phpunitConfiguration->beStrictAboutTodoAnnotatedTests(); + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? $phpunitConfiguration->beStrictAboutResourceUsageDuringSmallTests(); + $arguments['verbose'] = $arguments['verbose'] ?? $phpunitConfiguration->verbose(); + $arguments['reverseDefectList'] = $arguments['reverseDefectList'] ?? $phpunitConfiguration->reverseDefectList(); + $arguments['forceCoversAnnotation'] = $arguments['forceCoversAnnotation'] ?? $phpunitConfiguration->forceCoversAnnotation(); + $arguments['disableCodeCoverageIgnore'] = $arguments['disableCodeCoverageIgnore'] ?? $codeCoverageConfiguration->disableCodeCoverageIgnore(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? $phpunitConfiguration->registerMockObjectsFromTestArgumentsRecursively(); + $arguments['noInteraction'] = $arguments['noInteraction'] ?? $phpunitConfiguration->noInteraction(); + $arguments['executionOrder'] = $arguments['executionOrder'] ?? $phpunitConfiguration->executionOrder(); + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? $phpunitConfiguration->resolveDependencies(); + + if (!isset($arguments['bootstrap']) && $phpunitConfiguration->hasBootstrap()) { + $arguments['bootstrap'] = $phpunitConfiguration->bootstrap(); + } + + if (!isset($arguments['cacheResultFile']) && $phpunitConfiguration->hasCacheResultFile()) { + $arguments['cacheResultFile'] = $phpunitConfiguration->cacheResultFile(); + } + + if (!isset($arguments['executionOrderDefects'])) { + $arguments['executionOrderDefects'] = $phpunitConfiguration->defectsFirst() ? TestSuiteSorter::ORDER_DEFECTS_FIRST : TestSuiteSorter::ORDER_DEFAULT; + } + + if ($phpunitConfiguration->conflictBetweenPrinterClassAndTestdox()) { + $arguments['conflictBetweenPrinterClassAndTestdox'] = true; + } + + $groupCliArgs = []; + + if (!empty($arguments['groups'])) { + $groupCliArgs = $arguments['groups']; + } + + $groupConfiguration = $arguments['configurationObject']->groups(); + + if (!isset($arguments['groups']) && $groupConfiguration->hasInclude()) { + $arguments['groups'] = $groupConfiguration->include()->asArrayOfStrings(); + } + + if (!isset($arguments['excludeGroups']) && $groupConfiguration->hasExclude()) { + $arguments['excludeGroups'] = array_diff($groupConfiguration->exclude()->asArrayOfStrings(), $groupCliArgs); + } + + $extensionHandler = new ExtensionHandler; + + foreach ($arguments['configurationObject']->extensions() as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + + foreach ($arguments['configurationObject']->listeners() as $listener) { + $arguments['listeners'][] = $extensionHandler->createTestListenerInstance($listener); + } + + unset($extensionHandler); + + foreach ($arguments['unavailableExtensions'] as $extension) { + $arguments['warnings'][] = sprintf( + 'Extension "%s" is not available', + $extension + ); + } + + $loggingConfiguration = $arguments['configurationObject']->logging(); + + if (!isset($arguments['noLogging'])) { + if ($loggingConfiguration->hasText()) { + $arguments['listeners'][] = new DefaultResultPrinter( + $loggingConfiguration->text()->target()->path(), + true + ); + } + + if (!isset($arguments['teamcityLogfile']) && $loggingConfiguration->hasTeamCity()) { + $arguments['teamcityLogfile'] = $loggingConfiguration->teamCity()->target()->path(); + } + + if (!isset($arguments['junitLogfile']) && $loggingConfiguration->hasJunit()) { + $arguments['junitLogfile'] = $loggingConfiguration->junit()->target()->path(); + } + + if (!isset($arguments['testdoxHTMLFile']) && $loggingConfiguration->hasTestDoxHtml()) { + $arguments['testdoxHTMLFile'] = $loggingConfiguration->testDoxHtml()->target()->path(); + } + + if (!isset($arguments['testdoxTextFile']) && $loggingConfiguration->hasTestDoxText()) { + $arguments['testdoxTextFile'] = $loggingConfiguration->testDoxText()->target()->path(); + } + + if (!isset($arguments['testdoxXMLFile']) && $loggingConfiguration->hasTestDoxXml()) { + $arguments['testdoxXMLFile'] = $loggingConfiguration->testDoxXml()->target()->path(); + } + } + + $testdoxGroupConfiguration = $arguments['configurationObject']->testdoxGroups(); + + if (!isset($arguments['testdoxGroups']) && $testdoxGroupConfiguration->hasInclude()) { + $arguments['testdoxGroups'] = $testdoxGroupConfiguration->include()->asArrayOfStrings(); + } + + if (!isset($arguments['testdoxExcludeGroups']) && $testdoxGroupConfiguration->hasExclude()) { + $arguments['testdoxExcludeGroups'] = $testdoxGroupConfiguration->exclude()->asArrayOfStrings(); + } + } + + $extensionHandler = new ExtensionHandler; + + foreach ($arguments['extensions'] as $extension) { + $extensionHandler->registerExtension($extension, $this); + } + + unset($extensionHandler); + + $arguments['backupGlobals'] = $arguments['backupGlobals'] ?? null; + $arguments['backupStaticAttributes'] = $arguments['backupStaticAttributes'] ?? null; + $arguments['beStrictAboutChangesToGlobalState'] = $arguments['beStrictAboutChangesToGlobalState'] ?? null; + $arguments['beStrictAboutResourceUsageDuringSmallTests'] = $arguments['beStrictAboutResourceUsageDuringSmallTests'] ?? false; + $arguments['cacheResult'] = $arguments['cacheResult'] ?? true; + $arguments['colors'] = $arguments['colors'] ?? DefaultResultPrinter::COLOR_DEFAULT; + $arguments['columns'] = $arguments['columns'] ?? 80; + $arguments['convertDeprecationsToExceptions'] = $arguments['convertDeprecationsToExceptions'] ?? false; + $arguments['convertErrorsToExceptions'] = $arguments['convertErrorsToExceptions'] ?? true; + $arguments['convertNoticesToExceptions'] = $arguments['convertNoticesToExceptions'] ?? true; + $arguments['convertWarningsToExceptions'] = $arguments['convertWarningsToExceptions'] ?? true; + $arguments['crap4jThreshold'] = $arguments['crap4jThreshold'] ?? 30; + $arguments['disallowTestOutput'] = $arguments['disallowTestOutput'] ?? false; + $arguments['disallowTodoAnnotatedTests'] = $arguments['disallowTodoAnnotatedTests'] ?? false; + $arguments['defaultTimeLimit'] = $arguments['defaultTimeLimit'] ?? 0; + $arguments['enforceTimeLimit'] = $arguments['enforceTimeLimit'] ?? false; + $arguments['excludeGroups'] = $arguments['excludeGroups'] ?? []; + $arguments['executionOrder'] = $arguments['executionOrder'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['executionOrderDefects'] = $arguments['executionOrderDefects'] ?? TestSuiteSorter::ORDER_DEFAULT; + $arguments['failOnIncomplete'] = $arguments['failOnIncomplete'] ?? false; + $arguments['failOnRisky'] = $arguments['failOnRisky'] ?? false; + $arguments['failOnSkipped'] = $arguments['failOnSkipped'] ?? false; + $arguments['failOnWarning'] = $arguments['failOnWarning'] ?? false; + $arguments['groups'] = $arguments['groups'] ?? []; + $arguments['noInteraction'] = $arguments['noInteraction'] ?? false; + $arguments['processIsolation'] = $arguments['processIsolation'] ?? false; + $arguments['randomOrderSeed'] = $arguments['randomOrderSeed'] ?? time(); + $arguments['registerMockObjectsFromTestArgumentsRecursively'] = $arguments['registerMockObjectsFromTestArgumentsRecursively'] ?? false; + $arguments['repeat'] = $arguments['repeat'] ?? false; + $arguments['reportHighLowerBound'] = $arguments['reportHighLowerBound'] ?? 90; + $arguments['reportLowUpperBound'] = $arguments['reportLowUpperBound'] ?? 50; + $arguments['reportUselessTests'] = $arguments['reportUselessTests'] ?? true; + $arguments['reverseList'] = $arguments['reverseList'] ?? false; + $arguments['resolveDependencies'] = $arguments['resolveDependencies'] ?? true; + $arguments['stopOnError'] = $arguments['stopOnError'] ?? false; + $arguments['stopOnFailure'] = $arguments['stopOnFailure'] ?? false; + $arguments['stopOnIncomplete'] = $arguments['stopOnIncomplete'] ?? false; + $arguments['stopOnRisky'] = $arguments['stopOnRisky'] ?? false; + $arguments['stopOnSkipped'] = $arguments['stopOnSkipped'] ?? false; + $arguments['stopOnWarning'] = $arguments['stopOnWarning'] ?? false; + $arguments['stopOnDefect'] = $arguments['stopOnDefect'] ?? false; + $arguments['strictCoverage'] = $arguments['strictCoverage'] ?? false; + $arguments['testdoxExcludeGroups'] = $arguments['testdoxExcludeGroups'] ?? []; + $arguments['testdoxGroups'] = $arguments['testdoxGroups'] ?? []; + $arguments['timeoutForLargeTests'] = $arguments['timeoutForLargeTests'] ?? 60; + $arguments['timeoutForMediumTests'] = $arguments['timeoutForMediumTests'] ?? 10; + $arguments['timeoutForSmallTests'] = $arguments['timeoutForSmallTests'] ?? 1; + $arguments['verbose'] = $arguments['verbose'] ?? false; + + if ($arguments['reportLowUpperBound'] > $arguments['reportHighLowerBound']) { + $arguments['reportLowUpperBound'] = 50; + $arguments['reportHighLowerBound'] = 90; + } + } + + private function processSuiteFilters(TestSuite $suite, array $arguments): void + { + if (!$arguments['filter'] && + empty($arguments['groups']) && + empty($arguments['excludeGroups']) && + empty($arguments['testsCovering']) && + empty($arguments['testsUsing'])) { + return; + } + + $filterFactory = new Factory; + + if (!empty($arguments['excludeGroups'])) { + $filterFactory->addFilter( + new ReflectionClass(ExcludeGroupFilterIterator::class), + $arguments['excludeGroups'] + ); + } + + if (!empty($arguments['groups'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + $arguments['groups'] + ); + } + + if (!empty($arguments['testsCovering'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + array_map( + static function (string $name): string + { + return '__phpunit_covers_' . $name; + }, + $arguments['testsCovering'] + ) + ); + } + + if (!empty($arguments['testsUsing'])) { + $filterFactory->addFilter( + new ReflectionClass(IncludeGroupFilterIterator::class), + array_map( + static function (string $name): string + { + return '__phpunit_uses_' . $name; + }, + $arguments['testsUsing'] + ) + ); + } + + if ($arguments['filter']) { + $filterFactory->addFilter( + new ReflectionClass(NameFilterIterator::class), + $arguments['filter'] + ); + } + + $suite->injectFilter($filterFactory); + } + + private function writeMessage(string $type, string $message): void + { + if (!$this->messagePrinted) { + $this->write("\n"); + } + + $this->write( + sprintf( + "%-15s%s\n", + $type . ':', + $message + ) + ); + + $this->messagePrinted = true; + } + + private function createPrinter(string $class, array $arguments): ResultPrinter + { + $object = new $class( + (isset($arguments['stderr']) && $arguments['stderr'] === true) ? 'php://stderr' : null, + $arguments['verbose'], + $arguments['colors'], + $arguments['debug'], + $arguments['columns'], + $arguments['reverseList'] + ); + + assert($object instanceof ResultPrinter); + + return $object; + } + + private function codeCoverageGenerationStart(string $format): void + { + $this->printer->write( + sprintf( + "\nGenerating code coverage report in %s format ... ", + $format + ) + ); + + $this->timer->start(); + } + + private function codeCoverageGenerationSucceeded(): void + { + $this->printer->write( + sprintf( + "done [%s]\n", + $this->timer->stop()->asString() + ) + ); + } + + private function codeCoverageGenerationFailed(\Exception $e): void + { + $this->printer->write( + sprintf( + "failed [%s]\n%s\n", + $this->timer->stop()->asString(), + $e->getMessage() + ) + ); + } +} |