Configuration::getPHPConfiguration()   B
last analyzed

Complexity

Conditions 10
Paths 120

Size

Total Lines 66
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 39
nc 120
nop 0
dl 0
loc 66
rs 7.5
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
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\Util;
11
12
use DOMElement;
13
use DOMXPath;
14
use PHPUnit\Framework\Exception;
15
use PHPUnit\Framework\TestSuite;
16
use PHPUnit\Runner\TestSuiteSorter;
17
use PHPUnit\TextUI\ResultPrinter;
18
use SebastianBergmann\FileIterator\Facade as FileIteratorFacade;
19
20
/**
21
 * Wrapper for the PHPUnit XML configuration file.
22
 *
23
 * Example XML configuration file:
24
 * <code>
25
 * <?xml version="1.0" encoding="utf-8" ?>
26
 *
27
 * <phpunit backupGlobals="false"
28
 *          backupStaticAttributes="false"
29
 *          bootstrap="/path/to/bootstrap.php"
30
 *          cacheResult="false"
31
 *          cacheResultFile=".phpunit.result.cache"
32
 *          cacheTokens="false"
33
 *          columns="80"
34
 *          colors="false"
35
 *          stderr="false"
36
 *          convertDeprecationsToExceptions="true"
37
 *          convertErrorsToExceptions="true"
38
 *          convertNoticesToExceptions="true"
39
 *          convertWarningsToExceptions="true"
40
 *          disableCodeCoverageIgnore="false"
41
 *          forceCoversAnnotation="false"
42
 *          processIsolation="false"
43
 *          stopOnDefect="false"
44
 *          stopOnError="false"
45
 *          stopOnFailure="false"
46
 *          stopOnWarning="false"
47
 *          stopOnIncomplete="false"
48
 *          stopOnRisky="false"
49
 *          stopOnSkipped="false"
50
 *          failOnWarning="false"
51
 *          failOnRisky="false"
52
 *          extensionsDirectory="tools/phpunit.d"
53
 *          printerClass="PHPUnit\TextUI\ResultPrinter"
54
 *          testSuiteLoaderClass="PHPUnit\Runner\StandardTestSuiteLoader"
55
 *          defaultTestSuite=""
56
 *          beStrictAboutChangesToGlobalState="false"
57
 *          beStrictAboutCoversAnnotation="false"
58
 *          beStrictAboutOutputDuringTests="false"
59
 *          beStrictAboutResourceUsageDuringSmallTests="false"
60
 *          beStrictAboutTestsThatDoNotTestAnything="false"
61
 *          beStrictAboutTodoAnnotatedTests="false"
62
 *          defaultTimeLimit="0"
63
 *          enforceTimeLimit="false"
64
 *          ignoreDeprecatedCodeUnitsFromCodeCoverage="false"
65
 *          timeoutForSmallTests="1"
66
 *          timeoutForMediumTests="10"
67
 *          timeoutForLargeTests="60"
68
 *          verbose="false"
69
 *          reverseDefectList="false"
70
 *          registerMockObjectsFromTestArgumentsRecursively="false"
71
 *          executionOrder="default"
72
 *          executionOrderDefects="default"
73
 *          resolveDependencies="false">
74
 *   <testsuites>
75
 *     <testsuite name="My Test Suite">
76
 *       <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/path/to/files</directory>
77
 *       <file phpVersion="5.3.0" phpVersionOperator=">=">/path/to/MyTest.php</file>
78
 *       <exclude>/path/to/files/exclude</exclude>
79
 *     </testsuite>
80
 *   </testsuites>
81
 *
82
 *   <groups>
83
 *     <include>
84
 *       <group>name</group>
85
 *     </include>
86
 *     <exclude>
87
 *       <group>name</group>
88
 *     </exclude>
89
 *   </groups>
90
 *
91
 *   <testdoxGroups>
92
 *     <include>
93
 *       <group>name</group>
94
 *     </include>
95
 *     <exclude>
96
 *       <group>name</group>
97
 *     </exclude>
98
 *   </testdoxGroups>
99
 *
100
 *   <filter>
101
 *     <whitelist addUncoveredFilesFromWhitelist="true"
102
 *                processUncoveredFilesFromWhitelist="false">
103
 *       <directory suffix=".php">/path/to/files</directory>
104
 *       <file>/path/to/file</file>
105
 *       <exclude>
106
 *         <directory suffix=".php">/path/to/files</directory>
107
 *         <file>/path/to/file</file>
108
 *       </exclude>
109
 *     </whitelist>
110
 *   </filter>
111
 *
112
 *   <listeners>
113
 *     <listener class="MyListener" file="/optional/path/to/MyListener.php">
114
 *       <arguments>
115
 *         <array>
116
 *           <element key="0">
117
 *             <string>Sebastian</string>
118
 *           </element>
119
 *         </array>
120
 *         <integer>22</integer>
121
 *         <string>April</string>
122
 *         <double>19.78</double>
123
 *         <null/>
124
 *         <object class="stdClass"/>
125
 *         <file>MyRelativeFile.php</file>
126
 *         <directory>MyRelativeDir</directory>
127
 *       </arguments>
128
 *     </listener>
129
 *   </listeners>
130
 *
131
 *   <logging>
132
 *     <log type="coverage-html" target="/tmp/report" lowUpperBound="50" highLowerBound="90"/>
133
 *     <log type="coverage-clover" target="/tmp/clover.xml"/>
134
 *     <log type="coverage-crap4j" target="/tmp/crap.xml" threshold="30"/>
135
 *     <log type="plain" target="/tmp/logfile.txt"/>
136
 *     <log type="teamcity" target="/tmp/logfile.txt"/>
137
 *     <log type="junit" target="/tmp/logfile.xml"/>
138
 *     <log type="testdox-html" target="/tmp/testdox.html"/>
139
 *     <log type="testdox-text" target="/tmp/testdox.txt"/>
140
 *     <log type="testdox-xml" target="/tmp/testdox.xml"/>
141
 *   </logging>
142
 *
143
 *   <php>
144
 *     <includePath>.</includePath>
145
 *     <ini name="foo" value="bar"/>
146
 *     <const name="foo" value="bar"/>
147
 *     <var name="foo" value="bar"/>
148
 *     <env name="foo" value="bar"/>
149
 *     <post name="foo" value="bar"/>
150
 *     <get name="foo" value="bar"/>
151
 *     <cookie name="foo" value="bar"/>
152
 *     <server name="foo" value="bar"/>
153
 *     <files name="foo" value="bar"/>
154
 *     <request name="foo" value="bar"/>
155
 *   </php>
156
 * </phpunit>
157
 * </code>
158
 */
159
final class Configuration
160
{
161
    /**
162
     * @var self[]
163
     */
164
    private static $instances = [];
165
166
    /**
167
     * @var \DOMDocument
168
     */
169
    private $document;
170
171
    /**
172
     * @var DOMXPath
173
     */
174
    private $xpath;
175
176
    /**
177
     * @var string
178
     */
179
    private $filename;
180
181
    /**
182
     * @var \LibXMLError[]
183
     */
184
    private $errors = [];
185
186
    /**
187
     * Returns a PHPUnit configuration object.
188
     *
189
     * @throws Exception
190
     */
191
    public static function getInstance(string $filename): self
192
    {
193
        $realPath = \realpath($filename);
194
195
        if ($realPath === false) {
196
            throw new Exception(
197
                \sprintf(
198
                    'Could not read "%s".',
199
                    $filename
200
                )
201
            );
202
        }
203
204
        /** @var string $realPath */
205
        if (!isset(self::$instances[$realPath])) {
206
            self::$instances[$realPath] = new self($realPath);
207
        }
208
209
        return self::$instances[$realPath];
210
    }
211
212
    /**
213
     * Loads a PHPUnit configuration file.
214
     *
215
     * @throws Exception
216
     */
217
    private function __construct(string $filename)
218
    {
219
        $this->filename = $filename;
220
        $this->document = Xml::loadFile($filename, false, true, true);
221
        $this->xpath    = new DOMXPath($this->document);
222
223
        $this->validateConfigurationAgainstSchema();
224
    }
225
226
    /**
227
     * @codeCoverageIgnore
228
     */
229
    private function __clone()
230
    {
231
    }
232
233
    public function hasValidationErrors(): bool
234
    {
235
        return \count($this->errors) > 0;
236
    }
237
238
    public function getValidationErrors(): array
239
    {
240
        $result = [];
241
242
        foreach ($this->errors as $error) {
243
            if (!isset($result[$error->line])) {
244
                $result[$error->line] = [];
245
            }
246
            $result[$error->line][] = \trim($error->message);
247
        }
248
249
        return $result;
250
    }
251
252
    /**
253
     * Returns the real path to the configuration file.
254
     */
255
    public function getFilename(): string
256
    {
257
        return $this->filename;
258
    }
259
260
    public function getExtensionConfiguration(): array
261
    {
262
        $result = [];
263
264
        foreach ($this->xpath->query('extensions/extension') as $extension) {
265
            /** @var DOMElement $extension */
266
            $class     = (string) $extension->getAttribute('class');
267
            $file      = '';
268
            $arguments = $this->getConfigurationArguments($extension->childNodes);
269
270
            if ($extension->getAttribute('file')) {
271
                $file = $this->toAbsolutePath(
272
                    (string) $extension->getAttribute('file'),
273
                    true
274
                );
275
            }
276
            $result[] = [
277
                'class'     => $class,
278
                'file'      => $file,
279
                'arguments' => $arguments,
280
            ];
281
        }
282
283
        return $result;
284
    }
285
286
    /**
287
     * Returns the configuration for SUT filtering.
288
     */
289
    public function getFilterConfiguration(): array
290
    {
291
        $addUncoveredFilesFromWhitelist     = true;
292
        $processUncoveredFilesFromWhitelist = false;
293
        $includeDirectory                   = [];
294
        $includeFile                        = [];
295
        $excludeDirectory                   = [];
296
        $excludeFile                        = [];
297
298
        $tmp = $this->xpath->query('filter/whitelist');
299
300
        if ($tmp->length === 1) {
301
            if ($tmp->item(0)->hasAttribute('addUncoveredFilesFromWhitelist')) {
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

301
            if ($tmp->item(0)->/** @scrutinizer ignore-call */ hasAttribute('addUncoveredFilesFromWhitelist')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
302
                $addUncoveredFilesFromWhitelist = $this->getBoolean(
303
                    (string) $tmp->item(0)->getAttribute(
304
                        'addUncoveredFilesFromWhitelist'
305
                    ),
306
                    true
307
                );
308
            }
309
310
            if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) {
311
                $processUncoveredFilesFromWhitelist = $this->getBoolean(
312
                    (string) $tmp->item(0)->getAttribute(
313
                        'processUncoveredFilesFromWhitelist'
314
                    ),
315
                    false
316
                );
317
            }
318
319
            $includeDirectory = $this->readFilterDirectories(
320
                'filter/whitelist/directory'
321
            );
322
323
            $includeFile = $this->readFilterFiles(
324
                'filter/whitelist/file'
325
            );
326
327
            $excludeDirectory = $this->readFilterDirectories(
328
                'filter/whitelist/exclude/directory'
329
            );
330
331
            $excludeFile = $this->readFilterFiles(
332
                'filter/whitelist/exclude/file'
333
            );
334
        }
335
336
        return [
337
            'whitelist' => [
338
                'addUncoveredFilesFromWhitelist'     => $addUncoveredFilesFromWhitelist,
339
                'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist,
340
                'include'                            => [
341
                    'directory' => $includeDirectory,
342
                    'file'      => $includeFile,
343
                ],
344
                'exclude' => [
345
                    'directory' => $excludeDirectory,
346
                    'file'      => $excludeFile,
347
                ],
348
            ],
349
        ];
350
    }
351
352
    /**
353
     * Returns the configuration for groups.
354
     */
355
    public function getGroupConfiguration(): array
356
    {
357
        return $this->parseGroupConfiguration('groups');
358
    }
359
360
    /**
361
     * Returns the configuration for testdox groups.
362
     */
363
    public function getTestdoxGroupConfiguration(): array
364
    {
365
        return $this->parseGroupConfiguration('testdoxGroups');
366
    }
367
368
    /**
369
     * Returns the configuration for listeners.
370
     */
371
    public function getListenerConfiguration(): array
372
    {
373
        $result = [];
374
375
        foreach ($this->xpath->query('listeners/listener') as $listener) {
376
            /** @var DOMElement $listener */
377
            $class     = (string) $listener->getAttribute('class');
378
            $file      = '';
379
            $arguments = $this->getConfigurationArguments($listener->childNodes);
380
381
            if ($listener->getAttribute('file')) {
382
                $file = $this->toAbsolutePath(
383
                    (string) $listener->getAttribute('file'),
384
                    true
385
                );
386
            }
387
388
            $result[] = [
389
                'class'     => $class,
390
                'file'      => $file,
391
                'arguments' => $arguments,
392
            ];
393
        }
394
395
        return $result;
396
    }
397
398
    /**
399
     * Returns the logging configuration.
400
     */
401
    public function getLoggingConfiguration(): array
402
    {
403
        $result = [];
404
405
        foreach ($this->xpath->query('logging/log') as $log) {
406
            /** @var DOMElement $log */
407
            $type   = (string) $log->getAttribute('type');
408
            $target = (string) $log->getAttribute('target');
409
410
            if (!$target) {
411
                continue;
412
            }
413
414
            $target = $this->toAbsolutePath($target);
415
416
            if ($type === 'coverage-html') {
417
                if ($log->hasAttribute('lowUpperBound')) {
418
                    $result['lowUpperBound'] = $this->getInteger(
419
                        (string) $log->getAttribute('lowUpperBound'),
420
                        50
421
                    );
422
                }
423
424
                if ($log->hasAttribute('highLowerBound')) {
425
                    $result['highLowerBound'] = $this->getInteger(
426
                        (string) $log->getAttribute('highLowerBound'),
427
                        90
428
                    );
429
                }
430
            } elseif ($type === 'coverage-crap4j') {
431
                if ($log->hasAttribute('threshold')) {
432
                    $result['crap4jThreshold'] = $this->getInteger(
433
                        (string) $log->getAttribute('threshold'),
434
                        30
435
                    );
436
                }
437
            } elseif ($type === 'coverage-text') {
438
                if ($log->hasAttribute('showUncoveredFiles')) {
439
                    $result['coverageTextShowUncoveredFiles'] = $this->getBoolean(
440
                        (string) $log->getAttribute('showUncoveredFiles'),
441
                        false
442
                    );
443
                }
444
445
                if ($log->hasAttribute('showOnlySummary')) {
446
                    $result['coverageTextShowOnlySummary'] = $this->getBoolean(
447
                        (string) $log->getAttribute('showOnlySummary'),
448
                        false
449
                    );
450
                }
451
            }
452
453
            $result[$type] = $target;
454
        }
455
456
        return $result;
457
    }
458
459
    /**
460
     * Returns the PHP configuration.
461
     */
462
    public function getPHPConfiguration(): array
463
    {
464
        $result = [
465
            'include_path' => [],
466
            'ini'          => [],
467
            'const'        => [],
468
            'var'          => [],
469
            'env'          => [],
470
            'post'         => [],
471
            'get'          => [],
472
            'cookie'       => [],
473
            'server'       => [],
474
            'files'        => [],
475
            'request'      => [],
476
        ];
477
478
        foreach ($this->xpath->query('php/includePath') as $includePath) {
479
            $path = (string) $includePath->textContent;
480
481
            if ($path) {
482
                $result['include_path'][] = $this->toAbsolutePath($path);
483
            }
484
        }
485
486
        foreach ($this->xpath->query('php/ini') as $ini) {
487
            /** @var DOMElement $ini */
488
            $name  = (string) $ini->getAttribute('name');
489
            $value = (string) $ini->getAttribute('value');
490
491
            $result['ini'][$name]['value'] = $value;
492
        }
493
494
        foreach ($this->xpath->query('php/const') as $const) {
495
            /** @var DOMElement $const */
496
            $name  = (string) $const->getAttribute('name');
497
            $value = (string) $const->getAttribute('value');
498
499
            $result['const'][$name]['value'] = $this->getBoolean($value, $value);
500
        }
501
502
        foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
503
            foreach ($this->xpath->query('php/' . $array) as $var) {
504
                /** @var DOMElement $var */
505
                $name     = (string) $var->getAttribute('name');
506
                $value    = (string) $var->getAttribute('value');
507
                $verbatim = false;
508
509
                if ($var->hasAttribute('verbatim')) {
510
                    $verbatim                          = $this->getBoolean($var->getAttribute('verbatim'), false);
511
                    $result[$array][$name]['verbatim'] = $verbatim;
512
                }
513
514
                if ($var->hasAttribute('force')) {
515
                    $force                          = $this->getBoolean($var->getAttribute('force'), false);
516
                    $result[$array][$name]['force'] = $force;
517
                }
518
519
                if (!$verbatim) {
520
                    $value = $this->getBoolean($value, $value);
521
                }
522
523
                $result[$array][$name]['value'] = $value;
524
            }
525
        }
526
527
        return $result;
528
    }
529
530
    /**
531
     * Handles the PHP configuration.
532
     */
533
    public function handlePHPConfiguration(): void
534
    {
535
        $configuration = $this->getPHPConfiguration();
536
537
        if (!empty($configuration['include_path'])) {
538
            \ini_set(
539
                'include_path',
540
                \implode(\PATH_SEPARATOR, $configuration['include_path']) .
541
                \PATH_SEPARATOR .
542
                \ini_get('include_path')
543
            );
544
        }
545
546
        foreach ($configuration['ini'] as $name => $data) {
547
            $value = $data['value'];
548
549
            if (\defined($value)) {
550
                $value = (string) \constant($value);
551
            }
552
553
            \ini_set($name, $value);
554
        }
555
556
        foreach ($configuration['const'] as $name => $data) {
557
            $value = $data['value'];
558
559
            if (!\defined($name)) {
560
                \define($name, $value);
561
            }
562
        }
563
564
        foreach (['var', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
565
            /*
566
             * @see https://github.com/sebastianbergmann/phpunit/issues/277
567
             */
568
            switch ($array) {
569
                case 'var':
570
                    $target = &$GLOBALS;
571
572
                    break;
573
574
                case 'server':
575
                    $target = &$_SERVER;
576
577
                    break;
578
579
                default:
580
                    $target = &$GLOBALS['_' . \strtoupper($array)];
581
582
                    break;
583
            }
584
585
            foreach ($configuration[$array] as $name => $data) {
586
                $target[$name] = $data['value'];
587
            }
588
        }
589
590
        foreach ($configuration['env'] as $name => $data) {
591
            $value = $data['value'];
592
            $force = $data['force'] ?? false;
593
594
            if ($force || \getenv($name) === false) {
595
                \putenv("{$name}={$value}");
596
            }
597
598
            $value = \getenv($name);
599
600
            if (!isset($_ENV[$name])) {
601
                $_ENV[$name] = $value;
602
            }
603
604
            if ($force === true) {
605
                $_ENV[$name] = $value;
606
            }
607
        }
608
    }
609
610
    /**
611
     * Returns the PHPUnit configuration.
612
     */
613
    public function getPHPUnitConfiguration(): array
614
    {
615
        $result = [];
616
        $root   = $this->document->documentElement;
617
618
        if ($root->hasAttribute('cacheTokens')) {
619
            $result['cacheTokens'] = $this->getBoolean(
620
                (string) $root->getAttribute('cacheTokens'),
621
                false
622
            );
623
        }
624
625
        if ($root->hasAttribute('columns')) {
626
            $columns = (string) $root->getAttribute('columns');
627
628
            if ($columns === 'max') {
629
                $result['columns'] = 'max';
630
            } else {
631
                $result['columns'] = $this->getInteger($columns, 80);
632
            }
633
        }
634
635
        if ($root->hasAttribute('colors')) {
636
            /* only allow boolean for compatibility with previous versions
637
              'always' only allowed from command line */
638
            if ($this->getBoolean($root->getAttribute('colors'), false)) {
639
                $result['colors'] = ResultPrinter::COLOR_AUTO;
640
            } else {
641
                $result['colors'] = ResultPrinter::COLOR_NEVER;
642
            }
643
        }
644
645
        /*
646
         * @see https://github.com/sebastianbergmann/phpunit/issues/657
647
         */
648
        if ($root->hasAttribute('stderr')) {
649
            $result['stderr'] = $this->getBoolean(
650
                (string) $root->getAttribute('stderr'),
651
                false
652
            );
653
        }
654
655
        if ($root->hasAttribute('backupGlobals')) {
656
            $result['backupGlobals'] = $this->getBoolean(
657
                (string) $root->getAttribute('backupGlobals'),
658
                false
659
            );
660
        }
661
662
        if ($root->hasAttribute('backupStaticAttributes')) {
663
            $result['backupStaticAttributes'] = $this->getBoolean(
664
                (string) $root->getAttribute('backupStaticAttributes'),
665
                false
666
            );
667
        }
668
669
        if ($root->getAttribute('bootstrap')) {
670
            $result['bootstrap'] = $this->toAbsolutePath(
671
                (string) $root->getAttribute('bootstrap')
672
            );
673
        }
674
675
        if ($root->hasAttribute('convertDeprecationsToExceptions')) {
676
            $result['convertDeprecationsToExceptions'] = $this->getBoolean(
677
                (string) $root->getAttribute('convertDeprecationsToExceptions'),
678
                true
679
            );
680
        }
681
682
        if ($root->hasAttribute('convertErrorsToExceptions')) {
683
            $result['convertErrorsToExceptions'] = $this->getBoolean(
684
                (string) $root->getAttribute('convertErrorsToExceptions'),
685
                true
686
            );
687
        }
688
689
        if ($root->hasAttribute('convertNoticesToExceptions')) {
690
            $result['convertNoticesToExceptions'] = $this->getBoolean(
691
                (string) $root->getAttribute('convertNoticesToExceptions'),
692
                true
693
            );
694
        }
695
696
        if ($root->hasAttribute('convertWarningsToExceptions')) {
697
            $result['convertWarningsToExceptions'] = $this->getBoolean(
698
                (string) $root->getAttribute('convertWarningsToExceptions'),
699
                true
700
            );
701
        }
702
703
        if ($root->hasAttribute('forceCoversAnnotation')) {
704
            $result['forceCoversAnnotation'] = $this->getBoolean(
705
                (string) $root->getAttribute('forceCoversAnnotation'),
706
                false
707
            );
708
        }
709
710
        if ($root->hasAttribute('disableCodeCoverageIgnore')) {
711
            $result['disableCodeCoverageIgnore'] = $this->getBoolean(
712
                (string) $root->getAttribute('disableCodeCoverageIgnore'),
713
                false
714
            );
715
        }
716
717
        if ($root->hasAttribute('processIsolation')) {
718
            $result['processIsolation'] = $this->getBoolean(
719
                (string) $root->getAttribute('processIsolation'),
720
                false
721
            );
722
        }
723
724
        if ($root->hasAttribute('stopOnDefect')) {
725
            $result['stopOnDefect'] = $this->getBoolean(
726
                (string) $root->getAttribute('stopOnDefect'),
727
                false
728
            );
729
        }
730
731
        if ($root->hasAttribute('stopOnError')) {
732
            $result['stopOnError'] = $this->getBoolean(
733
                (string) $root->getAttribute('stopOnError'),
734
                false
735
            );
736
        }
737
738
        if ($root->hasAttribute('stopOnFailure')) {
739
            $result['stopOnFailure'] = $this->getBoolean(
740
                (string) $root->getAttribute('stopOnFailure'),
741
                false
742
            );
743
        }
744
745
        if ($root->hasAttribute('stopOnWarning')) {
746
            $result['stopOnWarning'] = $this->getBoolean(
747
                (string) $root->getAttribute('stopOnWarning'),
748
                false
749
            );
750
        }
751
752
        if ($root->hasAttribute('stopOnIncomplete')) {
753
            $result['stopOnIncomplete'] = $this->getBoolean(
754
                (string) $root->getAttribute('stopOnIncomplete'),
755
                false
756
            );
757
        }
758
759
        if ($root->hasAttribute('stopOnRisky')) {
760
            $result['stopOnRisky'] = $this->getBoolean(
761
                (string) $root->getAttribute('stopOnRisky'),
762
                false
763
            );
764
        }
765
766
        if ($root->hasAttribute('stopOnSkipped')) {
767
            $result['stopOnSkipped'] = $this->getBoolean(
768
                (string) $root->getAttribute('stopOnSkipped'),
769
                false
770
            );
771
        }
772
773
        if ($root->hasAttribute('failOnWarning')) {
774
            $result['failOnWarning'] = $this->getBoolean(
775
                (string) $root->getAttribute('failOnWarning'),
776
                false
777
            );
778
        }
779
780
        if ($root->hasAttribute('failOnRisky')) {
781
            $result['failOnRisky'] = $this->getBoolean(
782
                (string) $root->getAttribute('failOnRisky'),
783
                false
784
            );
785
        }
786
787
        if ($root->hasAttribute('testSuiteLoaderClass')) {
788
            $result['testSuiteLoaderClass'] = (string) $root->getAttribute(
789
                'testSuiteLoaderClass'
790
            );
791
        }
792
793
        if ($root->hasAttribute('defaultTestSuite')) {
794
            $result['defaultTestSuite'] = (string) $root->getAttribute(
795
                'defaultTestSuite'
796
            );
797
        }
798
799
        if ($root->getAttribute('testSuiteLoaderFile')) {
800
            $result['testSuiteLoaderFile'] = $this->toAbsolutePath(
801
                (string) $root->getAttribute('testSuiteLoaderFile')
802
            );
803
        }
804
805
        if ($root->hasAttribute('printerClass')) {
806
            $result['printerClass'] = (string) $root->getAttribute(
807
                'printerClass'
808
            );
809
        }
810
811
        if ($root->getAttribute('printerFile')) {
812
            $result['printerFile'] = $this->toAbsolutePath(
813
                (string) $root->getAttribute('printerFile')
814
            );
815
        }
816
817
        if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) {
818
            $result['beStrictAboutChangesToGlobalState'] = $this->getBoolean(
819
                (string) $root->getAttribute('beStrictAboutChangesToGlobalState'),
820
                false
821
            );
822
        }
823
824
        if ($root->hasAttribute('beStrictAboutOutputDuringTests')) {
825
            $result['disallowTestOutput'] = $this->getBoolean(
826
                (string) $root->getAttribute('beStrictAboutOutputDuringTests'),
827
                false
828
            );
829
        }
830
831
        if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) {
832
            $result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean(
833
                (string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'),
834
                false
835
            );
836
        }
837
838
        if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) {
839
            $result['reportUselessTests'] = $this->getBoolean(
840
                (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'),
841
                true
842
            );
843
        }
844
845
        if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) {
846
            $result['disallowTodoAnnotatedTests'] = $this->getBoolean(
847
                (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'),
848
                false
849
            );
850
        }
851
852
        if ($root->hasAttribute('beStrictAboutCoversAnnotation')) {
853
            $result['strictCoverage'] = $this->getBoolean(
854
                (string) $root->getAttribute('beStrictAboutCoversAnnotation'),
855
                false
856
            );
857
        }
858
859
        if ($root->hasAttribute('defaultTimeLimit')) {
860
            $result['defaultTimeLimit'] = $this->getInteger(
861
                (string) $root->getAttribute('defaultTimeLimit'),
862
                1
863
            );
864
        }
865
866
        if ($root->hasAttribute('enforceTimeLimit')) {
867
            $result['enforceTimeLimit'] = $this->getBoolean(
868
                (string) $root->getAttribute('enforceTimeLimit'),
869
                false
870
            );
871
        }
872
873
        if ($root->hasAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage')) {
874
            $result['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $this->getBoolean(
875
                (string) $root->getAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage'),
876
                false
877
            );
878
        }
879
880
        if ($root->hasAttribute('timeoutForSmallTests')) {
881
            $result['timeoutForSmallTests'] = $this->getInteger(
882
                (string) $root->getAttribute('timeoutForSmallTests'),
883
                1
884
            );
885
        }
886
887
        if ($root->hasAttribute('timeoutForMediumTests')) {
888
            $result['timeoutForMediumTests'] = $this->getInteger(
889
                (string) $root->getAttribute('timeoutForMediumTests'),
890
                10
891
            );
892
        }
893
894
        if ($root->hasAttribute('timeoutForLargeTests')) {
895
            $result['timeoutForLargeTests'] = $this->getInteger(
896
                (string) $root->getAttribute('timeoutForLargeTests'),
897
                60
898
            );
899
        }
900
901
        if ($root->hasAttribute('reverseDefectList')) {
902
            $result['reverseDefectList'] = $this->getBoolean(
903
                (string) $root->getAttribute('reverseDefectList'),
904
                false
905
            );
906
        }
907
908
        if ($root->hasAttribute('verbose')) {
909
            $result['verbose'] = $this->getBoolean(
910
                (string) $root->getAttribute('verbose'),
911
                false
912
            );
913
        }
914
915
        if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) {
916
            $result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean(
917
                (string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'),
918
                false
919
            );
920
        }
921
922
        if ($root->hasAttribute('extensionsDirectory')) {
923
            $result['extensionsDirectory'] = $this->toAbsolutePath(
924
                (string) $root->getAttribute(
925
                    'extensionsDirectory'
926
                )
927
            );
928
        }
929
930
        if ($root->hasAttribute('cacheResult')) {
931
            $result['cacheResult'] = $this->getBoolean(
932
                (string) $root->getAttribute('cacheResult'),
933
                false
934
            );
935
        }
936
937
        if ($root->hasAttribute('cacheResultFile')) {
938
            $result['cacheResultFile'] = $this->toAbsolutePath(
939
                (string) $root->getAttribute('cacheResultFile')
940
            );
941
        }
942
943
        if ($root->hasAttribute('executionOrder')) {
944
            foreach (\explode(',', $root->getAttribute('executionOrder')) as $order) {
945
                switch ($order) {
946
                    case 'default':
947
                        $result['executionOrder']        = TestSuiteSorter::ORDER_DEFAULT;
948
                        $result['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFAULT;
949
                        $result['resolveDependencies']   = false;
950
951
                        break;
952
                    case 'reverse':
953
                        $result['executionOrder'] = TestSuiteSorter::ORDER_REVERSED;
954
955
                        break;
956
                    case 'random':
957
                        $result['executionOrder'] = TestSuiteSorter::ORDER_RANDOMIZED;
958
959
                        break;
960
                    case 'defects':
961
                        $result['executionOrderDefects'] = TestSuiteSorter::ORDER_DEFECTS_FIRST;
962
963
                        break;
964
                    case 'depends':
965
                        $result['resolveDependencies'] = true;
966
967
                        break;
968
                }
969
            }
970
        }
971
972
        if ($root->hasAttribute('resolveDependencies')) {
973
            $result['resolveDependencies'] = $this->getBoolean(
974
                (string) $root->getAttribute('resolveDependencies'),
975
                false
976
            );
977
        }
978
979
        return $result;
980
    }
981
982
    /**
983
     * Returns the test suite configuration.
984
     *
985
     * @throws Exception
986
     */
987
    public function getTestSuiteConfiguration(string $testSuiteFilter = ''): TestSuite
988
    {
989
        $testSuiteNodes = $this->xpath->query('testsuites/testsuite');
990
991
        if ($testSuiteNodes->length === 0) {
992
            $testSuiteNodes = $this->xpath->query('testsuite');
993
        }
994
995
        if ($testSuiteNodes->length === 1) {
996
            return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter);
997
        }
998
999
        $suite = new TestSuite;
1000
1001
        foreach ($testSuiteNodes as $testSuiteNode) {
1002
            $suite->addTestSuite(
1003
                $this->getTestSuite($testSuiteNode, $testSuiteFilter)
1004
            );
1005
        }
1006
1007
        return $suite;
1008
    }
1009
1010
    /**
1011
     * Returns the test suite names from the configuration.
1012
     */
1013
    public function getTestSuiteNames(): array
1014
    {
1015
        $names = [];
1016
1017
        foreach ($this->xpath->query('*/testsuite') as $node) {
1018
            /* @var DOMElement $node */
1019
            $names[] = $node->getAttribute('name');
1020
        }
1021
1022
        return $names;
1023
    }
1024
1025
    private function validateConfigurationAgainstSchema(): void
1026
    {
1027
        $original    = \libxml_use_internal_errors(true);
1028
        $xsdFilename = __DIR__ . '/../../phpunit.xsd';
1029
1030
        if (\defined('__PHPUNIT_PHAR_ROOT__')) {
1031
            $xsdFilename =  __PHPUNIT_PHAR_ROOT__ . '/phpunit.xsd';
0 ignored issues
show
Bug introduced by
The constant __PHPUNIT_PHAR_ROOT__ was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
1032
        }
1033
1034
        $this->document->schemaValidate($xsdFilename);
1035
        $this->errors = \libxml_get_errors();
1036
        \libxml_clear_errors();
1037
        \libxml_use_internal_errors($original);
1038
    }
1039
1040
    /**
1041
     * Collects and returns the configuration arguments from the PHPUnit
1042
     * XML configuration
1043
     */
1044
    private function getConfigurationArguments(\DOMNodeList $nodes): array
1045
    {
1046
        $arguments = [];
1047
1048
        if ($nodes->length === 0) {
1049
            return $arguments;
1050
        }
1051
1052
        foreach ($nodes as $node) {
1053
            if (!$node instanceof DOMElement) {
1054
                continue;
1055
            }
1056
1057
            if ($node->tagName !== 'arguments') {
1058
                continue;
1059
            }
1060
1061
            foreach ($node->childNodes as $argument) {
1062
                if (!$argument instanceof DOMElement) {
1063
                    continue;
1064
                }
1065
1066
                if ($argument->tagName === 'file' || $argument->tagName === 'directory') {
1067
                    $arguments[] = $this->toAbsolutePath((string) $argument->textContent);
1068
                } else {
1069
                    $arguments[] = Xml::xmlToVariable($argument);
1070
                }
1071
            }
1072
        }
1073
1074
        return $arguments;
1075
    }
1076
1077
    /**
1078
     * @throws \PHPUnit\Framework\Exception
1079
     */
1080
    private function getTestSuite(DOMElement $testSuiteNode, string $testSuiteFilter = ''): TestSuite
1081
    {
1082
        if ($testSuiteNode->hasAttribute('name')) {
1083
            $suite = new TestSuite(
1084
                (string) $testSuiteNode->getAttribute('name')
1085
            );
1086
        } else {
1087
            $suite = new TestSuite;
1088
        }
1089
1090
        $exclude = [];
1091
1092
        foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) {
1093
            $excludeFile = (string) $excludeNode->textContent;
1094
1095
            if ($excludeFile) {
1096
                $exclude[] = $this->toAbsolutePath($excludeFile);
1097
            }
1098
        }
1099
1100
        $fileIteratorFacade = new FileIteratorFacade;
1101
        $testSuiteFilter    = $testSuiteFilter ? \explode(',', $testSuiteFilter) : [];
1102
1103
        foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) {
1104
            /** @var DOMElement $directoryNode */
1105
            if (!empty($testSuiteFilter) && !\in_array($directoryNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
0 ignored issues
show
Bug introduced by
The method getAttribute() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1105
            if (!empty($testSuiteFilter) && !\in_array($directoryNode->parentNode->/** @scrutinizer ignore-call */ getAttribute('name'), $testSuiteFilter)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1106
                continue;
1107
            }
1108
1109
            $directory = (string) $directoryNode->textContent;
1110
1111
            if (empty($directory)) {
1112
                continue;
1113
            }
1114
1115
            $prefix = '';
1116
            $suffix = 'Test.php';
1117
1118
            if (!$this->satisfiesPhpVersion($directoryNode)) {
1119
                continue;
1120
            }
1121
1122
            if ($directoryNode->hasAttribute('prefix')) {
1123
                $prefix = (string) $directoryNode->getAttribute('prefix');
1124
            }
1125
1126
            if ($directoryNode->hasAttribute('suffix')) {
1127
                $suffix = (string) $directoryNode->getAttribute('suffix');
1128
            }
1129
1130
            $files = $fileIteratorFacade->getFilesAsArray(
1131
                $this->toAbsolutePath($directory),
1132
                $suffix,
1133
                $prefix,
1134
                $exclude
1135
            );
1136
1137
            $suite->addTestFiles($files);
1138
        }
1139
1140
        foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) {
1141
            /** @var DOMElement $fileNode */
1142
            if (!empty($testSuiteFilter) && !\in_array($fileNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
0 ignored issues
show
Bug introduced by
The method getAttribute() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

1142
            if (!empty($testSuiteFilter) && !\in_array($fileNode->parentNode->/** @scrutinizer ignore-call */ getAttribute('name'), $testSuiteFilter)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
1143
                continue;
1144
            }
1145
1146
            $file = (string) $fileNode->textContent;
1147
1148
            if (empty($file)) {
1149
                continue;
1150
            }
1151
1152
            $file = $fileIteratorFacade->getFilesAsArray(
1153
                $this->toAbsolutePath($file)
1154
            );
1155
1156
            if (!isset($file[0])) {
1157
                continue;
1158
            }
1159
1160
            $file = $file[0];
1161
1162
            if (!$this->satisfiesPhpVersion($fileNode)) {
1163
                continue;
1164
            }
1165
1166
            $suite->addTestFile($file);
1167
        }
1168
1169
        return $suite;
1170
    }
1171
1172
    private function satisfiesPhpVersion(DOMElement $node): bool
1173
    {
1174
        $phpVersion         = \PHP_VERSION;
1175
        $phpVersionOperator = '>=';
1176
1177
        if ($node->hasAttribute('phpVersion')) {
1178
            $phpVersion = (string) $node->getAttribute('phpVersion');
1179
        }
1180
1181
        if ($node->hasAttribute('phpVersionOperator')) {
1182
            $phpVersionOperator = (string) $node->getAttribute('phpVersionOperator');
1183
        }
1184
1185
        return \version_compare(\PHP_VERSION, $phpVersion, $phpVersionOperator);
0 ignored issues
show
Bug Best Practice introduced by
The expression return version_compare(P...n, $phpVersionOperator) could return the type integer which is incompatible with the type-hinted return boolean. Consider adding an additional type-check to rule them out.
Loading history...
1186
    }
1187
1188
    /**
1189
     * if $value is 'false' or 'true', this returns the value that $value represents.
1190
     * Otherwise, returns $default, which may be a string in rare cases.
1191
     * See PHPUnit\Util\ConfigurationTest::testPHPConfigurationIsReadCorrectly
1192
     *
1193
     * @param bool|string $default
1194
     *
1195
     * @return bool|string
1196
     */
1197
    private function getBoolean(string $value, $default)
1198
    {
1199
        if (\strtolower($value) === 'false') {
1200
            return false;
1201
        }
1202
1203
        if (\strtolower($value) === 'true') {
1204
            return true;
1205
        }
1206
1207
        return $default;
1208
    }
1209
1210
    private function getInteger(string $value, int $default): int
1211
    {
1212
        if (\is_numeric($value)) {
1213
            return (int) $value;
1214
        }
1215
1216
        return $default;
1217
    }
1218
1219
    private function readFilterDirectories(string $query): array
1220
    {
1221
        $directories = [];
1222
1223
        foreach ($this->xpath->query($query) as $directoryNode) {
1224
            /** @var DOMElement $directoryNode */
1225
            $directoryPath = (string) $directoryNode->textContent;
1226
1227
            if (!$directoryPath) {
1228
                continue;
1229
            }
1230
1231
            $prefix = '';
1232
            $suffix = '.php';
1233
            $group  = 'DEFAULT';
1234
1235
            if ($directoryNode->hasAttribute('prefix')) {
1236
                $prefix = (string) $directoryNode->getAttribute('prefix');
1237
            }
1238
1239
            if ($directoryNode->hasAttribute('suffix')) {
1240
                $suffix = (string) $directoryNode->getAttribute('suffix');
1241
            }
1242
1243
            if ($directoryNode->hasAttribute('group')) {
1244
                $group = (string) $directoryNode->getAttribute('group');
1245
            }
1246
1247
            $directories[] = [
1248
                'path'   => $this->toAbsolutePath($directoryPath),
1249
                'prefix' => $prefix,
1250
                'suffix' => $suffix,
1251
                'group'  => $group,
1252
            ];
1253
        }
1254
1255
        return $directories;
1256
    }
1257
1258
    /**
1259
     * @return string[]
1260
     */
1261
    private function readFilterFiles(string $query): array
1262
    {
1263
        $files = [];
1264
1265
        foreach ($this->xpath->query($query) as $file) {
1266
            $filePath = (string) $file->textContent;
1267
1268
            if ($filePath) {
1269
                $files[] = $this->toAbsolutePath($filePath);
1270
            }
1271
        }
1272
1273
        return $files;
1274
    }
1275
1276
    private function toAbsolutePath(string $path, bool $useIncludePath = false): string
1277
    {
1278
        $path = \trim($path);
1279
1280
        if ($path[0] === '/') {
1281
            return $path;
1282
        }
1283
1284
        // Matches the following on Windows:
1285
        //  - \\NetworkComputer\Path
1286
        //  - \\.\D:
1287
        //  - \\.\c:
1288
        //  - C:\Windows
1289
        //  - C:\windows
1290
        //  - C:/windows
1291
        //  - c:/windows
1292
        if (\defined('PHP_WINDOWS_VERSION_BUILD') &&
1293
            ($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) {
1294
            return $path;
1295
        }
1296
1297
        if (\strpos($path, '://') !== false) {
1298
            return $path;
1299
        }
1300
1301
        $file = \dirname($this->filename) . \DIRECTORY_SEPARATOR . $path;
1302
1303
        if ($useIncludePath && !\file_exists($file)) {
1304
            $includePathFile = \stream_resolve_include_path($path);
1305
1306
            if ($includePathFile) {
1307
                $file = $includePathFile;
1308
            }
1309
        }
1310
1311
        return $file;
1312
    }
1313
1314
    private function parseGroupConfiguration(string $root): array
1315
    {
1316
        $groups = [
1317
            'include' => [],
1318
            'exclude' => [],
1319
        ];
1320
1321
        foreach ($this->xpath->query($root . '/include/group') as $group) {
1322
            $groups['include'][] = (string) $group->textContent;
1323
        }
1324
1325
        foreach ($this->xpath->query($root . '/exclude/group') as $group) {
1326
            $groups['exclude'][] = (string) $group->textContent;
1327
        }
1328
1329
        return $groups;
1330
    }
1331
}
1332