"Fossies" - the Fresh Open Source Software Archive

Member "phpunit-9.0.1/src/TextUI/Configuration/Loader.php" (13 Feb 2020, 31883 Bytes) of package /linux/www/phpunit-9.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) PHP source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "Loader.php" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 9.0.0_vs_9.0.1.

    1 <?php declare(strict_types=1);
    2 /*
    3  * This file is part of PHPUnit.
    4  *
    5  * (c) Sebastian Bergmann <sebastian@phpunit.de>
    6  *
    7  * For the full copyright and license information, please view the LICENSE
    8  * file that was distributed with this source code.
    9  */
   10 namespace PHPUnit\TextUI\Configuration;
   11 
   12 use PHPUnit\Runner\TestSuiteSorter;
   13 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Clover;
   14 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Crap4j;
   15 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Html as CodeCoverageHtml;
   16 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Php as CodeCoveragePhp;
   17 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Text as CodeCoverageText;
   18 use PHPUnit\TextUI\Configuration\Logging\CodeCoverage\Xml as CodeCoverageXml;
   19 use PHPUnit\TextUI\Configuration\Logging\Junit;
   20 use PHPUnit\TextUI\Configuration\Logging\Logging;
   21 use PHPUnit\TextUI\Configuration\Logging\PlainText;
   22 use PHPUnit\TextUI\Configuration\Logging\TeamCity;
   23 use PHPUnit\TextUI\Configuration\Logging\TestDox\Html as TestDoxHtml;
   24 use PHPUnit\TextUI\Configuration\Logging\TestDox\Text as TestDoxText;
   25 use PHPUnit\TextUI\Configuration\Logging\TestDox\Xml as TestDoxXml;
   26 use PHPUnit\TextUI\Configuration\TestSuite as TestSuiteConfiguration;
   27 use PHPUnit\TextUI\DefaultResultPrinter;
   28 use PHPUnit\Util\TestDox\CliTestDoxPrinter;
   29 use PHPUnit\Util\VersionComparisonOperator;
   30 use PHPUnit\Util\Xml;
   31 
   32 /**
   33  * @internal This class is not covered by the backward compatibility promise for PHPUnit
   34  */
   35 final class Loader
   36 {
   37     public function load(string $filename): Configuration
   38     {
   39         $document = Xml::loadFile($filename, false, true, true);
   40         $xpath    = new \DOMXPath($document);
   41 
   42         return new Configuration(
   43             $filename,
   44             $this->validate($document),
   45             $this->extensions($filename, $xpath),
   46             $this->filter($filename, $xpath),
   47             $this->groups($xpath),
   48             $this->testdoxGroups($xpath),
   49             $this->listeners($filename, $xpath),
   50             $this->logging($filename, $xpath),
   51             $this->php($filename, $xpath),
   52             $this->phpunit($filename, $document),
   53             $this->testSuite($filename, $xpath)
   54         );
   55     }
   56 
   57     public function logging(string $filename, \DOMXPath $xpath): Logging
   58     {
   59         $codeCoverageClover = null;
   60         $codeCoverageCrap4j = null;
   61         $codeCoverageHtml   = null;
   62         $codeCoveragePhp    = null;
   63         $codeCoverageText   = null;
   64         $codeCoverageXml    = null;
   65         $junit              = null;
   66         $plainText          = null;
   67         $teamCity           = null;
   68         $testDoxHtml        = null;
   69         $testDoxText        = null;
   70         $testDoxXml         = null;
   71 
   72         foreach ($xpath->query('logging/log') as $log) {
   73             \assert($log instanceof \DOMElement);
   74 
   75             $type   = (string) $log->getAttribute('type');
   76             $target = (string) $log->getAttribute('target');
   77 
   78             if (!$target) {
   79                 continue;
   80             }
   81 
   82             $target = $this->toAbsolutePath($filename, $target);
   83 
   84             switch ($type) {
   85                 case 'coverage-clover':
   86                     $codeCoverageClover = new Clover(
   87                         new File($target)
   88                     );
   89 
   90                     break;
   91 
   92                 case 'coverage-crap4j':
   93                     $codeCoverageCrap4j = new Crap4j(
   94                         new File($target),
   95                         $this->getIntegerAttribute($log, 'threshold', 30)
   96                     );
   97 
   98                     break;
   99 
  100                 case 'coverage-html':
  101                     $codeCoverageHtml = new CodeCoverageHtml(
  102                         new Directory($target),
  103                         $this->getIntegerAttribute($log, 'lowUpperBound', 50),
  104                         $this->getIntegerAttribute($log, 'highLowerBound', 90)
  105                     );
  106 
  107                     break;
  108 
  109                 case 'coverage-php':
  110                     $codeCoveragePhp = new CodeCoveragePhp(
  111                         new File($target)
  112                     );
  113 
  114                     break;
  115 
  116                 case 'coverage-text':
  117                     $codeCoverageText = new CodeCoverageText(
  118                         new File($target),
  119                         $this->getBooleanAttribute($log, 'showUncoveredFiles', false),
  120                         $this->getBooleanAttribute($log, 'showOnlySummary', false)
  121                     );
  122 
  123                     break;
  124 
  125                 case 'coverage-xml':
  126                     $codeCoverageXml = new CodeCoverageXml(
  127                         new Directory($target)
  128                     );
  129 
  130                     break;
  131 
  132                 case 'plain':
  133                     $plainText = new PlainText(
  134                         new File($target)
  135                     );
  136 
  137                     break;
  138 
  139                 case 'junit':
  140                     $junit = new Junit(
  141                         new File($target)
  142                     );
  143 
  144                     break;
  145 
  146                 case 'teamcity':
  147                     $teamCity = new TeamCity(
  148                         new File($target)
  149                     );
  150 
  151                     break;
  152 
  153                 case 'testdox-html':
  154                     $testDoxHtml = new TestDoxHtml(
  155                         new File($target)
  156                     );
  157 
  158                     break;
  159 
  160                 case 'testdox-text':
  161                     $testDoxText = new TestDoxText(
  162                         new File($target)
  163                     );
  164 
  165                     break;
  166 
  167                 case 'testdox-xml':
  168                     $testDoxXml = new TestDoxXml(
  169                         new File($target)
  170                     );
  171 
  172                     break;
  173             }
  174         }
  175 
  176         return new Logging(
  177             $codeCoverageClover,
  178             $codeCoverageCrap4j,
  179             $codeCoverageHtml,
  180             $codeCoveragePhp,
  181             $codeCoverageText,
  182             $codeCoverageXml,
  183             $junit,
  184             $plainText,
  185             $teamCity,
  186             $testDoxHtml,
  187             $testDoxText,
  188             $testDoxXml
  189         );
  190     }
  191 
  192     /**
  193      * @psalm-return array<int,array<int,string>>
  194      */
  195     private function validate(\DOMDocument $document): array
  196     {
  197         $original    = \libxml_use_internal_errors(true);
  198         $xsdFilename = __DIR__ . '/../../../phpunit.xsd';
  199 
  200         if (\defined('__PHPUNIT_PHAR_ROOT__')) {
  201             $xsdFilename =  __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd';
  202         }
  203 
  204         $document->schemaValidate($xsdFilename);
  205         $tmp = \libxml_get_errors();
  206         \libxml_clear_errors();
  207         \libxml_use_internal_errors($original);
  208 
  209         $errors = [];
  210 
  211         foreach ($tmp as $error) {
  212             if (!isset($errors[$error->line])) {
  213                 $errors[$error->line] = [];
  214             }
  215 
  216             $errors[$error->line][] = \trim($error->message);
  217         }
  218 
  219         return $errors;
  220     }
  221 
  222     private function extensions(string $filename, \DOMXPath $xpath): ExtensionCollection
  223     {
  224         $extensions = [];
  225 
  226         foreach ($xpath->query('extensions/extension') as $extension) {
  227             \assert($extension instanceof \DOMElement);
  228 
  229             $extensions[] = $this->getElementConfigurationParameters($filename, $extension);
  230         }
  231 
  232         return ExtensionCollection::fromArray($extensions);
  233     }
  234 
  235     private function getElementConfigurationParameters(string $filename, \DOMElement $element): Extension
  236     {
  237         /** @psalm-var class-string $class */
  238         $class     = (string) $element->getAttribute('class');
  239         $file      = '';
  240         $arguments = $this->getConfigurationArguments($filename, $element->childNodes);
  241 
  242         if ($element->getAttribute('file')) {
  243             $file = $this->toAbsolutePath(
  244                 $filename,
  245                 (string) $element->getAttribute('file'),
  246                 true
  247             );
  248         }
  249 
  250         return new Extension($class, $file, $arguments);
  251     }
  252 
  253     private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string
  254     {
  255         $path = \trim($path);
  256 
  257         if (\strpos($path, '/') === 0) {
  258             return $path;
  259         }
  260 
  261         // Matches the following on Windows:
  262         //  - \\NetworkComputer\Path
  263         //  - \\.\D:
  264         //  - \\.\c:
  265         //  - C:\Windows
  266         //  - C:\windows
  267         //  - C:/windows
  268         //  - c:/windows
  269         if (\defined('PHP_WINDOWS_VERSION_BUILD') &&
  270             ($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) {
  271             return $path;
  272         }
  273 
  274         if (\strpos($path, '://') !== false) {
  275             return $path;
  276         }
  277 
  278         $file = \dirname($filename) . \DIRECTORY_SEPARATOR . $path;
  279 
  280         if ($useIncludePath && !\file_exists($file)) {
  281             $includePathFile = \stream_resolve_include_path($path);
  282 
  283             if ($includePathFile) {
  284                 $file = $includePathFile;
  285             }
  286         }
  287 
  288         return $file;
  289     }
  290 
  291     private function getConfigurationArguments(string $filename, \DOMNodeList $nodes): array
  292     {
  293         $arguments = [];
  294 
  295         if ($nodes->length === 0) {
  296             return $arguments;
  297         }
  298 
  299         foreach ($nodes as $node) {
  300             if (!$node instanceof \DOMElement) {
  301                 continue;
  302             }
  303 
  304             if ($node->tagName !== 'arguments') {
  305                 continue;
  306             }
  307 
  308             foreach ($node->childNodes as $argument) {
  309                 if (!$argument instanceof \DOMElement) {
  310                     continue;
  311                 }
  312 
  313                 if ($argument->tagName === 'file' || $argument->tagName === 'directory') {
  314                     $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent);
  315                 } else {
  316                     $arguments[] = Xml::xmlToVariable($argument);
  317                 }
  318             }
  319         }
  320 
  321         return $arguments;
  322     }
  323 
  324     private function filter(string $filename, \DOMXPath $xpath): Filter
  325     {
  326         $addUncoveredFilesFromWhitelist     = true;
  327         $processUncoveredFilesFromWhitelist = false;
  328 
  329         $nodes = $xpath->query('filter/whitelist');
  330 
  331         if ($nodes->length === 1) {
  332             $node = $nodes->item(0);
  333 
  334             \assert($node instanceof \DOMElement);
  335 
  336             if ($node->hasAttribute('addUncoveredFilesFromWhitelist')) {
  337                 $addUncoveredFilesFromWhitelist = (bool) $this->getBoolean(
  338                     (string) $node->getAttribute('addUncoveredFilesFromWhitelist'),
  339                     true
  340                 );
  341             }
  342 
  343             if ($node->hasAttribute('processUncoveredFilesFromWhitelist')) {
  344                 $processUncoveredFilesFromWhitelist = (bool) $this->getBoolean(
  345                     (string) $node->getAttribute('processUncoveredFilesFromWhitelist'),
  346                     false
  347                 );
  348             }
  349         }
  350 
  351         return new Filter(
  352             $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'),
  353             $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'),
  354             $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'),
  355             $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'),
  356             $addUncoveredFilesFromWhitelist,
  357             $processUncoveredFilesFromWhitelist
  358         );
  359     }
  360 
  361     /**
  362      * if $value is 'false' or 'true', this returns the value that $value represents.
  363      * Otherwise, returns $default, which may be a string in rare cases.
  364      * See PHPUnit\TextUI\ConfigurationTest::testPHPConfigurationIsReadCorrectly
  365      *
  366      * @param bool|string $default
  367      *
  368      * @return bool|string
  369      */
  370     private function getBoolean(string $value, $default)
  371     {
  372         if (\strtolower($value) === 'false') {
  373             return false;
  374         }
  375 
  376         if (\strtolower($value) === 'true') {
  377             return true;
  378         }
  379 
  380         return $default;
  381     }
  382 
  383     private function readFilterDirectories(string $filename, \DOMXPath $xpath, string $query): FilterDirectoryCollection
  384     {
  385         $directories = [];
  386 
  387         foreach ($xpath->query($query) as $directoryNode) {
  388             \assert($directoryNode instanceof \DOMElement);
  389 
  390             $directoryPath = (string) $directoryNode->textContent;
  391 
  392             if (!$directoryPath) {
  393                 continue;
  394             }
  395 
  396             $directories[] = new FilterDirectory(
  397                 $this->toAbsolutePath($filename, $directoryPath),
  398                 $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '',
  399                 $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php',
  400                 $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT'
  401             );
  402         }
  403 
  404         return FilterDirectoryCollection::fromArray($directories);
  405     }
  406 
  407     private function readFilterFiles(string $filename, \DOMXPath $xpath, string $query): FilterFileCollection
  408     {
  409         $files = [];
  410 
  411         foreach ($xpath->query($query) as $file) {
  412             $filePath = (string) $file->textContent;
  413 
  414             if ($filePath) {
  415                 $files[] = new FilterFile($this->toAbsolutePath($filename, $filePath));
  416             }
  417         }
  418 
  419         return FilterFileCollection::fromArray($files);
  420     }
  421 
  422     private function groups(\DOMXPath $xpath): Groups
  423     {
  424         return $this->parseGroupConfiguration($xpath, 'groups');
  425     }
  426 
  427     private function testdoxGroups(\DOMXPath $xpath): Groups
  428     {
  429         return $this->parseGroupConfiguration($xpath, 'testdoxGroups');
  430     }
  431 
  432     private function parseGroupConfiguration(\DOMXPath $xpath, string $root): Groups
  433     {
  434         $include = [];
  435         $exclude = [];
  436 
  437         foreach ($xpath->query($root . '/include/group') as $group) {
  438             $include[] = new Group((string) $group->textContent);
  439         }
  440 
  441         foreach ($xpath->query($root . '/exclude/group') as $group) {
  442             $exclude[] = new Group((string) $group->textContent);
  443         }
  444 
  445         return new Groups(
  446             GroupCollection::fromArray($include),
  447             GroupCollection::fromArray($exclude)
  448         );
  449     }
  450 
  451     private function listeners(string $filename, \DOMXPath $xpath): ExtensionCollection
  452     {
  453         $listeners = [];
  454 
  455         foreach ($xpath->query('listeners/listener') as $listener) {
  456             \assert($listener instanceof \DOMElement);
  457 
  458             $listeners[] = $this->getElementConfigurationParameters($filename, $listener);
  459         }
  460 
  461         return ExtensionCollection::fromArray($listeners);
  462     }
  463 
  464     private function getBooleanAttribute(\DOMElement $element, string $attribute, bool $default): bool
  465     {
  466         if (!$element->hasAttribute($attribute)) {
  467             return $default;
  468         }
  469 
  470         return (bool) $this->getBoolean(
  471             (string) $element->getAttribute($attribute),
  472             false
  473         );
  474     }
  475 
  476     private function getIntegerAttribute(\DOMElement $element, string $attribute, int $default): int
  477     {
  478         if (!$element->hasAttribute($attribute)) {
  479             return $default;
  480         }
  481 
  482         return $this->getInteger(
  483             (string) $element->getAttribute($attribute),
  484             $default
  485         );
  486     }
  487 
  488     private function getStringAttribute(\DOMElement $element, string $attribute): ?string
  489     {
  490         if (!$element->hasAttribute($attribute)) {
  491             return null;
  492         }
  493 
  494         return (string) $element->getAttribute($attribute);
  495     }
  496 
  497     private function getInteger(string $value, int $default): int
  498     {
  499         if (\is_numeric($value)) {
  500             return (int) $value;
  501         }
  502 
  503         return $default;
  504     }
  505 
  506     private function php(string $filename, \DOMXPath $xpath): Php
  507     {
  508         $includePaths = [];
  509 
  510         foreach ($xpath->query('php/includePath') as $includePath) {
  511             $path = (string) $includePath->textContent;
  512 
  513             if ($path) {
  514                 $includePaths[] = new Directory($this->toAbsolutePath($filename, $path));
  515             }
  516         }
  517 
  518         $iniSettings = [];
  519 
  520         foreach ($xpath->query('php/ini') as $ini) {
  521             \assert($ini instanceof \DOMElement);
  522 
  523             $iniSettings[] = new IniSetting(
  524                 (string) $ini->getAttribute('name'),
  525                 (string) $ini->getAttribute('value')
  526             );
  527         }
  528 
  529         $constants = [];
  530 
  531         foreach ($xpath->query('php/const') as $const) {
  532             \assert($const instanceof \DOMElement);
  533 
  534             $value = (string) $const->getAttribute('value');
  535 
  536             $constants[] = new Constant(
  537                 (string) $const->getAttribute('name'),
  538                 $this->getBoolean($value, $value)
  539             );
  540         }
  541 
  542         $variables = [
  543             'var'     => [],
  544             'env'     => [],
  545             'post'    => [],
  546             'get'     => [],
  547             'cookie'  => [],
  548             'server'  => [],
  549             'files'   => [],
  550             'request' => [],
  551         ];
  552 
  553         foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
  554             foreach ($xpath->query('php/' . $array) as $var) {
  555                 \assert($var instanceof \DOMElement);
  556 
  557                 $name     = (string) $var->getAttribute('name');
  558                 $value    = (string) $var->getAttribute('value');
  559                 $force    = false;
  560                 $verbatim = false;
  561 
  562                 if ($var->hasAttribute('force')) {
  563                     $force = (bool) $this->getBoolean($var->getAttribute('force'), false);
  564                 }
  565 
  566                 if ($var->hasAttribute('verbatim')) {
  567                     $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false);
  568                 }
  569 
  570                 if (!$verbatim) {
  571                     $value = $this->getBoolean($value, $value);
  572                 }
  573 
  574                 $variables[$array][] = new Variable($name, $value, $force);
  575             }
  576         }
  577 
  578         return new Php(
  579             DirectoryCollection::fromArray($includePaths),
  580             IniSettingCollection::fromArray($iniSettings),
  581             ConstantCollection::fromArray($constants),
  582             VariableCollection::fromArray($variables['var']),
  583             VariableCollection::fromArray($variables['env']),
  584             VariableCollection::fromArray($variables['post']),
  585             VariableCollection::fromArray($variables['get']),
  586             VariableCollection::fromArray($variables['cookie']),
  587             VariableCollection::fromArray($variables['server']),
  588             VariableCollection::fromArray($variables['files']),
  589             VariableCollection::fromArray($variables['request']),
  590         );
  591     }
  592 
  593     private function phpunit(string $filename, \DOMDocument $document): PHPUnit
  594     {
  595         $executionOrder      = TestSuiteSorter::ORDER_DEFAULT;
  596         $defectsFirst        = false;
  597         $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true);
  598 
  599         if ($document->documentElement->hasAttribute('executionOrder')) {
  600             foreach (\explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) {
  601                 switch ($order) {
  602                     case 'default':
  603                         $executionOrder      = TestSuiteSorter::ORDER_DEFAULT;
  604                         $defectsFirst        = false;
  605                         $resolveDependencies = true;
  606 
  607                         break;
  608 
  609                     case 'depends':
  610                         $resolveDependencies = true;
  611 
  612                         break;
  613 
  614                     case 'no-depends':
  615                         $resolveDependencies = false;
  616 
  617                         break;
  618 
  619                     case 'defects':
  620                         $defectsFirst = true;
  621 
  622                         break;
  623 
  624                     case 'duration':
  625                         $executionOrder = TestSuiteSorter::ORDER_DURATION;
  626 
  627                         break;
  628 
  629                     case 'random':
  630                         $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED;
  631 
  632                         break;
  633 
  634                     case 'reverse':
  635                         $executionOrder = TestSuiteSorter::ORDER_REVERSED;
  636 
  637                         break;
  638 
  639                     case 'size':
  640                         $executionOrder = TestSuiteSorter::ORDER_SIZE;
  641 
  642                         break;
  643                 }
  644             }
  645         }
  646 
  647         $printerClass                          = $this->getStringAttribute($document->documentElement, 'printerClass');
  648         $testdox                               = $this->getBooleanAttribute($document->documentElement, 'testdox', false);
  649         $conflictBetweenPrinterClassAndTestdox = false;
  650 
  651         if ($testdox) {
  652             if ($printerClass !== null) {
  653                 $conflictBetweenPrinterClassAndTestdox = true;
  654             }
  655 
  656             $printerClass = CliTestDoxPrinter::class;
  657         }
  658 
  659         $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile');
  660 
  661         if ($cacheResultFile !== null) {
  662             $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile);
  663         }
  664 
  665         $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap');
  666 
  667         if ($bootstrap !== null) {
  668             $bootstrap = $this->toAbsolutePath($filename, $bootstrap);
  669         }
  670 
  671         $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory');
  672 
  673         if ($extensionsDirectory !== null) {
  674             $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory);
  675         }
  676 
  677         $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile');
  678 
  679         if ($testSuiteLoaderFile !== null) {
  680             $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile);
  681         }
  682 
  683         $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile');
  684 
  685         if ($printerFile !== null) {
  686             $printerFile = $this->toAbsolutePath($filename, $printerFile);
  687         }
  688 
  689         return new PHPUnit(
  690             $this->getBooleanAttribute($document->documentElement, 'cacheResult', false),
  691             $cacheResultFile,
  692             $this->getBooleanAttribute($document->documentElement, 'cacheTokens', false),
  693             $this->getColumns($document),
  694             $this->getColors($document),
  695             $this->getBooleanAttribute($document->documentElement, 'stderr', false),
  696             $this->getBooleanAttribute($document->documentElement, 'noInteraction', false),
  697             $this->getBooleanAttribute($document->documentElement, 'verbose', false),
  698             $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false),
  699             $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', true),
  700             $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true),
  701             $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true),
  702             $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true),
  703             $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false),
  704             $this->getBooleanAttribute($document->documentElement, 'ignoreDeprecatedCodeUnitsFromCodeCoverage', false),
  705             $this->getBooleanAttribute($document->documentElement, 'disableCodeCoverageIgnore', false),
  706             $bootstrap,
  707             $this->getBooleanAttribute($document->documentElement, 'processIsolation', false),
  708             $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false),
  709             $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false),
  710             $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false),
  711             $this->getBooleanAttribute($document->documentElement, 'stopOnError', false),
  712             $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false),
  713             $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false),
  714             $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false),
  715             $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false),
  716             $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false),
  717             $extensionsDirectory,
  718             $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'),
  719             $testSuiteLoaderFile,
  720             $printerClass,
  721             $printerFile,
  722             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false),
  723             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false),
  724             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false),
  725             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true),
  726             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false),
  727             $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false),
  728             $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false),
  729             $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1),
  730             $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1),
  731             $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10),
  732             $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60),
  733             $this->getStringAttribute($document->documentElement, 'defaultTestSuite'),
  734             $executionOrder,
  735             $resolveDependencies,
  736             $defectsFirst,
  737             $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false),
  738             $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false),
  739             $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false),
  740             $conflictBetweenPrinterClassAndTestdox
  741         );
  742     }
  743 
  744     private function getColors(\DOMDocument $document): string
  745     {
  746         $colors = DefaultResultPrinter::COLOR_DEFAULT;
  747 
  748         if ($document->documentElement->hasAttribute('colors')) {
  749             /* only allow boolean for compatibility with previous versions
  750               'always' only allowed from command line */
  751             if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) {
  752                 $colors = DefaultResultPrinter::COLOR_AUTO;
  753             } else {
  754                 $colors = DefaultResultPrinter::COLOR_NEVER;
  755             }
  756         }
  757 
  758         return $colors;
  759     }
  760 
  761     /**
  762      * @return int|string
  763      */
  764     private function getColumns(\DOMDocument $document)
  765     {
  766         $columns = 80;
  767 
  768         if ($document->documentElement->hasAttribute('columns')) {
  769             $columns = (string) $document->documentElement->getAttribute('columns');
  770 
  771             if ($columns !== 'max') {
  772                 $columns = $this->getInteger($columns, 80);
  773             }
  774         }
  775 
  776         return $columns;
  777     }
  778 
  779     private function testSuite(string $filename, \DOMXPath $xpath): TestSuiteCollection
  780     {
  781         $testSuites = [];
  782 
  783         foreach ($this->getTestSuiteElements($xpath) as $element) {
  784             $exclude = [];
  785 
  786             foreach ($element->getElementsByTagName('exclude') as $excludeNode) {
  787                 $excludeFile = (string) $excludeNode->textContent;
  788 
  789                 if ($excludeFile) {
  790                     $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile));
  791                 }
  792             }
  793 
  794             $directories = [];
  795 
  796             foreach ($element->getElementsByTagName('directory') as $directoryNode) {
  797                 \assert($directoryNode instanceof \DOMElement);
  798 
  799                 $directory = (string) $directoryNode->textContent;
  800 
  801                 if (empty($directory)) {
  802                     continue;
  803                 }
  804 
  805                 $prefix = '';
  806 
  807                 if ($directoryNode->hasAttribute('prefix')) {
  808                     $prefix = (string) $directoryNode->getAttribute('prefix');
  809                 }
  810 
  811                 $suffix = 'Test.php';
  812 
  813                 if ($directoryNode->hasAttribute('suffix')) {
  814                     $suffix = (string) $directoryNode->getAttribute('suffix');
  815                 }
  816 
  817                 $phpVersion = \PHP_VERSION;
  818 
  819                 if ($directoryNode->hasAttribute('phpVersion')) {
  820                     $phpVersion = (string) $directoryNode->getAttribute('phpVersion');
  821                 }
  822 
  823                 $phpVersionOperator = new VersionComparisonOperator('>=');
  824 
  825                 if ($directoryNode->hasAttribute('phpVersionOperator')) {
  826                     $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator'));
  827                 }
  828 
  829                 $directories[] = new TestDirectory(
  830                     $this->toAbsolutePath($filename, $directory),
  831                     $prefix,
  832                     $suffix,
  833                     $phpVersion,
  834                     $phpVersionOperator
  835                 );
  836             }
  837 
  838             $files = [];
  839 
  840             foreach ($element->getElementsByTagName('file') as $fileNode) {
  841                 \assert($fileNode instanceof \DOMElement);
  842 
  843                 $file = (string) $fileNode->textContent;
  844 
  845                 if (empty($file)) {
  846                     continue;
  847                 }
  848 
  849                 $phpVersion = \PHP_VERSION;
  850 
  851                 if ($fileNode->hasAttribute('phpVersion')) {
  852                     $phpVersion = (string) $fileNode->getAttribute('phpVersion');
  853                 }
  854 
  855                 $phpVersionOperator = new VersionComparisonOperator('>=');
  856 
  857                 if ($fileNode->hasAttribute('phpVersionOperator')) {
  858                     $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator'));
  859                 }
  860 
  861                 $files[] = new TestFile(
  862                     $this->toAbsolutePath($filename, $file),
  863                     $phpVersion,
  864                     $phpVersionOperator
  865                 );
  866             }
  867 
  868             $testSuites[] = new TestSuiteConfiguration(
  869                 (string) $element->getAttribute('name'),
  870                 TestDirectoryCollection::fromArray($directories),
  871                 TestFileCollection::fromArray($files),
  872                 FileCollection::fromArray($exclude)
  873             );
  874         }
  875 
  876         return TestSuiteCollection::fromArray($testSuites);
  877     }
  878 
  879     /**
  880      * @return \DOMElement[]
  881      */
  882     private function getTestSuiteElements(\DOMXPath $xpath): array
  883     {
  884         /** @var \DOMElement[] $elements */
  885         $elements = [];
  886 
  887         $testSuiteNodes = $xpath->query('testsuites/testsuite');
  888 
  889         if ($testSuiteNodes->length === 0) {
  890             $testSuiteNodes = $xpath->query('testsuite');
  891         }
  892 
  893         if ($testSuiteNodes->length === 1) {
  894             $element = $testSuiteNodes->item(0);
  895 
  896             \assert($element instanceof \DOMElement);
  897 
  898             $elements[] = $element;
  899         } else {
  900             foreach ($testSuiteNodes as $testSuiteNode) {
  901                 \assert($testSuiteNode instanceof \DOMElement);
  902 
  903                 $elements[] = $testSuiteNode;
  904             }
  905         }
  906 
  907         return $elements;
  908     }
  909 }