Subversion-Projekte lars-tiefland.laravel_shop

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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\XmlConfiguration;
11
 
12
use const DIRECTORY_SEPARATOR;
13
use const PHP_VERSION;
14
use function assert;
15
use function defined;
16
use function dirname;
17
use function explode;
18
use function is_file;
19
use function is_numeric;
20
use function preg_match;
21
use function stream_resolve_include_path;
22
use function strlen;
23
use function strpos;
24
use function strtolower;
25
use function substr;
26
use function trim;
27
use DOMDocument;
28
use DOMElement;
29
use DOMNodeList;
30
use DOMXPath;
31
use PHPUnit\Runner\TestSuiteSorter;
32
use PHPUnit\Runner\Version;
33
use PHPUnit\TextUI\DefaultResultPrinter;
34
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\CodeCoverage;
35
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\Directory as FilterDirectory;
36
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Filter\DirectoryCollection as FilterDirectoryCollection;
37
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Clover;
38
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Cobertura;
39
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Crap4j;
40
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Html as CodeCoverageHtml;
41
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Php as CodeCoveragePhp;
42
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Text as CodeCoverageText;
43
use PHPUnit\TextUI\XmlConfiguration\CodeCoverage\Report\Xml as CodeCoverageXml;
44
use PHPUnit\TextUI\XmlConfiguration\Logging\Junit;
45
use PHPUnit\TextUI\XmlConfiguration\Logging\Logging;
46
use PHPUnit\TextUI\XmlConfiguration\Logging\TeamCity;
47
use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Html as TestDoxHtml;
48
use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Text as TestDoxText;
49
use PHPUnit\TextUI\XmlConfiguration\Logging\TestDox\Xml as TestDoxXml;
50
use PHPUnit\TextUI\XmlConfiguration\Logging\Text;
51
use PHPUnit\TextUI\XmlConfiguration\TestSuite as TestSuiteConfiguration;
52
use PHPUnit\Util\TestDox\CliTestDoxPrinter;
53
use PHPUnit\Util\VersionComparisonOperator;
54
use PHPUnit\Util\Xml;
55
use PHPUnit\Util\Xml\Exception as XmlException;
56
use PHPUnit\Util\Xml\Loader as XmlLoader;
57
use PHPUnit\Util\Xml\SchemaFinder;
58
use PHPUnit\Util\Xml\Validator;
59
 
60
/**
61
 * @internal This class is not covered by the backward compatibility promise for PHPUnit
62
 */
63
final class Loader
64
{
65
    /**
66
     * @throws Exception
67
     */
68
    public function load(string $filename): Configuration
69
    {
70
        try {
71
            $document = (new XmlLoader)->loadFile($filename, false, true, true);
72
        } catch (XmlException $e) {
73
            throw new Exception(
74
                $e->getMessage(),
75
                $e->getCode(),
76
                $e
77
            );
78
        }
79
 
80
        $xpath = new DOMXPath($document);
81
 
82
        try {
83
            $xsdFilename = (new SchemaFinder)->find(Version::series());
84
        } catch (XmlException $e) {
85
            throw new Exception(
86
                $e->getMessage(),
87
                $e->getCode(),
88
                $e
89
            );
90
        }
91
 
92
        return new Configuration(
93
            $filename,
94
            (new Validator)->validate($document, $xsdFilename),
95
            $this->extensions($filename, $xpath),
96
            $this->codeCoverage($filename, $xpath, $document),
97
            $this->groups($xpath),
98
            $this->testdoxGroups($xpath),
99
            $this->listeners($filename, $xpath),
100
            $this->logging($filename, $xpath),
101
            $this->php($filename, $xpath),
102
            $this->phpunit($filename, $document),
103
            $this->testSuite($filename, $xpath)
104
        );
105
    }
106
 
107
    public function logging(string $filename, DOMXPath $xpath): Logging
108
    {
109
        if ($xpath->query('logging/log')->length !== 0) {
110
            return $this->legacyLogging($filename, $xpath);
111
        }
112
 
113
        $junit   = null;
114
        $element = $this->element($xpath, 'logging/junit');
115
 
116
        if ($element) {
117
            $junit = new Junit(
118
                new File(
119
                    $this->toAbsolutePath(
120
                        $filename,
121
                        (string) $this->getStringAttribute($element, 'outputFile')
122
                    )
123
                )
124
            );
125
        }
126
 
127
        $text    = null;
128
        $element = $this->element($xpath, 'logging/text');
129
 
130
        if ($element) {
131
            $text = new Text(
132
                new File(
133
                    $this->toAbsolutePath(
134
                        $filename,
135
                        (string) $this->getStringAttribute($element, 'outputFile')
136
                    )
137
                )
138
            );
139
        }
140
 
141
        $teamCity = null;
142
        $element  = $this->element($xpath, 'logging/teamcity');
143
 
144
        if ($element) {
145
            $teamCity = new TeamCity(
146
                new File(
147
                    $this->toAbsolutePath(
148
                        $filename,
149
                        (string) $this->getStringAttribute($element, 'outputFile')
150
                    )
151
                )
152
            );
153
        }
154
 
155
        $testDoxHtml = null;
156
        $element     = $this->element($xpath, 'logging/testdoxHtml');
157
 
158
        if ($element) {
159
            $testDoxHtml = new TestDoxHtml(
160
                new File(
161
                    $this->toAbsolutePath(
162
                        $filename,
163
                        (string) $this->getStringAttribute($element, 'outputFile')
164
                    )
165
                )
166
            );
167
        }
168
 
169
        $testDoxText = null;
170
        $element     = $this->element($xpath, 'logging/testdoxText');
171
 
172
        if ($element) {
173
            $testDoxText = new TestDoxText(
174
                new File(
175
                    $this->toAbsolutePath(
176
                        $filename,
177
                        (string) $this->getStringAttribute($element, 'outputFile')
178
                    )
179
                )
180
            );
181
        }
182
 
183
        $testDoxXml = null;
184
        $element    = $this->element($xpath, 'logging/testdoxXml');
185
 
186
        if ($element) {
187
            $testDoxXml = new TestDoxXml(
188
                new File(
189
                    $this->toAbsolutePath(
190
                        $filename,
191
                        (string) $this->getStringAttribute($element, 'outputFile')
192
                    )
193
                )
194
            );
195
        }
196
 
197
        return new Logging(
198
            $junit,
199
            $text,
200
            $teamCity,
201
            $testDoxHtml,
202
            $testDoxText,
203
            $testDoxXml
204
        );
205
    }
206
 
207
    public function legacyLogging(string $filename, DOMXPath $xpath): Logging
208
    {
209
        $junit       = null;
210
        $teamCity    = null;
211
        $testDoxHtml = null;
212
        $testDoxText = null;
213
        $testDoxXml  = null;
214
        $text        = null;
215
 
216
        foreach ($xpath->query('logging/log') as $log) {
217
            assert($log instanceof DOMElement);
218
 
219
            $type   = (string) $log->getAttribute('type');
220
            $target = (string) $log->getAttribute('target');
221
 
222
            if (!$target) {
223
                continue;
224
            }
225
 
226
            $target = $this->toAbsolutePath($filename, $target);
227
 
228
            switch ($type) {
229
                case 'plain':
230
                    $text = new Text(
231
                        new File($target)
232
                    );
233
 
234
                    break;
235
 
236
                case 'junit':
237
                    $junit = new Junit(
238
                        new File($target)
239
                    );
240
 
241
                    break;
242
 
243
                case 'teamcity':
244
                    $teamCity = new TeamCity(
245
                        new File($target)
246
                    );
247
 
248
                    break;
249
 
250
                case 'testdox-html':
251
                    $testDoxHtml = new TestDoxHtml(
252
                        new File($target)
253
                    );
254
 
255
                    break;
256
 
257
                case 'testdox-text':
258
                    $testDoxText = new TestDoxText(
259
                        new File($target)
260
                    );
261
 
262
                    break;
263
 
264
                case 'testdox-xml':
265
                    $testDoxXml = new TestDoxXml(
266
                        new File($target)
267
                    );
268
 
269
                    break;
270
            }
271
        }
272
 
273
        return new Logging(
274
            $junit,
275
            $text,
276
            $teamCity,
277
            $testDoxHtml,
278
            $testDoxText,
279
            $testDoxXml
280
        );
281
    }
282
 
283
    private function extensions(string $filename, DOMXPath $xpath): ExtensionCollection
284
    {
285
        $extensions = [];
286
 
287
        foreach ($xpath->query('extensions/extension') as $extension) {
288
            assert($extension instanceof DOMElement);
289
 
290
            $extensions[] = $this->getElementConfigurationParameters($filename, $extension);
291
        }
292
 
293
        return ExtensionCollection::fromArray($extensions);
294
    }
295
 
296
    private function getElementConfigurationParameters(string $filename, DOMElement $element): Extension
297
    {
298
        /** @psalm-var class-string $class */
299
        $class     = (string) $element->getAttribute('class');
300
        $file      = '';
301
        $arguments = $this->getConfigurationArguments($filename, $element->childNodes);
302
 
303
        if ($element->getAttribute('file')) {
304
            $file = $this->toAbsolutePath(
305
                $filename,
306
                (string) $element->getAttribute('file'),
307
                true
308
            );
309
        }
310
 
311
        return new Extension($class, $file, $arguments);
312
    }
313
 
314
    private function toAbsolutePath(string $filename, string $path, bool $useIncludePath = false): string
315
    {
316
        $path = trim($path);
317
 
318
        if (strpos($path, '/') === 0) {
319
            return $path;
320
        }
321
 
322
        // Matches the following on Windows:
323
        //  - \\NetworkComputer\Path
324
        //  - \\.\D:
325
        //  - \\.\c:
326
        //  - C:\Windows
327
        //  - C:\windows
328
        //  - C:/windows
329
        //  - c:/windows
330
        if (defined('PHP_WINDOWS_VERSION_BUILD') &&
331
            ($path[0] === '\\' || (strlen($path) >= 3 && preg_match('#^[A-Z]\:[/\\\]#i', substr($path, 0, 3))))) {
332
            return $path;
333
        }
334
 
335
        if (strpos($path, '://') !== false) {
336
            return $path;
337
        }
338
 
339
        $file = dirname($filename) . DIRECTORY_SEPARATOR . $path;
340
 
341
        if ($useIncludePath && !is_file($file)) {
342
            $includePathFile = stream_resolve_include_path($path);
343
 
344
            if ($includePathFile) {
345
                $file = $includePathFile;
346
            }
347
        }
348
 
349
        return $file;
350
    }
351
 
352
    private function getConfigurationArguments(string $filename, DOMNodeList $nodes): array
353
    {
354
        $arguments = [];
355
 
356
        if ($nodes->length === 0) {
357
            return $arguments;
358
        }
359
 
360
        foreach ($nodes as $node) {
361
            if (!$node instanceof DOMElement) {
362
                continue;
363
            }
364
 
365
            if ($node->tagName !== 'arguments') {
366
                continue;
367
            }
368
 
369
            foreach ($node->childNodes as $argument) {
370
                if (!$argument instanceof DOMElement) {
371
                    continue;
372
                }
373
 
374
                if ($argument->tagName === 'file' || $argument->tagName === 'directory') {
375
                    $arguments[] = $this->toAbsolutePath($filename, (string) $argument->textContent);
376
                } else {
377
                    $arguments[] = Xml::xmlToVariable($argument);
378
                }
379
            }
380
        }
381
 
382
        return $arguments;
383
    }
384
 
385
    private function codeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage
386
    {
387
        if ($xpath->query('filter/whitelist')->length !== 0) {
388
            return $this->legacyCodeCoverage($filename, $xpath, $document);
389
        }
390
 
391
        $cacheDirectory            = null;
392
        $pathCoverage              = false;
393
        $includeUncoveredFiles     = true;
394
        $processUncoveredFiles     = false;
395
        $ignoreDeprecatedCodeUnits = false;
396
        $disableCodeCoverageIgnore = false;
397
 
398
        $element = $this->element($xpath, 'coverage');
399
 
400
        if ($element) {
401
            $cacheDirectory = $this->getStringAttribute($element, 'cacheDirectory');
402
 
403
            if ($cacheDirectory !== null) {
404
                $cacheDirectory = new Directory(
405
                    $this->toAbsolutePath($filename, $cacheDirectory)
406
                );
407
            }
408
 
409
            $pathCoverage = $this->getBooleanAttribute(
410
                $element,
411
                'pathCoverage',
412
                false
413
            );
414
 
415
            $includeUncoveredFiles = $this->getBooleanAttribute(
416
                $element,
417
                'includeUncoveredFiles',
418
                true
419
            );
420
 
421
            $processUncoveredFiles = $this->getBooleanAttribute(
422
                $element,
423
                'processUncoveredFiles',
424
                false
425
            );
426
 
427
            $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute(
428
                $element,
429
                'ignoreDeprecatedCodeUnits',
430
                false
431
            );
432
 
433
            $disableCodeCoverageIgnore = $this->getBooleanAttribute(
434
                $element,
435
                'disableCodeCoverageIgnore',
436
                false
437
            );
438
        }
439
 
440
        $clover  = null;
441
        $element = $this->element($xpath, 'coverage/report/clover');
442
 
443
        if ($element) {
444
            $clover = new Clover(
445
                new File(
446
                    $this->toAbsolutePath(
447
                        $filename,
448
                        (string) $this->getStringAttribute($element, 'outputFile')
449
                    )
450
                )
451
            );
452
        }
453
 
454
        $cobertura = null;
455
        $element   = $this->element($xpath, 'coverage/report/cobertura');
456
 
457
        if ($element) {
458
            $cobertura = new Cobertura(
459
                new File(
460
                    $this->toAbsolutePath(
461
                        $filename,
462
                        (string) $this->getStringAttribute($element, 'outputFile')
463
                    )
464
                )
465
            );
466
        }
467
 
468
        $crap4j  = null;
469
        $element = $this->element($xpath, 'coverage/report/crap4j');
470
 
471
        if ($element) {
472
            $crap4j = new Crap4j(
473
                new File(
474
                    $this->toAbsolutePath(
475
                        $filename,
476
                        (string) $this->getStringAttribute($element, 'outputFile')
477
                    )
478
                ),
479
                $this->getIntegerAttribute($element, 'threshold', 30)
480
            );
481
        }
482
 
483
        $html    = null;
484
        $element = $this->element($xpath, 'coverage/report/html');
485
 
486
        if ($element) {
487
            $html = new CodeCoverageHtml(
488
                new Directory(
489
                    $this->toAbsolutePath(
490
                        $filename,
491
                        (string) $this->getStringAttribute($element, 'outputDirectory')
492
                    )
493
                ),
494
                $this->getIntegerAttribute($element, 'lowUpperBound', 50),
495
                $this->getIntegerAttribute($element, 'highLowerBound', 90)
496
            );
497
        }
498
 
499
        $php     = null;
500
        $element = $this->element($xpath, 'coverage/report/php');
501
 
502
        if ($element) {
503
            $php = new CodeCoveragePhp(
504
                new File(
505
                    $this->toAbsolutePath(
506
                        $filename,
507
                        (string) $this->getStringAttribute($element, 'outputFile')
508
                    )
509
                )
510
            );
511
        }
512
 
513
        $text    = null;
514
        $element = $this->element($xpath, 'coverage/report/text');
515
 
516
        if ($element) {
517
            $text = new CodeCoverageText(
518
                new File(
519
                    $this->toAbsolutePath(
520
                        $filename,
521
                        (string) $this->getStringAttribute($element, 'outputFile')
522
                    )
523
                ),
524
                $this->getBooleanAttribute($element, 'showUncoveredFiles', false),
525
                $this->getBooleanAttribute($element, 'showOnlySummary', false)
526
            );
527
        }
528
 
529
        $xml     = null;
530
        $element = $this->element($xpath, 'coverage/report/xml');
531
 
532
        if ($element) {
533
            $xml = new CodeCoverageXml(
534
                new Directory(
535
                    $this->toAbsolutePath(
536
                        $filename,
537
                        (string) $this->getStringAttribute($element, 'outputDirectory')
538
                    )
539
                )
540
            );
541
        }
542
 
543
        return new CodeCoverage(
544
            $cacheDirectory,
545
            $this->readFilterDirectories($filename, $xpath, 'coverage/include/directory'),
546
            $this->readFilterFiles($filename, $xpath, 'coverage/include/file'),
547
            $this->readFilterDirectories($filename, $xpath, 'coverage/exclude/directory'),
548
            $this->readFilterFiles($filename, $xpath, 'coverage/exclude/file'),
549
            $pathCoverage,
550
            $includeUncoveredFiles,
551
            $processUncoveredFiles,
552
            $ignoreDeprecatedCodeUnits,
553
            $disableCodeCoverageIgnore,
554
            $clover,
555
            $cobertura,
556
            $crap4j,
557
            $html,
558
            $php,
559
            $text,
560
            $xml
561
        );
562
    }
563
 
564
    /**
565
     * @deprecated
566
     */
567
    private function legacyCodeCoverage(string $filename, DOMXPath $xpath, DOMDocument $document): CodeCoverage
568
    {
569
        $ignoreDeprecatedCodeUnits = $this->getBooleanAttribute(
570
            $document->documentElement,
571
            'ignoreDeprecatedCodeUnitsFromCodeCoverage',
572
            false
573
        );
574
 
575
        $disableCodeCoverageIgnore = $this->getBooleanAttribute(
576
            $document->documentElement,
577
            'disableCodeCoverageIgnore',
578
            false
579
        );
580
 
581
        $includeUncoveredFiles = true;
582
        $processUncoveredFiles = false;
583
 
584
        $element = $this->element($xpath, 'filter/whitelist');
585
 
586
        if ($element) {
587
            if ($element->hasAttribute('addUncoveredFilesFromWhitelist')) {
588
                $includeUncoveredFiles = (bool) $this->getBoolean(
589
                    (string) $element->getAttribute('addUncoveredFilesFromWhitelist'),
590
                    true
591
                );
592
            }
593
 
594
            if ($element->hasAttribute('processUncoveredFilesFromWhitelist')) {
595
                $processUncoveredFiles = (bool) $this->getBoolean(
596
                    (string) $element->getAttribute('processUncoveredFilesFromWhitelist'),
597
                    false
598
                );
599
            }
600
        }
601
 
602
        $clover    = null;
603
        $cobertura = null;
604
        $crap4j    = null;
605
        $html      = null;
606
        $php       = null;
607
        $text      = null;
608
        $xml       = null;
609
 
610
        foreach ($xpath->query('logging/log') as $log) {
611
            assert($log instanceof DOMElement);
612
 
613
            $type   = (string) $log->getAttribute('type');
614
            $target = (string) $log->getAttribute('target');
615
 
616
            if (!$target) {
617
                continue;
618
            }
619
 
620
            $target = $this->toAbsolutePath($filename, $target);
621
 
622
            switch ($type) {
623
                case 'coverage-clover':
624
                    $clover = new Clover(
625
                        new File($target)
626
                    );
627
 
628
                    break;
629
 
630
                case 'coverage-cobertura':
631
                    $cobertura = new Cobertura(
632
                        new File($target)
633
                    );
634
 
635
                    break;
636
 
637
                case 'coverage-crap4j':
638
                    $crap4j = new Crap4j(
639
                        new File($target),
640
                        $this->getIntegerAttribute($log, 'threshold', 30)
641
                    );
642
 
643
                    break;
644
 
645
                case 'coverage-html':
646
                    $html = new CodeCoverageHtml(
647
                        new Directory($target),
648
                        $this->getIntegerAttribute($log, 'lowUpperBound', 50),
649
                        $this->getIntegerAttribute($log, 'highLowerBound', 90)
650
                    );
651
 
652
                    break;
653
 
654
                case 'coverage-php':
655
                    $php = new CodeCoveragePhp(
656
                        new File($target)
657
                    );
658
 
659
                    break;
660
 
661
                case 'coverage-text':
662
                    $text = new CodeCoverageText(
663
                        new File($target),
664
                        $this->getBooleanAttribute($log, 'showUncoveredFiles', false),
665
                        $this->getBooleanAttribute($log, 'showOnlySummary', false)
666
                    );
667
 
668
                    break;
669
 
670
                case 'coverage-xml':
671
                    $xml = new CodeCoverageXml(
672
                        new Directory($target)
673
                    );
674
 
675
                    break;
676
            }
677
        }
678
 
679
        return new CodeCoverage(
680
            null,
681
            $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/directory'),
682
            $this->readFilterFiles($filename, $xpath, 'filter/whitelist/file'),
683
            $this->readFilterDirectories($filename, $xpath, 'filter/whitelist/exclude/directory'),
684
            $this->readFilterFiles($filename, $xpath, 'filter/whitelist/exclude/file'),
685
            false,
686
            $includeUncoveredFiles,
687
            $processUncoveredFiles,
688
            $ignoreDeprecatedCodeUnits,
689
            $disableCodeCoverageIgnore,
690
            $clover,
691
            $cobertura,
692
            $crap4j,
693
            $html,
694
            $php,
695
            $text,
696
            $xml
697
        );
698
    }
699
 
700
    /**
701
     * If $value is 'false' or 'true', this returns the value that $value represents.
702
     * Otherwise, returns $default, which may be a string in rare cases.
703
     *
704
     * @see \PHPUnit\TextUI\XmlConfigurationTest::testPHPConfigurationIsReadCorrectly
705
     *
706
     * @param bool|string $default
707
     *
708
     * @return bool|string
709
     */
710
    private function getBoolean(string $value, $default)
711
    {
712
        if (strtolower($value) === 'false') {
713
            return false;
714
        }
715
 
716
        if (strtolower($value) === 'true') {
717
            return true;
718
        }
719
 
720
        return $default;
721
    }
722
 
723
    private function readFilterDirectories(string $filename, DOMXPath $xpath, string $query): FilterDirectoryCollection
724
    {
725
        $directories = [];
726
 
727
        foreach ($xpath->query($query) as $directoryNode) {
728
            assert($directoryNode instanceof DOMElement);
729
 
730
            $directoryPath = (string) $directoryNode->textContent;
731
 
732
            if (!$directoryPath) {
733
                continue;
734
            }
735
 
736
            $directories[] = new FilterDirectory(
737
                $this->toAbsolutePath($filename, $directoryPath),
738
                $directoryNode->hasAttribute('prefix') ? (string) $directoryNode->getAttribute('prefix') : '',
739
                $directoryNode->hasAttribute('suffix') ? (string) $directoryNode->getAttribute('suffix') : '.php',
740
                $directoryNode->hasAttribute('group') ? (string) $directoryNode->getAttribute('group') : 'DEFAULT'
741
            );
742
        }
743
 
744
        return FilterDirectoryCollection::fromArray($directories);
745
    }
746
 
747
    private function readFilterFiles(string $filename, DOMXPath $xpath, string $query): FileCollection
748
    {
749
        $files = [];
750
 
751
        foreach ($xpath->query($query) as $file) {
752
            $filePath = (string) $file->textContent;
753
 
754
            if ($filePath) {
755
                $files[] = new File($this->toAbsolutePath($filename, $filePath));
756
            }
757
        }
758
 
759
        return FileCollection::fromArray($files);
760
    }
761
 
762
    private function groups(DOMXPath $xpath): Groups
763
    {
764
        return $this->parseGroupConfiguration($xpath, 'groups');
765
    }
766
 
767
    private function testdoxGroups(DOMXPath $xpath): Groups
768
    {
769
        return $this->parseGroupConfiguration($xpath, 'testdoxGroups');
770
    }
771
 
772
    private function parseGroupConfiguration(DOMXPath $xpath, string $root): Groups
773
    {
774
        $include = [];
775
        $exclude = [];
776
 
777
        foreach ($xpath->query($root . '/include/group') as $group) {
778
            $include[] = new Group((string) $group->textContent);
779
        }
780
 
781
        foreach ($xpath->query($root . '/exclude/group') as $group) {
782
            $exclude[] = new Group((string) $group->textContent);
783
        }
784
 
785
        return new Groups(
786
            GroupCollection::fromArray($include),
787
            GroupCollection::fromArray($exclude)
788
        );
789
    }
790
 
791
    private function listeners(string $filename, DOMXPath $xpath): ExtensionCollection
792
    {
793
        $listeners = [];
794
 
795
        foreach ($xpath->query('listeners/listener') as $listener) {
796
            assert($listener instanceof DOMElement);
797
 
798
            $listeners[] = $this->getElementConfigurationParameters($filename, $listener);
799
        }
800
 
801
        return ExtensionCollection::fromArray($listeners);
802
    }
803
 
804
    private function getBooleanAttribute(DOMElement $element, string $attribute, bool $default): bool
805
    {
806
        if (!$element->hasAttribute($attribute)) {
807
            return $default;
808
        }
809
 
810
        return (bool) $this->getBoolean(
811
            (string) $element->getAttribute($attribute),
812
            false
813
        );
814
    }
815
 
816
    private function getIntegerAttribute(DOMElement $element, string $attribute, int $default): int
817
    {
818
        if (!$element->hasAttribute($attribute)) {
819
            return $default;
820
        }
821
 
822
        return $this->getInteger(
823
            (string) $element->getAttribute($attribute),
824
            $default
825
        );
826
    }
827
 
828
    private function getStringAttribute(DOMElement $element, string $attribute): ?string
829
    {
830
        if (!$element->hasAttribute($attribute)) {
831
            return null;
832
        }
833
 
834
        return (string) $element->getAttribute($attribute);
835
    }
836
 
837
    private function getInteger(string $value, int $default): int
838
    {
839
        if (is_numeric($value)) {
840
            return (int) $value;
841
        }
842
 
843
        return $default;
844
    }
845
 
846
    private function php(string $filename, DOMXPath $xpath): Php
847
    {
848
        $includePaths = [];
849
 
850
        foreach ($xpath->query('php/includePath') as $includePath) {
851
            $path = (string) $includePath->textContent;
852
 
853
            if ($path) {
854
                $includePaths[] = new Directory($this->toAbsolutePath($filename, $path));
855
            }
856
        }
857
 
858
        $iniSettings = [];
859
 
860
        foreach ($xpath->query('php/ini') as $ini) {
861
            assert($ini instanceof DOMElement);
862
 
863
            $iniSettings[] = new IniSetting(
864
                (string) $ini->getAttribute('name'),
865
                (string) $ini->getAttribute('value')
866
            );
867
        }
868
 
869
        $constants = [];
870
 
871
        foreach ($xpath->query('php/const') as $const) {
872
            assert($const instanceof DOMElement);
873
 
874
            $value = (string) $const->getAttribute('value');
875
 
876
            $constants[] = new Constant(
877
                (string) $const->getAttribute('name'),
878
                $this->getBoolean($value, $value)
879
            );
880
        }
881
 
882
        $variables = [
883
            'var'     => [],
884
            'env'     => [],
885
            'post'    => [],
886
            'get'     => [],
887
            'cookie'  => [],
888
            'server'  => [],
889
            'files'   => [],
890
            'request' => [],
891
        ];
892
 
893
        foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
894
            foreach ($xpath->query('php/' . $array) as $var) {
895
                assert($var instanceof DOMElement);
896
 
897
                $name     = (string) $var->getAttribute('name');
898
                $value    = (string) $var->getAttribute('value');
899
                $force    = false;
900
                $verbatim = false;
901
 
902
                if ($var->hasAttribute('force')) {
903
                    $force = (bool) $this->getBoolean($var->getAttribute('force'), false);
904
                }
905
 
906
                if ($var->hasAttribute('verbatim')) {
907
                    $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false);
908
                }
909
 
910
                if (!$verbatim) {
911
                    $value = $this->getBoolean($value, $value);
912
                }
913
 
914
                $variables[$array][] = new Variable($name, $value, $force);
915
            }
916
        }
917
 
918
        return new Php(
919
            DirectoryCollection::fromArray($includePaths),
920
            IniSettingCollection::fromArray($iniSettings),
921
            ConstantCollection::fromArray($constants),
922
            VariableCollection::fromArray($variables['var']),
923
            VariableCollection::fromArray($variables['env']),
924
            VariableCollection::fromArray($variables['post']),
925
            VariableCollection::fromArray($variables['get']),
926
            VariableCollection::fromArray($variables['cookie']),
927
            VariableCollection::fromArray($variables['server']),
928
            VariableCollection::fromArray($variables['files']),
929
            VariableCollection::fromArray($variables['request']),
930
        );
931
    }
932
 
933
    private function phpunit(string $filename, DOMDocument $document): PHPUnit
934
    {
935
        $executionOrder      = TestSuiteSorter::ORDER_DEFAULT;
936
        $defectsFirst        = false;
937
        $resolveDependencies = $this->getBooleanAttribute($document->documentElement, 'resolveDependencies', true);
938
 
939
        if ($document->documentElement->hasAttribute('executionOrder')) {
940
            foreach (explode(',', $document->documentElement->getAttribute('executionOrder')) as $order) {
941
                switch ($order) {
942
                    case 'default':
943
                        $executionOrder      = TestSuiteSorter::ORDER_DEFAULT;
944
                        $defectsFirst        = false;
945
                        $resolveDependencies = true;
946
 
947
                        break;
948
 
949
                    case 'depends':
950
                        $resolveDependencies = true;
951
 
952
                        break;
953
 
954
                    case 'no-depends':
955
                        $resolveDependencies = false;
956
 
957
                        break;
958
 
959
                    case 'defects':
960
                        $defectsFirst = true;
961
 
962
                        break;
963
 
964
                    case 'duration':
965
                        $executionOrder = TestSuiteSorter::ORDER_DURATION;
966
 
967
                        break;
968
 
969
                    case 'random':
970
                        $executionOrder = TestSuiteSorter::ORDER_RANDOMIZED;
971
 
972
                        break;
973
 
974
                    case 'reverse':
975
                        $executionOrder = TestSuiteSorter::ORDER_REVERSED;
976
 
977
                        break;
978
 
979
                    case 'size':
980
                        $executionOrder = TestSuiteSorter::ORDER_SIZE;
981
 
982
                        break;
983
                }
984
            }
985
        }
986
 
987
        $printerClass                          = $this->getStringAttribute($document->documentElement, 'printerClass');
988
        $testdox                               = $this->getBooleanAttribute($document->documentElement, 'testdox', false);
989
        $conflictBetweenPrinterClassAndTestdox = false;
990
 
991
        if ($testdox) {
992
            if ($printerClass !== null) {
993
                $conflictBetweenPrinterClassAndTestdox = true;
994
            }
995
 
996
            $printerClass = CliTestDoxPrinter::class;
997
        }
998
 
999
        $cacheResultFile = $this->getStringAttribute($document->documentElement, 'cacheResultFile');
1000
 
1001
        if ($cacheResultFile !== null) {
1002
            $cacheResultFile = $this->toAbsolutePath($filename, $cacheResultFile);
1003
        }
1004
 
1005
        $bootstrap = $this->getStringAttribute($document->documentElement, 'bootstrap');
1006
 
1007
        if ($bootstrap !== null) {
1008
            $bootstrap = $this->toAbsolutePath($filename, $bootstrap);
1009
        }
1010
 
1011
        $extensionsDirectory = $this->getStringAttribute($document->documentElement, 'extensionsDirectory');
1012
 
1013
        if ($extensionsDirectory !== null) {
1014
            $extensionsDirectory = $this->toAbsolutePath($filename, $extensionsDirectory);
1015
        }
1016
 
1017
        $testSuiteLoaderFile = $this->getStringAttribute($document->documentElement, 'testSuiteLoaderFile');
1018
 
1019
        if ($testSuiteLoaderFile !== null) {
1020
            $testSuiteLoaderFile = $this->toAbsolutePath($filename, $testSuiteLoaderFile);
1021
        }
1022
 
1023
        $printerFile = $this->getStringAttribute($document->documentElement, 'printerFile');
1024
 
1025
        if ($printerFile !== null) {
1026
            $printerFile = $this->toAbsolutePath($filename, $printerFile);
1027
        }
1028
 
1029
        return new PHPUnit(
1030
            $this->getBooleanAttribute($document->documentElement, 'cacheResult', true),
1031
            $cacheResultFile,
1032
            $this->getColumns($document),
1033
            $this->getColors($document),
1034
            $this->getBooleanAttribute($document->documentElement, 'stderr', false),
1035
            $this->getBooleanAttribute($document->documentElement, 'noInteraction', false),
1036
            $this->getBooleanAttribute($document->documentElement, 'verbose', false),
1037
            $this->getBooleanAttribute($document->documentElement, 'reverseDefectList', false),
1038
            $this->getBooleanAttribute($document->documentElement, 'convertDeprecationsToExceptions', false),
1039
            $this->getBooleanAttribute($document->documentElement, 'convertErrorsToExceptions', true),
1040
            $this->getBooleanAttribute($document->documentElement, 'convertNoticesToExceptions', true),
1041
            $this->getBooleanAttribute($document->documentElement, 'convertWarningsToExceptions', true),
1042
            $this->getBooleanAttribute($document->documentElement, 'forceCoversAnnotation', false),
1043
            $bootstrap,
1044
            $this->getBooleanAttribute($document->documentElement, 'processIsolation', false),
1045
            $this->getBooleanAttribute($document->documentElement, 'failOnEmptyTestSuite', false),
1046
            $this->getBooleanAttribute($document->documentElement, 'failOnIncomplete', false),
1047
            $this->getBooleanAttribute($document->documentElement, 'failOnRisky', false),
1048
            $this->getBooleanAttribute($document->documentElement, 'failOnSkipped', false),
1049
            $this->getBooleanAttribute($document->documentElement, 'failOnWarning', false),
1050
            $this->getBooleanAttribute($document->documentElement, 'stopOnDefect', false),
1051
            $this->getBooleanAttribute($document->documentElement, 'stopOnError', false),
1052
            $this->getBooleanAttribute($document->documentElement, 'stopOnFailure', false),
1053
            $this->getBooleanAttribute($document->documentElement, 'stopOnWarning', false),
1054
            $this->getBooleanAttribute($document->documentElement, 'stopOnIncomplete', false),
1055
            $this->getBooleanAttribute($document->documentElement, 'stopOnRisky', false),
1056
            $this->getBooleanAttribute($document->documentElement, 'stopOnSkipped', false),
1057
            $extensionsDirectory,
1058
            $this->getStringAttribute($document->documentElement, 'testSuiteLoaderClass'),
1059
            $testSuiteLoaderFile,
1060
            $printerClass,
1061
            $printerFile,
1062
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutChangesToGlobalState', false),
1063
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutOutputDuringTests', false),
1064
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutResourceUsageDuringSmallTests', false),
1065
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTestsThatDoNotTestAnything', true),
1066
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutTodoAnnotatedTests', false),
1067
            $this->getBooleanAttribute($document->documentElement, 'beStrictAboutCoversAnnotation', false),
1068
            $this->getBooleanAttribute($document->documentElement, 'enforceTimeLimit', false),
1069
            $this->getIntegerAttribute($document->documentElement, 'defaultTimeLimit', 1),
1070
            $this->getIntegerAttribute($document->documentElement, 'timeoutForSmallTests', 1),
1071
            $this->getIntegerAttribute($document->documentElement, 'timeoutForMediumTests', 10),
1072
            $this->getIntegerAttribute($document->documentElement, 'timeoutForLargeTests', 60),
1073
            $this->getStringAttribute($document->documentElement, 'defaultTestSuite'),
1074
            $executionOrder,
1075
            $resolveDependencies,
1076
            $defectsFirst,
1077
            $this->getBooleanAttribute($document->documentElement, 'backupGlobals', false),
1078
            $this->getBooleanAttribute($document->documentElement, 'backupStaticAttributes', false),
1079
            $this->getBooleanAttribute($document->documentElement, 'registerMockObjectsFromTestArgumentsRecursively', false),
1080
            $conflictBetweenPrinterClassAndTestdox
1081
        );
1082
    }
1083
 
1084
    private function getColors(DOMDocument $document): string
1085
    {
1086
        $colors = DefaultResultPrinter::COLOR_DEFAULT;
1087
 
1088
        if ($document->documentElement->hasAttribute('colors')) {
1089
            /* only allow boolean for compatibility with previous versions
1090
              'always' only allowed from command line */
1091
            if ($this->getBoolean($document->documentElement->getAttribute('colors'), false)) {
1092
                $colors = DefaultResultPrinter::COLOR_AUTO;
1093
            } else {
1094
                $colors = DefaultResultPrinter::COLOR_NEVER;
1095
            }
1096
        }
1097
 
1098
        return $colors;
1099
    }
1100
 
1101
    /**
1102
     * @return int|string
1103
     */
1104
    private function getColumns(DOMDocument $document)
1105
    {
1106
        $columns = 80;
1107
 
1108
        if ($document->documentElement->hasAttribute('columns')) {
1109
            $columns = (string) $document->documentElement->getAttribute('columns');
1110
 
1111
            if ($columns !== 'max') {
1112
                $columns = $this->getInteger($columns, 80);
1113
            }
1114
        }
1115
 
1116
        return $columns;
1117
    }
1118
 
1119
    private function testSuite(string $filename, DOMXPath $xpath): TestSuiteCollection
1120
    {
1121
        $testSuites = [];
1122
 
1123
        foreach ($this->getTestSuiteElements($xpath) as $element) {
1124
            $exclude = [];
1125
 
1126
            foreach ($element->getElementsByTagName('exclude') as $excludeNode) {
1127
                $excludeFile = (string) $excludeNode->textContent;
1128
 
1129
                if ($excludeFile) {
1130
                    $exclude[] = new File($this->toAbsolutePath($filename, $excludeFile));
1131
                }
1132
            }
1133
 
1134
            $directories = [];
1135
 
1136
            foreach ($element->getElementsByTagName('directory') as $directoryNode) {
1137
                assert($directoryNode instanceof DOMElement);
1138
 
1139
                $directory = (string) $directoryNode->textContent;
1140
 
1141
                if (empty($directory)) {
1142
                    continue;
1143
                }
1144
 
1145
                $prefix = '';
1146
 
1147
                if ($directoryNode->hasAttribute('prefix')) {
1148
                    $prefix = (string) $directoryNode->getAttribute('prefix');
1149
                }
1150
 
1151
                $suffix = 'Test.php';
1152
 
1153
                if ($directoryNode->hasAttribute('suffix')) {
1154
                    $suffix = (string) $directoryNode->getAttribute('suffix');
1155
                }
1156
 
1157
                $phpVersion = PHP_VERSION;
1158
 
1159
                if ($directoryNode->hasAttribute('phpVersion')) {
1160
                    $phpVersion = (string) $directoryNode->getAttribute('phpVersion');
1161
                }
1162
 
1163
                $phpVersionOperator = new VersionComparisonOperator('>=');
1164
 
1165
                if ($directoryNode->hasAttribute('phpVersionOperator')) {
1166
                    $phpVersionOperator = new VersionComparisonOperator((string) $directoryNode->getAttribute('phpVersionOperator'));
1167
                }
1168
 
1169
                $directories[] = new TestDirectory(
1170
                    $this->toAbsolutePath($filename, $directory),
1171
                    $prefix,
1172
                    $suffix,
1173
                    $phpVersion,
1174
                    $phpVersionOperator
1175
                );
1176
            }
1177
 
1178
            $files = [];
1179
 
1180
            foreach ($element->getElementsByTagName('file') as $fileNode) {
1181
                assert($fileNode instanceof DOMElement);
1182
 
1183
                $file = (string) $fileNode->textContent;
1184
 
1185
                if (empty($file)) {
1186
                    continue;
1187
                }
1188
 
1189
                $phpVersion = PHP_VERSION;
1190
 
1191
                if ($fileNode->hasAttribute('phpVersion')) {
1192
                    $phpVersion = (string) $fileNode->getAttribute('phpVersion');
1193
                }
1194
 
1195
                $phpVersionOperator = new VersionComparisonOperator('>=');
1196
 
1197
                if ($fileNode->hasAttribute('phpVersionOperator')) {
1198
                    $phpVersionOperator = new VersionComparisonOperator((string) $fileNode->getAttribute('phpVersionOperator'));
1199
                }
1200
 
1201
                $files[] = new TestFile(
1202
                    $this->toAbsolutePath($filename, $file),
1203
                    $phpVersion,
1204
                    $phpVersionOperator
1205
                );
1206
            }
1207
 
1208
            $testSuites[] = new TestSuiteConfiguration(
1209
                (string) $element->getAttribute('name'),
1210
                TestDirectoryCollection::fromArray($directories),
1211
                TestFileCollection::fromArray($files),
1212
                FileCollection::fromArray($exclude)
1213
            );
1214
        }
1215
 
1216
        return TestSuiteCollection::fromArray($testSuites);
1217
    }
1218
 
1219
    /**
1220
     * @return DOMElement[]
1221
     */
1222
    private function getTestSuiteElements(DOMXPath $xpath): array
1223
    {
1224
        /** @var DOMElement[] $elements */
1225
        $elements = [];
1226
 
1227
        $testSuiteNodes = $xpath->query('testsuites/testsuite');
1228
 
1229
        if ($testSuiteNodes->length === 0) {
1230
            $testSuiteNodes = $xpath->query('testsuite');
1231
        }
1232
 
1233
        if ($testSuiteNodes->length === 1) {
1234
            $element = $testSuiteNodes->item(0);
1235
 
1236
            assert($element instanceof DOMElement);
1237
 
1238
            $elements[] = $element;
1239
        } else {
1240
            foreach ($testSuiteNodes as $testSuiteNode) {
1241
                assert($testSuiteNode instanceof DOMElement);
1242
 
1243
                $elements[] = $testSuiteNode;
1244
            }
1245
        }
1246
 
1247
        return $elements;
1248
    }
1249
 
1250
    private function element(DOMXPath $xpath, string $element): ?DOMElement
1251
    {
1252
        $nodes = $xpath->query($element);
1253
 
1254
        if ($nodes->length === 1) {
1255
            $node = $nodes->item(0);
1256
 
1257
            assert($node instanceof DOMElement);
1258
 
1259
            return $node;
1260
        }
1261
 
1262
        return null;
1263
    }
1264
}