Passed
Push — develop ( 30cf64...589229 )
by Guillaume
06:18 queued 04:10
created

Loader   F

Complexity

Total Complexity 126

Size/Duplication

Total Lines 875
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 475
dl 0
loc 875
rs 2
c 0
b 0
f 0
wmc 126

How to fix   Complexity   

Complex Class

Complex classes like Loader often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Loader, and based on these observations, apply Extract Interface, too.

1
<?php declare(strict_types=1);
2
/*
3
 * This file is part of PHPUnit.
4
 *
5
 * (c) Sebastian Bergmann <[email protected]>
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, 'failOnIncomplete', false),
709
            $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false),
710
            $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false),
711
            $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false),
712
            $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false),
713
            $this->getBooleanAttribute($document->documentElement, 'stopOnError', false),
714
            $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false),
715
            $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false),
716
            $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false),
717
            $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false),
718
            $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false),
719
            $extensionsDirectory,
720
            $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'),
721
            $testSuiteLoaderFile,
722
            $printerClass,
723
            $printerFile,
724
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false),
725
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false),
726
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false),
727
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true),
728
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false),
729
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false),
730
            $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false),
731
            $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1),
732
            $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1),
733
            $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10),
734
            $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60),
735
            $this->getStringAttribute($document->documentElement, 'defaultTestSuite'),
736
            $executionOrder,
737
            $resolveDependencies,
738
            $defectsFirst,
739
            $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false),
740
            $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false),
741
            $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false),
742
            $conflictBetweenPrinterClassAndTestdox
743
        );
744
    }
745
746
    private function getColors(\DOMDocument $document): string
747
    {
748
        $colors = DefaultResultPrinter::COLOR_DEFAULT;
749
750
        if ($document->documentElement->hasAttribute('colors')) {
751
            /* only allow boolean for compatibility with previous versions
752
              'always' only allowed from command line */
753
            if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) {
754
                $colors = DefaultResultPrinter::COLOR_AUTO;
755
            } else {
756
                $colors = DefaultResultPrinter::COLOR_NEVER;
757
            }
758
        }
759
760
        return $colors;
761
    }
762
763
    /**
764
     * @return int|string
765
     */
766
    private function getColumns(\DOMDocument $document)
767
    {
768
        $columns = 80;
769
770
        if ($document->documentElement->hasAttribute('columns')) {
771
            $columns = (string) $document->documentElement->getAttribute('columns');
772
773
            if ($columns !== 'max') {
774
                $columns = $this->getInteger($columns, 80);
775
            }
776
        }
777
778
        return $columns;
779
    }
780
781
    private function testSuite(string $filename, \DOMXPath $xpath): TestSuiteCollection
782
    {
783
        $testSuites = [];
784
785
        foreach ($this->getTestSuiteElements($xpath) as $element) {
786
            $exclude = [];
787
788
            foreach ($element->getElementsByTagName('exclude') as $excludeNode) {
789
                $excludeFile = (string) $excludeNode->textContent;
790
791
                if ($excludeFile) {
792
                    $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile));
793
                }
794
            }
795
796
            $directories = [];
797
798
            foreach ($element->getElementsByTagName('directory') as $directoryNode) {
799
                \assert($directoryNode instanceof \DOMElement);
800
801
                $directory = (string) $directoryNode->textContent;
802
803
                if (empty($directory)) {
804
                    continue;
805
                }
806
807
                $prefix = '';
808
809
                if ($directoryNode->hasAttribute('prefix')) {
810
                    $prefix = (string) $directoryNode->getAttribute('prefix');
811
                }
812
813
                $suffix = 'Test.php';
814
815
                if ($directoryNode->hasAttribute('suffix')) {
816
                    $suffix = (string) $directoryNode->getAttribute('suffix');
817
                }
818
819
                $phpVersion = \PHP_VERSION;
820
821
                if ($directoryNode->hasAttribute('phpVersion')) {
822
                    $phpVersion = (string) $directoryNode->getAttribute('phpVersion');
823
                }
824
825
                $phpVersionOperator = new VersionComparisonOperator('>=');
826
827
                if ($directoryNode->hasAttribute('phpVersionOperator')) {
828
                    $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator'));
829
                }
830
831
                $directories[] = new TestDirectory(
832
                    $this->toAbsolutePath($filename, $directory),
833
                    $prefix,
834
                    $suffix,
835
                    $phpVersion,
836
                    $phpVersionOperator
837
                );
838
            }
839
840
            $files = [];
841
842
            foreach ($element->getElementsByTagName('file') as $fileNode) {
843
                \assert($fileNode instanceof \DOMElement);
844
845
                $file = (string) $fileNode->textContent;
846
847
                if (empty($file)) {
848
                    continue;
849
                }
850
851
                $phpVersion = \PHP_VERSION;
852
853
                if ($fileNode->hasAttribute('phpVersion')) {
854
                    $phpVersion = (string) $fileNode->getAttribute('phpVersion');
855
                }
856
857
                $phpVersionOperator = new VersionComparisonOperator('>=');
858
859
                if ($fileNode->hasAttribute('phpVersionOperator')) {
860
                    $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator'));
861
                }
862
863
                $files[] = new TestFile(
864
                    $this->toAbsolutePath($filename, $file),
865
                    $phpVersion,
866
                    $phpVersionOperator
867
                );
868
            }
869
870
            $testSuites[] = new TestSuiteConfiguration(
871
                (string) $element->getAttribute('name'),
872
                TestDirectoryCollection::fromArray($directories),
873
                TestFileCollection::fromArray($files),
874
                FileCollection::fromArray($exclude)
875
            );
876
        }
877
878
        return TestSuiteCollection::fromArray($testSuites);
879
    }
880
881
    /**
882
     * @return \DOMElement[]
883
     */
884
    private function getTestSuiteElements(\DOMXPath $xpath): array
885
    {
886
        /** @var \DOMElement[] $elements */
887
        $elements = [];
888
889
        $testSuiteNodes = $xpath->query('testsuites/testsuite');
890
891
        if ($testSuiteNodes->length === 0) {
892
            $testSuiteNodes = $xpath->query('testsuite');
893
        }
894
895
        if ($testSuiteNodes->length === 1) {
896
            $element = $testSuiteNodes->item(0);
897
898
            \assert($element instanceof \DOMElement);
899
900
            $elements[] = $element;
901
        } else {
902
            foreach ($testSuiteNodes as $testSuiteNode) {
903
                \assert($testSuiteNode instanceof \DOMElement);
904
905
                $elements[] = $testSuiteNode;
906
            }
907
        }
908
909
        return $elements;
910
    }
911
}
912