Completed
Push — master ( 96d573...f9f049 )
by Ehsan
07:54
created

Configuration::getGroupConfiguration()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 File_Iterator_Facade;
15
use PHPUnit\Framework\Exception;
16
use PHPUnit\Framework\TestSuite;
17
use PHPUnit\TextUI\ResultPrinter;
18
19
/**
20
 * Wrapper for the PHPUnit XML configuration file.
21
 *
22
 * Example XML configuration file:
23
 * <code>
24
 * <?xml version="1.0" encoding="utf-8" ?>
25
 *
26
 * <phpunit backupGlobals="false"
27
 *          backupStaticAttributes="false"
28
 *          bootstrap="/path/to/bootstrap.php"
29
 *          cacheTokens="false"
30
 *          columns="80"
31
 *          colors="false"
32
 *          stderr="false"
33
 *          convertErrorsToExceptions="true"
34
 *          convertNoticesToExceptions="true"
35
 *          convertWarningsToExceptions="true"
36
 *          forceCoversAnnotation="false"
37
 *          processIsolation="false"
38
 *          stopOnError="false"
39
 *          stopOnFailure="false"
40
 *          stopOnWarning="false"
41
 *          stopOnIncomplete="false"
42
 *          stopOnRisky="false"
43
 *          stopOnSkipped="false"
44
 *          failOnWarning="false"
45
 *          failOnRisky="false"
46
 *          extensionsDirectory="tools/phpunit.d"
47
 *          printerClass="PHPUnit\TextUI\ResultPrinter"
48
 *          testSuiteLoaderClass="PHPUnit\Runner\StandardTestSuiteLoader"
49
 *          defaultTestSuite=""
50
 *          beStrictAboutChangesToGlobalState="false"
51
 *          beStrictAboutCoversAnnotation="false"
52
 *          beStrictAboutOutputDuringTests="false"
53
 *          beStrictAboutResourceUsageDuringSmallTests="false"
54
 *          beStrictAboutTestsThatDoNotTestAnything="false"
55
 *          beStrictAboutTodoAnnotatedTests="false"
56
 *          enforceTimeLimit="false"
57
 *          ignoreDeprecatedCodeUnitsFromCodeCoverage="false"
58
 *          timeoutForSmallTests="1"
59
 *          timeoutForMediumTests="10"
60
 *          timeoutForLargeTests="60"
61
 *          verbose="false"
62
 *          reverseDefectList="false"
63
 *          registerMockObjectsFromTestArgumentsRecursively="false">
64
 *   <testsuites>
65
 *     <testsuite name="My Test Suite">
66
 *       <directory suffix="Test.php" phpVersion="5.3.0" phpVersionOperator=">=">/path/to/files</directory>
67
 *       <file phpVersion="5.3.0" phpVersionOperator=">=">/path/to/MyTest.php</file>
68
 *       <exclude>/path/to/files/exclude</exclude>
69
 *     </testsuite>
70
 *   </testsuites>
71
 *
72
 *   <groups>
73
 *     <include>
74
 *       <group>name</group>
75
 *     </include>
76
 *     <exclude>
77
 *       <group>name</group>
78
 *     </exclude>
79
 *   </groups>
80
 *
81
 *   <testdoxGroups>
82
 *     <include>
83
 *       <group>name</group>
84
 *     </include>
85
 *     <exclude>
86
 *       <group>name</group>
87
 *     </exclude>
88
 *   </testdoxGroups>
89
 *
90
 *   <filter>
91
 *     <whitelist addUncoveredFilesFromWhitelist="true"
92
 *                processUncoveredFilesFromWhitelist="false">
93
 *       <directory suffix=".php">/path/to/files</directory>
94
 *       <file>/path/to/file</file>
95
 *       <exclude>
96
 *         <directory suffix=".php">/path/to/files</directory>
97
 *         <file>/path/to/file</file>
98
 *       </exclude>
99
 *     </whitelist>
100
 *   </filter>
101
 *
102
 *   <listeners>
103
 *     <listener class="MyListener" file="/optional/path/to/MyListener.php">
104
 *       <arguments>
105
 *         <array>
106
 *           <element key="0">
107
 *             <string>Sebastian</string>
108
 *           </element>
109
 *         </array>
110
 *         <integer>22</integer>
111
 *         <string>April</string>
112
 *         <double>19.78</double>
113
 *         <null/>
114
 *         <object class="stdClass"/>
115
 *         <file>MyRelativeFile.php</file>
116
 *         <directory>MyRelativeDir</directory>
117
 *       </arguments>
118
 *     </listener>
119
 *   </listeners>
120
 *
121
 *   <logging>
122
 *     <log type="coverage-html" target="/tmp/report" lowUpperBound="50" highLowerBound="90"/>
123
 *     <log type="coverage-clover" target="/tmp/clover.xml"/>
124
 *     <log type="coverage-crap4j" target="/tmp/crap.xml" threshold="30"/>
125
 *     <log type="json" target="/tmp/logfile.json"/>
126
 *     <log type="plain" target="/tmp/logfile.txt"/>
127
 *     <log type="teamcity" target="/tmp/logfile.txt"/>
128
 *     <log type="junit" target="/tmp/logfile.xml"/>
129
 *     <log type="testdox-html" target="/tmp/testdox.html"/>
130
 *     <log type="testdox-text" target="/tmp/testdox.txt"/>
131
 *     <log type="testdox-xml" target="/tmp/testdox.xml"/>
132
 *   </logging>
133
 *
134
 *   <php>
135
 *     <includePath>.</includePath>
136
 *     <ini name="foo" value="bar"/>
137
 *     <const name="foo" value="bar"/>
138
 *     <var name="foo" value="bar"/>
139
 *     <env name="foo" value="bar"/>
140
 *     <post name="foo" value="bar"/>
141
 *     <get name="foo" value="bar"/>
142
 *     <cookie name="foo" value="bar"/>
143
 *     <server name="foo" value="bar"/>
144
 *     <files name="foo" value="bar"/>
145
 *     <request name="foo" value="bar"/>
146
 *   </php>
147
 * </phpunit>
148
 * </code>
149
 */
150
class Configuration
151
{
152
    const TEST_SUITE_FILTER_SEPARATOR = ',';
153
154
    private static $instances = [];
155
156
    protected $document;
157
    protected $xpath;
158
    protected $filename;
159
160
    /**
161
     * Loads a PHPUnit configuration file.
162
     *
163
     * @param string $filename
164
     */
165
    protected function __construct($filename)
166
    {
167
        $this->filename = $filename;
168
        $this->document = Xml::loadFile($filename, false, true, true);
169
        $this->xpath    = new DOMXPath($this->document);
170
    }
171
172
    final private function __clone()
173
    {
174
    }
175
176
    /**
177
     * Returns a PHPUnit configuration object.
178
     *
179
     * @param string $filename
180
     *
181
     * @return Configuration
182
     */
183
    public static function getInstance($filename)
184
    {
185
        $realpath = \realpath($filename);
186
187
        if ($realpath === false) {
188
            throw new Exception(
189
                \sprintf(
190
                    'Could not read "%s".',
191
                    $filename
192
                )
193
            );
194
        }
195
196
        if (!isset(self::$instances[$realpath])) {
197
            self::$instances[$realpath] = new self($realpath);
198
        }
199
200
        return self::$instances[$realpath];
201
    }
202
203
    /**
204
     * Returns the realpath to the configuration file.
205
     *
206
     * @return string
207
     */
208
    public function getFilename()
209
    {
210
        return $this->filename;
211
    }
212
213
    /**
214
     * Returns the configuration for SUT filtering.
215
     *
216
     * @return array
217
     */
218
    public function getFilterConfiguration()
219
    {
220
        $addUncoveredFilesFromWhitelist     = true;
221
        $processUncoveredFilesFromWhitelist = false;
222
223
        $tmp = $this->xpath->query('filter/whitelist');
224
225
        if ($tmp->length == 0) {
226
            return [
227
                'whitelist' => []
228
            ];
229
        }
230
231
        if ($tmp->length == 1) {
232
            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()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
233
                $addUncoveredFilesFromWhitelist = $this->getBoolean(
234
                    (string) $tmp->item(0)->getAttribute(
235
                        'addUncoveredFilesFromWhitelist'
236
                    ),
237
                    true
238
                );
239
            }
240
241
            if ($tmp->item(0)->hasAttribute('processUncoveredFilesFromWhitelist')) {
0 ignored issues
show
Bug introduced by
The method hasAttribute() does not exist on DOMNode. Did you maybe mean hasAttributes()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
242
                $processUncoveredFilesFromWhitelist = $this->getBoolean(
243
                    (string) $tmp->item(0)->getAttribute(
244
                        'processUncoveredFilesFromWhitelist'
245
                    ),
246
                    false
247
                );
248
            }
249
        }
250
251
        return [
252
            'whitelist' => [
253
                'addUncoveredFilesFromWhitelist'     => $addUncoveredFilesFromWhitelist,
254
                'processUncoveredFilesFromWhitelist' => $processUncoveredFilesFromWhitelist,
255
                'include'                            => [
256
                    'directory' => $this->readFilterDirectories(
257
                        'filter/whitelist/directory'
258
                    ),
259
                    'file' => $this->readFilterFiles(
260
                        'filter/whitelist/file'
261
                    )
262
                ],
263
                'exclude' => [
264
                    'directory' => $this->readFilterDirectories(
265
                        'filter/whitelist/exclude/directory'
266
                    ),
267
                    'file' => $this->readFilterFiles(
268
                        'filter/whitelist/exclude/file'
269
                    )
270
                ]
271
            ]
272
        ];
273
    }
274
275
    /**
276
     * Returns the configuration for groups.
277
     *
278
     * @return array
279
     */
280
    public function getGroupConfiguration()
281
    {
282
        return $this->parseGroupConfiguration('groups');
283
    }
284
285
    /**
286
     * Returns the configuration for testdox groups.
287
     *
288
     * @return array
289
     */
290
    public function getTestdoxGroupConfiguration()
291
    {
292
        return $this->parseGroupConfiguration('testdoxGroups');
293
    }
294
295
    /**
296
     * @param string $root
297
     *
298
     * @return array
299
     */
300
    private function parseGroupConfiguration($root)
301
    {
302
        $groups = [
303
            'include' => [],
304
            'exclude' => []
305
        ];
306
307
        foreach ($this->xpath->query($root . '/include/group') as $group) {
308
            $groups['include'][] = (string) $group->textContent;
309
        }
310
311
        foreach ($this->xpath->query($root . '/exclude/group') as $group) {
312
            $groups['exclude'][] = (string) $group->textContent;
313
        }
314
315
        return $groups;
316
    }
317
318
    /**
319
     * Returns the configuration for listeners.
320
     *
321
     * @return array
322
     */
323
    public function getListenerConfiguration()
324
    {
325
        $result = [];
326
327
        foreach ($this->xpath->query('listeners/listener') as $listener) {
328
            $class     = (string) $listener->getAttribute('class');
329
            $file      = '';
330
            $arguments = [];
331
332
            if ($listener->getAttribute('file')) {
333
                $file = $this->toAbsolutePath(
334
                    (string) $listener->getAttribute('file'),
335
                    true
336
                );
337
            }
338
339
            foreach ($listener->childNodes as $node) {
340
                if ($node instanceof DOMElement && $node->tagName == 'arguments') {
341
                    foreach ($node->childNodes as $argument) {
342
                        if ($argument instanceof DOMElement) {
343
                            if ($argument->tagName == 'file' || $argument->tagName == 'directory') {
344
                                $arguments[] = $this->toAbsolutePath((string) $argument->textContent);
345
                            } else {
346
                                $arguments[] = Xml::xmlToVariable($argument);
347
                            }
348
                        }
349
                    }
350
                }
351
            }
352
353
            $result[] = [
354
                'class'     => $class,
355
                'file'      => $file,
356
                'arguments' => $arguments
357
            ];
358
        }
359
360
        return $result;
361
    }
362
363
    /**
364
     * Returns the logging configuration.
365
     *
366
     * @return array
367
     */
368
    public function getLoggingConfiguration()
369
    {
370
        $result = [];
371
372
        foreach ($this->xpath->query('logging/log') as $log) {
373
            $type   = (string) $log->getAttribute('type');
374
            $target = (string) $log->getAttribute('target');
375
376
            if (!$target) {
377
                continue;
378
            }
379
380
            $target = $this->toAbsolutePath($target);
381
382
            if ($type == 'coverage-html') {
383
                if ($log->hasAttribute('lowUpperBound')) {
384
                    $result['lowUpperBound'] = $this->getInteger(
385
                        (string) $log->getAttribute('lowUpperBound'),
386
                        50
0 ignored issues
show
Documentation introduced by
50 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
387
                    );
388
                }
389
390
                if ($log->hasAttribute('highLowerBound')) {
391
                    $result['highLowerBound'] = $this->getInteger(
392
                        (string) $log->getAttribute('highLowerBound'),
393
                        90
0 ignored issues
show
Documentation introduced by
90 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
394
                    );
395
                }
396
            } elseif ($type == 'coverage-crap4j') {
397
                if ($log->hasAttribute('threshold')) {
398
                    $result['crap4jThreshold'] = $this->getInteger(
399
                        (string) $log->getAttribute('threshold'),
400
                        30
0 ignored issues
show
Documentation introduced by
30 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
401
                    );
402
                }
403
            } elseif ($type == 'coverage-text') {
404
                if ($log->hasAttribute('showUncoveredFiles')) {
405
                    $result['coverageTextShowUncoveredFiles'] = $this->getBoolean(
406
                        (string) $log->getAttribute('showUncoveredFiles'),
407
                        false
408
                    );
409
                }
410
                if ($log->hasAttribute('showOnlySummary')) {
411
                    $result['coverageTextShowOnlySummary'] = $this->getBoolean(
412
                        (string) $log->getAttribute('showOnlySummary'),
413
                        false
414
                    );
415
                }
416
            }
417
418
            $result[$type] = $target;
419
        }
420
421
        return $result;
422
    }
423
424
    /**
425
     * Returns the PHP configuration.
426
     *
427
     * @return array
428
     */
429
    public function getPHPConfiguration()
430
    {
431
        $result = [
432
            'include_path' => [],
433
            'ini'          => [],
434
            'const'        => [],
435
            'var'          => [],
436
            'env'          => [],
437
            'post'         => [],
438
            'get'          => [],
439
            'cookie'       => [],
440
            'server'       => [],
441
            'files'        => [],
442
            'request'      => []
443
        ];
444
445
        foreach ($this->xpath->query('php/includePath') as $includePath) {
446
            $path = (string) $includePath->textContent;
447
448
            if ($path) {
449
                $result['include_path'][] = $this->toAbsolutePath($path);
450
            }
451
        }
452
453
        foreach ($this->xpath->query('php/ini') as $ini) {
454
            $name  = (string) $ini->getAttribute('name');
455
            $value = (string) $ini->getAttribute('value');
456
457
            $result['ini'][$name] = $value;
458
        }
459
460
        foreach ($this->xpath->query('php/const') as $const) {
461
            $name  = (string) $const->getAttribute('name');
462
            $value = (string) $const->getAttribute('value');
463
464
            $result['const'][$name] = $this->getBoolean($value, $value);
0 ignored issues
show
Documentation introduced by
$value is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
465
        }
466
467
        foreach (['var', 'env', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
468
            foreach ($this->xpath->query('php/' . $array) as $var) {
469
                $name     = (string) $var->getAttribute('name');
470
                $value    = (string) $var->getAttribute('value');
471
                $verbatim = false;
472
473
                if ($var->hasAttribute('verbatim')) {
474
                    $verbatim = $this->getBoolean($var->getAttribute('verbatim'), false);
475
                }
476
477
                if (!$verbatim) {
478
                    $value = $this->getBoolean($value, $value);
0 ignored issues
show
Documentation introduced by
$value is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
479
                }
480
481
                $result[$array][$name] = $value;
482
            }
483
        }
484
485
        return $result;
486
    }
487
488
    /**
489
     * Handles the PHP configuration.
490
     */
491
    public function handlePHPConfiguration()
492
    {
493
        $configuration = $this->getPHPConfiguration();
494
495
        if (!empty($configuration['include_path'])) {
496
            \ini_set(
497
                'include_path',
498
                \implode(PATH_SEPARATOR, $configuration['include_path']) .
499
                PATH_SEPARATOR .
500
                \ini_get('include_path')
501
            );
502
        }
503
504
        foreach ($configuration['ini'] as $name => $value) {
505
            if (\defined($value)) {
506
                $value = \constant($value);
507
            }
508
509
            \ini_set($name, $value);
510
        }
511
512
        foreach ($configuration['const'] as $name => $value) {
513
            if (!\defined($name)) {
514
                \define($name, $value);
515
            }
516
        }
517
518
        foreach (['var', 'post', 'get', 'cookie', 'server', 'files', 'request'] as $array) {
519
            // See https://github.com/sebastianbergmann/phpunit/issues/277
520
            switch ($array) {
521
                case 'var':
522
                    $target = &$GLOBALS;
523
                    break;
524
525
                case 'server':
526
                    $target = &$_SERVER;
527
                    break;
528
529
                default:
530
                    $target = &$GLOBALS['_' . \strtoupper($array)];
531
                    break;
532
            }
533
534
            foreach ($configuration[$array] as $name => $value) {
535
                $target[$name] = $value;
536
            }
537
        }
538
539
        foreach ($configuration['env'] as $name => $value) {
540
            if (false === \getenv($name)) {
541
                \putenv("{$name}={$value}");
542
            }
543
            if (!isset($_ENV[$name])) {
544
                $_ENV[$name] = $value;
545
            }
546
        }
547
    }
548
549
    /**
550
     * Returns the PHPUnit configuration.
551
     *
552
     * @return array
553
     */
554
    public function getPHPUnitConfiguration()
555
    {
556
        $result = [];
557
        $root   = $this->document->documentElement;
558
559
        if ($root->hasAttribute('cacheTokens')) {
560
            $result['cacheTokens'] = $this->getBoolean(
561
                (string) $root->getAttribute('cacheTokens'),
562
                false
563
            );
564
        }
565
566
        if ($root->hasAttribute('columns')) {
567
            $columns = (string) $root->getAttribute('columns');
568
569
            if ($columns == 'max') {
570
                $result['columns'] = 'max';
571
            } else {
572
                $result['columns'] = $this->getInteger($columns, 80);
0 ignored issues
show
Documentation introduced by
80 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
573
            }
574
        }
575
576
        if ($root->hasAttribute('colors')) {
577
            /* only allow boolean for compatibility with previous versions
578
              'always' only allowed from command line */
579
            if ($this->getBoolean($root->getAttribute('colors'), false)) {
580
                $result['colors'] = ResultPrinter::COLOR_AUTO;
581
            } else {
582
                $result['colors'] = ResultPrinter::COLOR_NEVER;
583
            }
584
        }
585
586
        /*
587
         * Issue #657
588
         */
589
        if ($root->hasAttribute('stderr')) {
590
            $result['stderr'] = $this->getBoolean(
591
                (string) $root->getAttribute('stderr'),
592
                false
593
            );
594
        }
595
596
        if ($root->hasAttribute('backupGlobals')) {
597
            $result['backupGlobals'] = $this->getBoolean(
598
                (string) $root->getAttribute('backupGlobals'),
599
                false
600
            );
601
        }
602
603
        if ($root->hasAttribute('backupStaticAttributes')) {
604
            $result['backupStaticAttributes'] = $this->getBoolean(
605
                (string) $root->getAttribute('backupStaticAttributes'),
606
                false
607
            );
608
        }
609
610
        if ($root->getAttribute('bootstrap')) {
611
            $result['bootstrap'] = $this->toAbsolutePath(
612
                (string) $root->getAttribute('bootstrap')
613
            );
614
        }
615
616
        if ($root->hasAttribute('convertErrorsToExceptions')) {
617
            $result['convertErrorsToExceptions'] = $this->getBoolean(
618
                (string) $root->getAttribute('convertErrorsToExceptions'),
619
                true
620
            );
621
        }
622
623
        if ($root->hasAttribute('convertNoticesToExceptions')) {
624
            $result['convertNoticesToExceptions'] = $this->getBoolean(
625
                (string) $root->getAttribute('convertNoticesToExceptions'),
626
                true
627
            );
628
        }
629
630
        if ($root->hasAttribute('convertWarningsToExceptions')) {
631
            $result['convertWarningsToExceptions'] = $this->getBoolean(
632
                (string) $root->getAttribute('convertWarningsToExceptions'),
633
                true
634
            );
635
        }
636
637
        if ($root->hasAttribute('forceCoversAnnotation')) {
638
            $result['forceCoversAnnotation'] = $this->getBoolean(
639
                (string) $root->getAttribute('forceCoversAnnotation'),
640
                false
641
            );
642
        }
643
644
        if ($root->hasAttribute('disableCodeCoverageIgnore')) {
645
            $result['disableCodeCoverageIgnore'] = $this->getBoolean(
646
                (string) $root->getAttribute('disableCodeCoverageIgnore'),
647
                false
648
            );
649
        }
650
651
        if ($root->hasAttribute('processIsolation')) {
652
            $result['processIsolation'] = $this->getBoolean(
653
                (string) $root->getAttribute('processIsolation'),
654
                false
655
            );
656
        }
657
658
        if ($root->hasAttribute('stopOnError')) {
659
            $result['stopOnError'] = $this->getBoolean(
660
                (string) $root->getAttribute('stopOnError'),
661
                false
662
            );
663
        }
664
665
        if ($root->hasAttribute('stopOnFailure')) {
666
            $result['stopOnFailure'] = $this->getBoolean(
667
                (string) $root->getAttribute('stopOnFailure'),
668
                false
669
            );
670
        }
671
672
        if ($root->hasAttribute('stopOnWarning')) {
673
            $result['stopOnWarning'] = $this->getBoolean(
674
                (string) $root->getAttribute('stopOnWarning'),
675
                false
676
            );
677
        }
678
679
        if ($root->hasAttribute('stopOnIncomplete')) {
680
            $result['stopOnIncomplete'] = $this->getBoolean(
681
                (string) $root->getAttribute('stopOnIncomplete'),
682
                false
683
            );
684
        }
685
686
        if ($root->hasAttribute('stopOnRisky')) {
687
            $result['stopOnRisky'] = $this->getBoolean(
688
                (string) $root->getAttribute('stopOnRisky'),
689
                false
690
            );
691
        }
692
693
        if ($root->hasAttribute('stopOnSkipped')) {
694
            $result['stopOnSkipped'] = $this->getBoolean(
695
                (string) $root->getAttribute('stopOnSkipped'),
696
                false
697
            );
698
        }
699
700
        if ($root->hasAttribute('failOnWarning')) {
701
            $result['failOnWarning'] = $this->getBoolean(
702
                (string) $root->getAttribute('failOnWarning'),
703
                false
704
            );
705
        }
706
707
        if ($root->hasAttribute('failOnRisky')) {
708
            $result['failOnRisky'] = $this->getBoolean(
709
                (string) $root->getAttribute('failOnRisky'),
710
                false
711
            );
712
        }
713
714
        if ($root->hasAttribute('testSuiteLoaderClass')) {
715
            $result['testSuiteLoaderClass'] = (string) $root->getAttribute(
716
                'testSuiteLoaderClass'
717
            );
718
        }
719
720
        if ($root->hasAttribute('defaultTestSuite')) {
721
            $result['defaultTestSuite'] = (string) $root->getAttribute(
722
                'defaultTestSuite'
723
            );
724
        }
725
726
        if ($root->getAttribute('testSuiteLoaderFile')) {
727
            $result['testSuiteLoaderFile'] = $this->toAbsolutePath(
728
                (string) $root->getAttribute('testSuiteLoaderFile')
729
            );
730
        }
731
732
        if ($root->hasAttribute('printerClass')) {
733
            $result['printerClass'] = (string) $root->getAttribute(
734
                'printerClass'
735
            );
736
        }
737
738
        if ($root->getAttribute('printerFile')) {
739
            $result['printerFile'] = $this->toAbsolutePath(
740
                (string) $root->getAttribute('printerFile')
741
            );
742
        }
743
744
        if ($root->hasAttribute('beStrictAboutChangesToGlobalState')) {
745
            $result['beStrictAboutChangesToGlobalState'] = $this->getBoolean(
746
                (string) $root->getAttribute('beStrictAboutChangesToGlobalState'),
747
                false
748
            );
749
        }
750
751
        if ($root->hasAttribute('beStrictAboutOutputDuringTests')) {
752
            $result['disallowTestOutput'] = $this->getBoolean(
753
                (string) $root->getAttribute('beStrictAboutOutputDuringTests'),
754
                false
755
            );
756
        }
757
758
        if ($root->hasAttribute('beStrictAboutResourceUsageDuringSmallTests')) {
759
            $result['beStrictAboutResourceUsageDuringSmallTests'] = $this->getBoolean(
760
                (string) $root->getAttribute('beStrictAboutResourceUsageDuringSmallTests'),
761
                false
762
            );
763
        }
764
765
        if ($root->hasAttribute('beStrictAboutTestsThatDoNotTestAnything')) {
766
            $result['reportUselessTests'] = $this->getBoolean(
767
                (string) $root->getAttribute('beStrictAboutTestsThatDoNotTestAnything'),
768
                true
769
            );
770
        }
771
772
        if ($root->hasAttribute('beStrictAboutTodoAnnotatedTests')) {
773
            $result['disallowTodoAnnotatedTests'] = $this->getBoolean(
774
                (string) $root->getAttribute('beStrictAboutTodoAnnotatedTests'),
775
                false
776
            );
777
        }
778
779
        if ($root->hasAttribute('beStrictAboutCoversAnnotation')) {
780
            $result['strictCoverage'] = $this->getBoolean(
781
                (string) $root->getAttribute('beStrictAboutCoversAnnotation'),
782
                false
783
            );
784
        }
785
786
        if ($root->hasAttribute('enforceTimeLimit')) {
787
            $result['enforceTimeLimit'] = $this->getBoolean(
788
                (string) $root->getAttribute('enforceTimeLimit'),
789
                false
790
            );
791
        }
792
793
        if ($root->hasAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage')) {
794
            $result['ignoreDeprecatedCodeUnitsFromCodeCoverage'] = $this->getBoolean(
795
                (string) $root->getAttribute('ignoreDeprecatedCodeUnitsFromCodeCoverage'),
796
                false
797
            );
798
        }
799
800
        if ($root->hasAttribute('timeoutForSmallTests')) {
801
            $result['timeoutForSmallTests'] = $this->getInteger(
802
                (string) $root->getAttribute('timeoutForSmallTests'),
803
                1
0 ignored issues
show
Documentation introduced by
1 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
804
            );
805
        }
806
807
        if ($root->hasAttribute('timeoutForMediumTests')) {
808
            $result['timeoutForMediumTests'] = $this->getInteger(
809
                (string) $root->getAttribute('timeoutForMediumTests'),
810
                10
0 ignored issues
show
Documentation introduced by
10 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
811
            );
812
        }
813
814
        if ($root->hasAttribute('timeoutForLargeTests')) {
815
            $result['timeoutForLargeTests'] = $this->getInteger(
816
                (string) $root->getAttribute('timeoutForLargeTests'),
817
                60
0 ignored issues
show
Documentation introduced by
60 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
818
            );
819
        }
820
821
        if ($root->hasAttribute('reverseDefectList')) {
822
            $result['reverseDefectList'] = $this->getBoolean(
823
                (string) $root->getAttribute('reverseDefectList'),
824
                false
825
            );
826
        }
827
828
        if ($root->hasAttribute('verbose')) {
829
            $result['verbose'] = $this->getBoolean(
830
                (string) $root->getAttribute('verbose'),
831
                false
832
            );
833
        }
834
835
        if ($root->hasAttribute('registerMockObjectsFromTestArgumentsRecursively')) {
836
            $result['registerMockObjectsFromTestArgumentsRecursively'] = $this->getBoolean(
837
                (string) $root->getAttribute('registerMockObjectsFromTestArgumentsRecursively'),
838
                false
839
            );
840
        }
841
842
        if ($root->hasAttribute('extensionsDirectory')) {
843
            $result['extensionsDirectory'] = $this->toAbsolutePath(
844
                (string) $root->getAttribute(
845
                    'extensionsDirectory'
846
                )
847
            );
848
        }
849
850
        return $result;
851
    }
852
853
    /**
854
     * Returns the test suite configuration.
855
     *
856
     * @return TestSuite
857
     */
858
    public function getTestSuiteConfiguration($testSuiteFilter = null)
859
    {
860
        $testSuiteNodes = $this->xpath->query('testsuites/testsuite');
861
862
        if ($testSuiteNodes->length == 0) {
863
            $testSuiteNodes = $this->xpath->query('testsuite');
864
        }
865
866
        if ($testSuiteNodes->length == 1) {
867
            return $this->getTestSuite($testSuiteNodes->item(0), $testSuiteFilter);
0 ignored issues
show
Compatibility introduced by
$testSuiteNodes->item(0) of type object<DOMNode> is not a sub-type of object<DOMElement>. It seems like you assume a child class of the class DOMNode to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
868
        }
869
870
        if ($testSuiteNodes->length > 1) {
871
            $suite = new TestSuite;
872
873
            foreach ($testSuiteNodes as $testSuiteNode) {
874
                $suite->addTestSuite(
875
                    $this->getTestSuite($testSuiteNode, $testSuiteFilter)
876
                );
877
            }
878
879
            return $suite;
880
        }
881
    }
882
883
    /**
884
     * Returns the test suite names from the configuration.
885
     *
886
     * @return array
887
     */
888
    public function getTestSuiteNames()
889
    {
890
        $names = [];
891
        $nodes = $this->xpath->query('*/testsuite');
892
        foreach ($nodes as $node) {
893
            $names[] = $node->getAttribute('name');
894
        }
895
896
        return $names;
897
    }
898
899
    /**
900
     * @param DOMElement $testSuiteNode
901
     *
902
     * @return TestSuite
903
     */
904
    protected function getTestSuite(DOMElement $testSuiteNode, $testSuiteFilter = null)
905
    {
906
        if ($testSuiteNode->hasAttribute('name')) {
907
            $suite = new TestSuite(
908
                (string) $testSuiteNode->getAttribute('name')
909
            );
910
        } else {
911
            $suite = new TestSuite;
912
        }
913
914
        $exclude = [];
915
916
        foreach ($testSuiteNode->getElementsByTagName('exclude') as $excludeNode) {
917
            $excludeFile = (string) $excludeNode->textContent;
918
            if ($excludeFile) {
919
                $exclude[] = $this->toAbsolutePath($excludeFile);
920
            }
921
        }
922
923
        $fileIteratorFacade = new File_Iterator_Facade;
924
        $testSuiteFilter    = $testSuiteFilter ? \explode(self::TEST_SUITE_FILTER_SEPARATOR, $testSuiteFilter) : [];
925
926
        foreach ($testSuiteNode->getElementsByTagName('directory') as $directoryNode) {
927
            if (!empty($testSuiteFilter) && !\in_array($directoryNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
928
                continue;
929
            }
930
931
            $directory = (string) $directoryNode->textContent;
932
933
            if (empty($directory)) {
934
                continue;
935
            }
936
937
            if ($directoryNode->hasAttribute('phpVersion')) {
938
                $phpVersion = (string) $directoryNode->getAttribute('phpVersion');
939
            } else {
940
                $phpVersion = PHP_VERSION;
941
            }
942
943
            if ($directoryNode->hasAttribute('phpVersionOperator')) {
944
                $phpVersionOperator = (string) $directoryNode->getAttribute('phpVersionOperator');
945
            } else {
946
                $phpVersionOperator = '>=';
947
            }
948
949
            if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
950
                continue;
951
            }
952
953
            if ($directoryNode->hasAttribute('prefix')) {
954
                $prefix = (string) $directoryNode->getAttribute('prefix');
955
            } else {
956
                $prefix = '';
957
            }
958
959
            if ($directoryNode->hasAttribute('suffix')) {
960
                $suffix = (string) $directoryNode->getAttribute('suffix');
961
            } else {
962
                $suffix = 'Test.php';
963
            }
964
965
            $files = $fileIteratorFacade->getFilesAsArray(
966
                $this->toAbsolutePath($directory),
967
                $suffix,
968
                $prefix,
969
                $exclude
970
            );
971
            $suite->addTestFiles($files);
972
        }
973
974
        foreach ($testSuiteNode->getElementsByTagName('file') as $fileNode) {
975
            if (!empty($testSuiteFilter) && !\in_array($fileNode->parentNode->getAttribute('name'), $testSuiteFilter)) {
976
                continue;
977
            }
978
979
            $file = (string) $fileNode->textContent;
980
981
            if (empty($file)) {
982
                continue;
983
            }
984
985
            // Get the absolute path to the file
986
            $file = $fileIteratorFacade->getFilesAsArray(
987
                $this->toAbsolutePath($file)
988
            );
989
990
            if (!isset($file[0])) {
991
                continue;
992
            }
993
994
            $file = $file[0];
995
996
            if ($fileNode->hasAttribute('phpVersion')) {
997
                $phpVersion = (string) $fileNode->getAttribute('phpVersion');
998
            } else {
999
                $phpVersion = PHP_VERSION;
1000
            }
1001
1002
            if ($fileNode->hasAttribute('phpVersionOperator')) {
1003
                $phpVersionOperator = (string) $fileNode->getAttribute('phpVersionOperator');
1004
            } else {
1005
                $phpVersionOperator = '>=';
1006
            }
1007
1008
            if (!\version_compare(PHP_VERSION, $phpVersion, $phpVersionOperator)) {
1009
                continue;
1010
            }
1011
1012
            $suite->addTestFile($file);
1013
        }
1014
1015
        return $suite;
1016
    }
1017
1018
    /**
1019
     * @param string $value
1020
     * @param bool   $default
1021
     *
1022
     * @return bool
1023
     */
1024
    protected function getBoolean($value, $default)
1025
    {
1026
        if (\strtolower($value) == 'false') {
1027
            return false;
1028
        }
1029
1030
        if (\strtolower($value) == 'true') {
1031
            return true;
1032
        }
1033
1034
        return $default;
1035
    }
1036
1037
    /**
1038
     * @param string $value
1039
     * @param bool   $default
1040
     *
1041
     * @return bool
1042
     */
1043
    protected function getInteger($value, $default)
1044
    {
1045
        if (\is_numeric($value)) {
1046
            return (int) $value;
1047
        }
1048
1049
        return $default;
1050
    }
1051
1052
    /**
1053
     * @param string $query
1054
     *
1055
     * @return array
1056
     */
1057
    protected function readFilterDirectories($query)
1058
    {
1059
        $directories = [];
1060
1061
        foreach ($this->xpath->query($query) as $directory) {
1062
            $directoryPath = (string) $directory->textContent;
1063
1064
            if (!$directoryPath) {
1065
                continue;
1066
            }
1067
1068
            if ($directory->hasAttribute('prefix')) {
1069
                $prefix = (string) $directory->getAttribute('prefix');
1070
            } else {
1071
                $prefix = '';
1072
            }
1073
1074
            if ($directory->hasAttribute('suffix')) {
1075
                $suffix = (string) $directory->getAttribute('suffix');
1076
            } else {
1077
                $suffix = '.php';
1078
            }
1079
1080
            if ($directory->hasAttribute('group')) {
1081
                $group = (string) $directory->getAttribute('group');
1082
            } else {
1083
                $group = 'DEFAULT';
1084
            }
1085
1086
            $directories[] = [
1087
                'path'   => $this->toAbsolutePath($directoryPath),
1088
                'prefix' => $prefix,
1089
                'suffix' => $suffix,
1090
                'group'  => $group
1091
            ];
1092
        }
1093
1094
        return $directories;
1095
    }
1096
1097
    /**
1098
     * @param string $query
1099
     *
1100
     * @return array
1101
     */
1102
    protected function readFilterFiles($query)
1103
    {
1104
        $files = [];
1105
1106
        foreach ($this->xpath->query($query) as $file) {
1107
            $filePath = (string) $file->textContent;
1108
1109
            if ($filePath) {
1110
                $files[] = $this->toAbsolutePath($filePath);
1111
            }
1112
        }
1113
1114
        return $files;
1115
    }
1116
1117
    /**
1118
     * @param string $path
1119
     * @param bool   $useIncludePath
1120
     *
1121
     * @return string
1122
     */
1123
    protected function toAbsolutePath($path, $useIncludePath = false)
1124
    {
1125
        $path = \trim($path);
1126
1127
        if ($path[0] === '/') {
1128
            return $path;
1129
        }
1130
1131
        // Matches the following on Windows:
1132
        //  - \\NetworkComputer\Path
1133
        //  - \\.\D:
1134
        //  - \\.\c:
1135
        //  - C:\Windows
1136
        //  - C:\windows
1137
        //  - C:/windows
1138
        //  - c:/windows
1139
        if (\defined('PHP_WINDOWS_VERSION_BUILD') &&
1140
            ($path[0] === '\\' || (\strlen($path) >= 3 && \preg_match('#^[A-Z]\:[/\\\]#i', \substr($path, 0, 3))))) {
1141
            return $path;
1142
        }
1143
1144
        // Stream
1145
        if (\strpos($path, '://') !== false) {
1146
            return $path;
1147
        }
1148
1149
        $file = \dirname($this->filename) . DIRECTORY_SEPARATOR . $path;
1150
1151
        if ($useIncludePath && !\file_exists($file)) {
1152
            $includePathFile = \stream_resolve_include_path($path);
1153
1154
            if ($includePathFile) {
1155
                $file = $includePathFile;
1156
            }
1157
        }
1158
1159
        return $file;
1160
    }
1161
}
1162