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

Command::handleExtensions()   B

Complexity

Conditions 6
Paths 12

Size

Total Lines 38
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
nc 12
nop 1
dl 0
loc 38
rs 8.439
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
11
namespace PHPUnit\TextUI;
12
13
use File_Iterator_Facade;
14
use PharIo\Manifest\ApplicationName;
15
use PharIo\Manifest\ManifestLoader;
16
use PharIo\Version\Version as PharIoVersion;
17
use PharIo\Manifest\Exception as ManifestException;
18
use PHPUnit\Framework\Exception;
19
use PHPUnit\Framework\TestSuite;
20
use PHPUnit\Framework\Test;
21
use PHPUnit\Framework\TestListener;
22
use PHPUnit\Runner\PhptTestCase;
23
use PHPUnit\Runner\StandardTestSuiteLoader;
24
use PHPUnit\Runner\Version;
25
use PHPUnit\Runner\TestSuiteLoader;
26
use PHPUnit\Util\Configuration;
27
use PHPUnit\Util\ConfigurationGenerator;
28
use PHPUnit\Util\Fileloader;
29
use PHPUnit\Util\Filesystem;
30
use PHPUnit\Util\Getopt;
31
use PHPUnit\Util\Log\TeamCity;
32
use PHPUnit\Util\TestDox\TextResultPrinter;
33
use PHPUnit\Util\Printer;
34
use ReflectionClass;
35
use Throwable;
36
37
/**
38
 * A TestRunner for the Command Line Interface (CLI)
39
 * PHP SAPI Module.
40
 */
41
class Command
42
{
43
    /**
44
     * @var array
45
     */
46
    protected $arguments = [
47
        'listGroups'              => false,
48
        'listSuites'              => false,
49
        'loader'                  => null,
50
        'useDefaultConfiguration' => true,
51
        'loadedExtensions'        => [],
52
        'notLoadedExtensions'     => []
53
    ];
54
55
    /**
56
     * @var array
57
     */
58
    protected $options = [];
59
60
    /**
61
     * @var array
62
     */
63
    protected $longOptions = [
64
        'atleast-version='          => null,
65
        'bootstrap='                => null,
66
        'check-version'             => null,
67
        'colors=='                  => null,
68
        'columns='                  => null,
69
        'configuration='            => null,
70
        'coverage-clover='          => null,
71
        'coverage-crap4j='          => null,
72
        'coverage-html='            => null,
73
        'coverage-php='             => null,
74
        'coverage-text=='           => null,
75
        'coverage-xml='             => null,
76
        'debug'                     => null,
77
        'disallow-test-output'      => null,
78
        'disallow-resource-usage'   => null,
79
        'disallow-todo-tests'       => null,
80
        'enforce-time-limit'        => null,
81
        'exclude-group='            => null,
82
        'filter='                   => null,
83
        'generate-configuration'    => null,
84
        'globals-backup'            => null,
85
        'group='                    => null,
86
        'help'                      => null,
87
        'include-path='             => null,
88
        'list-groups'               => null,
89
        'list-suites'               => null,
90
        'loader='                   => null,
91
        'log-junit='                => null,
92
        'log-teamcity='             => null,
93
        'no-configuration'          => null,
94
        'no-coverage'               => null,
95
        'no-extensions'             => null,
96
        'printer='                  => null,
97
        'process-isolation'         => null,
98
        'repeat='                   => null,
99
        'dont-report-useless-tests' => null,
100
        'reverse-list'              => null,
101
        'static-backup'             => null,
102
        'stderr'                    => null,
103
        'stop-on-error'             => null,
104
        'stop-on-failure'           => null,
105
        'stop-on-warning'           => null,
106
        'stop-on-incomplete'        => null,
107
        'stop-on-risky'             => null,
108
        'stop-on-skipped'           => null,
109
        'fail-on-warning'           => null,
110
        'fail-on-risky'             => null,
111
        'strict-coverage'           => null,
112
        'disable-coverage-ignore'   => null,
113
        'strict-global-state'       => null,
114
        'teamcity'                  => null,
115
        'testdox'                   => null,
116
        'testdox-group='            => null,
117
        'testdox-exclude-group='    => null,
118
        'testdox-html='             => null,
119
        'testdox-text='             => null,
120
        'testdox-xml='              => null,
121
        'test-suffix='              => null,
122
        'testsuite='                => null,
123
        'verbose'                   => null,
124
        'version'                   => null,
125
        'whitelist='                => null
126
    ];
127
128
    /**
129
     * @var bool
130
     */
131
    private $versionStringPrinted = false;
132
133
    /**
134
     * @param bool $exit
135
     */
136
    public static function main($exit = true)
137
    {
138
        $command = new static;
139
140
        return $command->run($_SERVER['argv'], $exit);
141
    }
142
143
    /**
144
     * @param array $argv
145
     * @param bool  $exit
146
     *
147
     * @return int
148
     */
149
    public function run(array $argv, $exit = true)
150
    {
151
        $this->handleArguments($argv);
152
153
        $runner = $this->createRunner();
154
155
        if ($this->arguments['test'] instanceof Test) {
156
            $suite = $this->arguments['test'];
157
        } else {
158
            $suite = $runner->getTest(
159
                $this->arguments['test'],
160
                $this->arguments['testFile'],
161
                $this->arguments['testSuffixes']
162
            );
163
        }
164
165
        if ($this->arguments['listGroups']) {
166
            $this->printVersionString();
167
168
            print "Available test group(s):\n";
169
170
            $groups = $suite->getGroups();
171
            \sort($groups);
172
173
            foreach ($groups as $group) {
174
                print " - $group\n";
175
            }
176
177
            if ($exit) {
178
                exit(TestRunner::SUCCESS_EXIT);
179
            }
180
181
            return TestRunner::SUCCESS_EXIT;
182
        }
183
184
        if ($this->arguments['listSuites']) {
185
            $this->printVersionString();
186
187
            print "Available test suite(s):\n";
188
189
            $configuration = Configuration::getInstance(
190
                $this->arguments['configuration']
191
            );
192
193
            $suiteNames = $configuration->getTestSuiteNames();
194
            foreach ($suiteNames as $suiteName) {
195
                print " - $suiteName\n";
196
            }
197
198
            if ($exit) {
199
                exit(TestRunner::SUCCESS_EXIT);
200
            }
201
202
            return TestRunner::SUCCESS_EXIT;
203
        }
204
205
        unset($this->arguments['test']);
206
        unset($this->arguments['testFile']);
207
208
        try {
209
            $result = $runner->doRun($suite, $this->arguments, $exit);
210
        } catch (Exception $e) {
211
            print $e->getMessage() . "\n";
212
        }
213
214
        $return = TestRunner::FAILURE_EXIT;
215
216
        if (isset($result) && $result->wasSuccessful()) {
217
            $return = TestRunner::SUCCESS_EXIT;
218
        } elseif (!isset($result) || $result->errorCount() > 0) {
219
            $return = TestRunner::EXCEPTION_EXIT;
220
        }
221
222
        if ($exit) {
223
            exit($return);
224
        }
225
226
        return $return;
227
    }
228
229
    /**
230
     * Create a TestRunner, override in subclasses.
231
     *
232
     * @return TestRunner
233
     */
234
    protected function createRunner()
235
    {
236
        return new TestRunner($this->arguments['loader']);
237
    }
238
239
    /**
240
     * Handles the command-line arguments.
241
     *
242
     * A child class of PHPUnit_TextUI_Command can hook into the argument
243
     * parsing by adding the switch(es) to the $longOptions array and point to a
244
     * callback method that handles the switch(es) in the child class like this
245
     *
246
     * <code>
247
     * <?php
248
     * class MyCommand extends PHPUnit_TextUI_Command
249
     * {
250
     *     public function __construct()
251
     *     {
252
     *         // my-switch won't accept a value, it's an on/off
253
     *         $this->longOptions['my-switch'] = 'myHandler';
254
     *         // my-secondswitch will accept a value - note the equals sign
255
     *         $this->longOptions['my-secondswitch='] = 'myOtherHandler';
256
     *     }
257
     *
258
     *     // --my-switch  -> myHandler()
259
     *     protected function myHandler()
260
     *     {
261
     *     }
262
     *
263
     *     // --my-secondswitch foo -> myOtherHandler('foo')
264
     *     protected function myOtherHandler ($value)
265
     *     {
266
     *     }
267
     *
268
     *     // You will also need this - the static keyword in the
269
     *     // PHPUnit_TextUI_Command will mean that it'll be
270
     *     // PHPUnit_TextUI_Command that gets instantiated,
271
     *     // not MyCommand
272
     *     public static function main($exit = true)
273
     *     {
274
     *         $command = new static;
275
     *
276
     *         return $command->run($_SERVER['argv'], $exit);
277
     *     }
278
     *
279
     * }
280
     * </code>
281
     *
282
     * @param array $argv
283
     */
284
    protected function handleArguments(array $argv)
285
    {
286
        try {
287
            $this->options = Getopt::getopt(
288
                $argv,
289
                'd:c:hv',
290
                \array_keys($this->longOptions)
291
            );
292
        } catch (Exception $t) {
293
            $this->showError($t->getMessage());
294
        }
295
296
        foreach ($this->options[0] as $option) {
297
            switch ($option[0]) {
298
                case '--colors':
299
                    $this->arguments['colors'] = $option[1] ?: ResultPrinter::COLOR_AUTO;
300
                    break;
301
302
                case '--bootstrap':
303
                    $this->arguments['bootstrap'] = $option[1];
304
                    break;
305
306
                case '--columns':
307
                    if (\is_numeric($option[1])) {
308
                        $this->arguments['columns'] = (int) $option[1];
309
                    } elseif ($option[1] == 'max') {
310
                        $this->arguments['columns'] = 'max';
311
                    }
312
                    break;
313
314
                case 'c':
315
                case '--configuration':
316
                    $this->arguments['configuration'] = $option[1];
317
                    break;
318
319
                case '--coverage-clover':
320
                    $this->arguments['coverageClover'] = $option[1];
321
                    break;
322
323
                case '--coverage-crap4j':
324
                    $this->arguments['coverageCrap4J'] = $option[1];
325
                    break;
326
327
                case '--coverage-html':
328
                    $this->arguments['coverageHtml'] = $option[1];
329
                    break;
330
331
                case '--coverage-php':
332
                    $this->arguments['coveragePHP'] = $option[1];
333
                    break;
334
335
                case '--coverage-text':
336
                    if ($option[1] === null) {
337
                        $option[1] = 'php://stdout';
338
                    }
339
340
                    $this->arguments['coverageText']                   = $option[1];
341
                    $this->arguments['coverageTextShowUncoveredFiles'] = false;
342
                    $this->arguments['coverageTextShowOnlySummary']    = false;
343
                    break;
344
345
                case '--coverage-xml':
346
                    $this->arguments['coverageXml'] = $option[1];
347
                    break;
348
349
                case 'd':
350
                    $ini = \explode('=', $option[1]);
351
352
                    if (isset($ini[0])) {
353
                        if (isset($ini[1])) {
354
                            \ini_set($ini[0], $ini[1]);
355
                        } else {
356
                            \ini_set($ini[0], true);
357
                        }
358
                    }
359
                    break;
360
361
                case '--debug':
362
                    $this->arguments['debug'] = true;
363
                    break;
364
365
                case 'h':
366
                case '--help':
367
                    $this->showHelp();
368
                    exit(TestRunner::SUCCESS_EXIT);
369
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
370
371
                case '--filter':
372
                    $this->arguments['filter'] = $option[1];
373
                    break;
374
375
                case '--testsuite':
376
                    $this->arguments['testsuite'] = $option[1];
377
                    break;
378
379
                case '--generate-configuration':
380
                    $this->printVersionString();
381
382
                    \printf(
383
                        "Generating phpunit.xml in %s\n\n",
384
                        \getcwd()
385
                    );
386
387
                    print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): ';
388
                    $bootstrapScript = \trim(\fgets(STDIN));
389
390
                    print 'Tests directory (relative to path shown above; default: tests): ';
391
                    $testsDirectory = \trim(\fgets(STDIN));
392
393
                    print 'Source directory (relative to path shown above; default: src): ';
394
                    $src = \trim(\fgets(STDIN));
395
396
                    if ($bootstrapScript == '') {
397
                        $bootstrapScript = 'vendor/autoload.php';
398
                    }
399
400
                    if ($testsDirectory == '') {
401
                        $testsDirectory = 'tests';
402
                    }
403
404
                    if ($src == '') {
405
                        $src = 'src';
406
                    }
407
408
                    $generator = new ConfigurationGenerator;
409
410
                    \file_put_contents(
411
                        'phpunit.xml',
412
                        $generator->generateDefaultConfiguration(
413
                            Version::series(),
414
                            $bootstrapScript,
415
                            $testsDirectory,
416
                            $src
417
                        )
418
                    );
419
420
                    \printf(
421
                        "\nGenerated phpunit.xml in %s\n",
422
                        \getcwd()
423
                    );
424
425
                    exit(TestRunner::SUCCESS_EXIT);
426
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
427
428
                case '--group':
429
                    $this->arguments['groups'] = \explode(',', $option[1]);
430
                    break;
431
432
                case '--exclude-group':
433
                    $this->arguments['excludeGroups'] = \explode(
434
                        ',',
435
                        $option[1]
436
                    );
437
                    break;
438
439
                case '--test-suffix':
440
                    $this->arguments['testSuffixes'] = \explode(
441
                        ',',
442
                        $option[1]
443
                    );
444
                    break;
445
446
                case '--include-path':
447
                    $includePath = $option[1];
448
                    break;
449
450
                case '--list-groups':
451
                    $this->arguments['listGroups'] = true;
452
                    break;
453
454
                case '--list-suites':
455
                    $this->arguments['listSuites'] = true;
456
                    break;
457
458
                case '--printer':
459
                    $this->arguments['printer'] = $option[1];
460
                    break;
461
462
                case '--loader':
463
                    $this->arguments['loader'] = $option[1];
464
                    break;
465
466
                case '--log-junit':
467
                    $this->arguments['junitLogfile'] = $option[1];
468
                    break;
469
470
                case '--log-teamcity':
471
                    $this->arguments['teamcityLogfile'] = $option[1];
472
                    break;
473
474
                case '--process-isolation':
475
                    $this->arguments['processIsolation'] = true;
476
                    break;
477
478
                case '--repeat':
479
                    $this->arguments['repeat'] = (int) $option[1];
480
                    break;
481
482
                case '--stderr':
483
                    $this->arguments['stderr'] = true;
484
                    break;
485
486
                case '--stop-on-error':
487
                    $this->arguments['stopOnError'] = true;
488
                    break;
489
490
                case '--stop-on-failure':
491
                    $this->arguments['stopOnFailure'] = true;
492
                    break;
493
494
                case '--stop-on-warning':
495
                    $this->arguments['stopOnWarning'] = true;
496
                    break;
497
498
                case '--stop-on-incomplete':
499
                    $this->arguments['stopOnIncomplete'] = true;
500
                    break;
501
502
                case '--stop-on-risky':
503
                    $this->arguments['stopOnRisky'] = true;
504
                    break;
505
506
                case '--stop-on-skipped':
507
                    $this->arguments['stopOnSkipped'] = true;
508
                    break;
509
510
                case '--fail-on-warning':
511
                    $this->arguments['failOnWarning'] = true;
512
                    break;
513
514
                case '--fail-on-risky':
515
                    $this->arguments['failOnRisky'] = true;
516
                    break;
517
518
                case '--teamcity':
519
                    $this->arguments['printer'] = TeamCity::class;
520
                    break;
521
522
                case '--testdox':
523
                    $this->arguments['printer'] = TextResultPrinter::class;
524
                    break;
525
526
                case '--testdox-group':
527
                    $this->arguments['testdoxGroups'] = \explode(
528
                        ',',
529
                        $option[1]
530
                    );
531
                    break;
532
533
                case '--testdox-exclude-group':
534
                    $this->arguments['testdoxExcludeGroups'] = \explode(
535
                        ',',
536
                        $option[1]
537
                    );
538
                    break;
539
540
                case '--testdox-html':
541
                    $this->arguments['testdoxHTMLFile'] = $option[1];
542
                    break;
543
544
                case '--testdox-text':
545
                    $this->arguments['testdoxTextFile'] = $option[1];
546
                    break;
547
548
                case '--testdox-xml':
549
                    $this->arguments['testdoxXMLFile'] = $option[1];
550
                    break;
551
552
                case '--no-configuration':
553
                    $this->arguments['useDefaultConfiguration'] = false;
554
                    break;
555
556
                case '--no-extensions':
557
                    $this->arguments['noExtensions'] = true;
558
                    break;
559
560
                case '--no-coverage':
561
                    $this->arguments['noCoverage'] = true;
562
                    break;
563
564
                case '--globals-backup':
565
                    $this->arguments['backupGlobals'] = true;
566
                    break;
567
568
                case '--static-backup':
569
                    $this->arguments['backupStaticAttributes'] = true;
570
                    break;
571
572
                case 'v':
573
                case '--verbose':
574
                    $this->arguments['verbose'] = true;
575
                    break;
576
577
                case '--atleast-version':
578
                    if (\version_compare(Version::id(), $option[1], '>=')) {
579
                        exit(TestRunner::SUCCESS_EXIT);
580
                    }
581
582
                    exit(TestRunner::FAILURE_EXIT);
583
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
584
585
                case '--version':
586
                    $this->printVersionString();
587
                    exit(TestRunner::SUCCESS_EXIT);
588
                    break;
0 ignored issues
show
Unused Code introduced by
break; does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
589
590
                case '--dont-report-useless-tests':
591
                    $this->arguments['reportUselessTests'] = false;
592
                    break;
593
594
                case '--strict-coverage':
595
                    $this->arguments['strictCoverage'] = true;
596
                    break;
597
598
                case '--disable-coverage-ignore':
599
                    $this->arguments['disableCodeCoverageIgnore'] = true;
600
                    break;
601
602
                case '--strict-global-state':
603
                    $this->arguments['beStrictAboutChangesToGlobalState'] = true;
604
                    break;
605
606
                case '--disallow-test-output':
607
                    $this->arguments['disallowTestOutput'] = true;
608
                    break;
609
610
                case '--disallow-resource-usage':
611
                    $this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true;
612
                    break;
613
614
                case '--enforce-time-limit':
615
                    $this->arguments['enforceTimeLimit'] = true;
616
                    break;
617
618
                case '--disallow-todo-tests':
619
                    $this->arguments['disallowTodoAnnotatedTests'] = true;
620
                    break;
621
622
                case '--reverse-list':
623
                    $this->arguments['reverseList'] = true;
624
                    break;
625
626
                case '--check-version':
627
                    $this->handleVersionCheck();
628
                    break;
629
630
                case '--whitelist':
631
                    $this->arguments['whitelist'] = $option[1];
632
                    break;
633
634
                default:
635
                    $optionName = \str_replace('--', '', $option[0]);
636
637
                    $handler = null;
638
                    if (isset($this->longOptions[$optionName])) {
639
                        $handler = $this->longOptions[$optionName];
640
                    } elseif (isset($this->longOptions[$optionName . '='])) {
641
                        $handler = $this->longOptions[$optionName . '='];
642
                    }
643
644
                    if (isset($handler) && \is_callable([$this, $handler])) {
645
                        $this->$handler($option[1]);
646
                    }
647
            }
648
        }
649
650
        $this->handleCustomTestSuite();
651
652
        if (!isset($this->arguments['test'])) {
653
            if (isset($this->options[1][0])) {
654
                $this->arguments['test'] = $this->options[1][0];
655
            }
656
657
            if (isset($this->options[1][1])) {
658
                $this->arguments['testFile'] = \realpath($this->options[1][1]);
659
            } else {
660
                $this->arguments['testFile'] = '';
661
            }
662
663
            if (isset($this->arguments['test']) &&
664
                \is_file($this->arguments['test']) &&
665
                \substr($this->arguments['test'], -5, 5) != '.phpt') {
666
                $this->arguments['testFile'] = \realpath($this->arguments['test']);
667
                $this->arguments['test']     = \substr($this->arguments['test'], 0, \strrpos($this->arguments['test'], '.'));
668
            }
669
        }
670
671
        if (!isset($this->arguments['testSuffixes'])) {
672
            $this->arguments['testSuffixes'] = ['Test.php', '.phpt'];
673
        }
674
675
        if (isset($includePath)) {
676
            \ini_set(
677
                'include_path',
678
                $includePath . PATH_SEPARATOR . \ini_get('include_path')
679
            );
680
        }
681
682
        if ($this->arguments['loader'] !== null) {
683
            $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
684
        }
685
686
        if (isset($this->arguments['configuration']) &&
687
            \is_dir($this->arguments['configuration'])) {
688
            $configurationFile = $this->arguments['configuration'] . '/phpunit.xml';
689
690
            if (\file_exists($configurationFile)) {
691
                $this->arguments['configuration'] = \realpath(
692
                    $configurationFile
693
                );
694
            } elseif (\file_exists($configurationFile . '.dist')) {
695
                $this->arguments['configuration'] = \realpath(
696
                    $configurationFile . '.dist'
697
                );
698
            }
699
        } elseif (!isset($this->arguments['configuration']) &&
700
            $this->arguments['useDefaultConfiguration']) {
701
            if (\file_exists('phpunit.xml')) {
702
                $this->arguments['configuration'] = \realpath('phpunit.xml');
703
            } elseif (\file_exists('phpunit.xml.dist')) {
704
                $this->arguments['configuration'] = \realpath(
705
                    'phpunit.xml.dist'
706
                );
707
            }
708
        }
709
710
        if (isset($this->arguments['configuration'])) {
711
            try {
712
                $configuration = Configuration::getInstance(
713
                    $this->arguments['configuration']
714
                );
715
            } catch (Throwable $t) {
716
                print $t->getMessage() . "\n";
717
                exit(TestRunner::FAILURE_EXIT);
718
            }
719
720
            $phpunitConfiguration = $configuration->getPHPUnitConfiguration();
721
722
            $configuration->handlePHPConfiguration();
723
724
            /*
725
             * Issue #1216
726
             */
727
            if (isset($this->arguments['bootstrap'])) {
728
                $this->handleBootstrap($this->arguments['bootstrap']);
729
            } elseif (isset($phpunitConfiguration['bootstrap'])) {
730
                $this->handleBootstrap($phpunitConfiguration['bootstrap']);
731
            }
732
733
            /*
734
             * Issue #657
735
             */
736
            if (isset($phpunitConfiguration['stderr']) && !isset($this->arguments['stderr'])) {
737
                $this->arguments['stderr'] = $phpunitConfiguration['stderr'];
738
            }
739
740
            if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && \extension_loaded('phar')) {
741
                $this->handleExtensions($phpunitConfiguration['extensionsDirectory']);
742
            }
743
744
            if (isset($phpunitConfiguration['columns']) && !isset($this->arguments['columns'])) {
745
                $this->arguments['columns'] = $phpunitConfiguration['columns'];
746
            }
747
748
            if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) {
749
                if (isset($phpunitConfiguration['printerFile'])) {
750
                    $file = $phpunitConfiguration['printerFile'];
751
                } else {
752
                    $file = '';
753
                }
754
755
                $this->arguments['printer'] = $this->handlePrinter(
756
                    $phpunitConfiguration['printerClass'],
757
                    $file
758
                );
759
            }
760
761
            if (isset($phpunitConfiguration['testSuiteLoaderClass'])) {
762
                if (isset($phpunitConfiguration['testSuiteLoaderFile'])) {
763
                    $file = $phpunitConfiguration['testSuiteLoaderFile'];
764
                } else {
765
                    $file = '';
766
                }
767
768
                $this->arguments['loader'] = $this->handleLoader(
769
                    $phpunitConfiguration['testSuiteLoaderClass'],
770
                    $file
771
                );
772
            }
773
774
            if (!isset($this->arguments['testsuite']) && isset($phpunitConfiguration['defaultTestSuite'])) {
775
                $this->arguments['testsuite'] = $phpunitConfiguration['defaultTestSuite'];
776
            }
777
778
            if (!isset($this->arguments['test'])) {
779
                $testSuite = $configuration->getTestSuiteConfiguration($this->arguments['testsuite'] ?? null);
780
781
                if ($testSuite !== null) {
782
                    $this->arguments['test'] = $testSuite;
783
                }
784
            }
785
        } elseif (isset($this->arguments['bootstrap'])) {
786
            $this->handleBootstrap($this->arguments['bootstrap']);
787
        }
788
789
        if (isset($this->arguments['printer']) &&
790
            \is_string($this->arguments['printer'])) {
791
            $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
792
        }
793
794
        if (isset($this->arguments['test']) && \is_string($this->arguments['test']) && \substr($this->arguments['test'], -5, 5) == '.phpt') {
795
            $test = new PhptTestCase($this->arguments['test']);
796
797
            $this->arguments['test'] = new TestSuite;
798
            $this->arguments['test']->addTest($test);
799
        }
800
801
        if (!isset($this->arguments['test'])) {
802
            $this->showHelp();
803
            exit(TestRunner::EXCEPTION_EXIT);
804
        }
805
    }
806
807
    /**
808
     * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
809
     *
810
     * @param string $loaderClass
811
     * @param string $loaderFile
812
     *
813
     * @return TestSuiteLoader
814
     */
815
    protected function handleLoader($loaderClass, $loaderFile = '')
816
    {
817
        if (!\class_exists($loaderClass, false)) {
818
            if ($loaderFile == '') {
819
                $loaderFile = Filesystem::classNameToFilename(
820
                    $loaderClass
821
                );
822
            }
823
824
            $loaderFile = \stream_resolve_include_path($loaderFile);
825
826
            if ($loaderFile) {
827
                require $loaderFile;
828
            }
829
        }
830
831
        if (\class_exists($loaderClass, false)) {
832
            $class = new ReflectionClass($loaderClass);
833
834
            if ($class->implementsInterface(TestSuiteLoader::class) &&
835
                $class->isInstantiable()) {
836
                return $class->newInstance();
837
            }
838
        }
839
840
        if ($loaderClass == StandardTestSuiteLoader::class) {
841
            return;
842
        }
843
844
        $this->showError(
845
            \sprintf(
846
                'Could not use "%s" as loader.',
847
                $loaderClass
848
            )
849
        );
850
    }
851
852
    /**
853
     * Handles the loading of the PHPUnit_Util_Printer implementation.
854
     *
855
     * @param string $printerClass
856
     * @param string $printerFile
857
     *
858
     * @return Printer|string
859
     */
860
    protected function handlePrinter($printerClass, $printerFile = '')
861
    {
862
        if (!\class_exists($printerClass, false)) {
863
            if ($printerFile == '') {
864
                $printerFile = Filesystem::classNameToFilename(
865
                    $printerClass
866
                );
867
            }
868
869
            $printerFile = \stream_resolve_include_path($printerFile);
870
871
            if ($printerFile) {
872
                require $printerFile;
873
            }
874
        }
875
876
        if (\class_exists($printerClass)) {
877
            $class = new ReflectionClass($printerClass);
878
879
            if ($class->implementsInterface(TestListener::class) &&
880
                $class->isSubclassOf(Printer::class) &&
881
                $class->isInstantiable()) {
882
                if ($class->isSubclassOf(ResultPrinter::class)) {
883
                    return $printerClass;
884
                }
885
886
                $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
887
888
                return $class->newInstance($outputStream);
889
            }
890
        }
891
892
        $this->showError(
893
            \sprintf(
894
                'Could not use "%s" as printer.',
895
                $printerClass
896
            )
897
        );
898
    }
899
900
    /**
901
     * Loads a bootstrap file.
902
     *
903
     * @param string $filename
904
     */
905
    protected function handleBootstrap($filename)
906
    {
907
        try {
908
            Fileloader::checkAndLoad($filename);
909
        } catch (Exception $e) {
910
            $this->showError($e->getMessage());
911
        }
912
    }
913
914
    protected function handleVersionCheck()
915
    {
916
        $this->printVersionString();
917
918
        $latestVersion = \file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
919
        $isOutdated    = \version_compare($latestVersion, Version::id(), '>');
920
921
        if ($isOutdated) {
922
            \printf(
923
                "You are not using the latest version of PHPUnit.\n" .
924
                "The latest version is PHPUnit %s.\n",
925
                $latestVersion
926
            );
927
        } else {
928
            print "You are using the latest version of PHPUnit.\n";
929
        }
930
931
        exit(TestRunner::SUCCESS_EXIT);
932
    }
933
934
    /**
935
     * Show the help message.
936
     */
937
    protected function showHelp()
938
    {
939
        $this->printVersionString();
940
941
        print <<<EOT
942
Usage: phpunit [options] UnitTest [UnitTest.php]
943
       phpunit [options] <directory>
944
945
Code Coverage Options:
946
947
  --coverage-clover <file>    Generate code coverage report in Clover XML format.
948
  --coverage-crap4j <file>    Generate code coverage report in Crap4J XML format.
949
  --coverage-html <dir>       Generate code coverage report in HTML format.
950
  --coverage-php <file>       Export PHP_CodeCoverage object to file.
951
  --coverage-text=<file>      Generate code coverage report in text format.
952
                              Default: Standard output.
953
  --coverage-xml <dir>        Generate code coverage report in PHPUnit XML format.
954
  --whitelist <dir>           Whitelist <dir> for code coverage analysis.
955
  --disable-coverage-ignore   Disable annotations for ignoring code coverage.
956
957
Logging Options:
958
959
  --log-junit <file>          Log test execution in JUnit XML format to file.
960
  --log-teamcity <file>       Log test execution in TeamCity format to file.
961
  --testdox-html <file>       Write agile documentation in HTML format to file.
962
  --testdox-text <file>       Write agile documentation in Text format to file.
963
  --testdox-xml <file>        Write agile documentation in XML format to file.
964
  --reverse-list              Print defects in reverse order
965
966
Test Selection Options:
967
968
  --filter <pattern>          Filter which tests to run.
969
  --testsuite <name,...>      Filter which testsuite to run.
970
  --group ...                 Only runs tests from the specified group(s).
971
  --exclude-group ...         Exclude tests from the specified group(s).
972
  --list-groups               List available test groups.
973
  --list-suites               List available test suites.
974
  --test-suffix ...           Only search for test in files with specified
975
                              suffix(es). Default: Test.php,.phpt
976
977
Test Execution Options:
978
979
  --dont-report-useless-tests Do not report tests that do not test anything.
980
  --strict-coverage           Be strict about @covers annotation usage.
981
  --strict-global-state       Be strict about changes to global state
982
  --disallow-test-output      Be strict about output during tests.
983
  --disallow-resource-usage   Be strict about resource usage during small tests.
984
  --enforce-time-limit        Enforce time limit based on test size.
985
  --disallow-todo-tests       Disallow @todo-annotated tests.
986
987
  --process-isolation         Run each test in a separate PHP process.
988
  --globals-backup            Backup and restore \$GLOBALS for each test.
989
  --static-backup             Backup and restore static attributes for each test.
990
991
  --colors=<flag>             Use colors in output ("never", "auto" or "always").
992
  --columns <n>               Number of columns to use for progress output.
993
  --columns max               Use maximum number of columns for progress output.
994
  --stderr                    Write to STDERR instead of STDOUT.
995
  --stop-on-error             Stop execution upon first error.
996
  --stop-on-failure           Stop execution upon first error or failure.
997
  --stop-on-warning           Stop execution upon first warning.
998
  --stop-on-risky             Stop execution upon first risky test.
999
  --stop-on-skipped           Stop execution upon first skipped test.
1000
  --stop-on-incomplete        Stop execution upon first incomplete test.
1001
  --fail-on-warning           Treat tests with warnings as failures.
1002
  --fail-on-risky             Treat risky tests as failures.
1003
  -v|--verbose                Output more verbose information.
1004
  --debug                     Display debugging information.
1005
1006
  --loader <loader>           TestSuiteLoader implementation to use.
1007
  --repeat <times>            Runs the test(s) repeatedly.
1008
  --teamcity                  Report test execution progress in TeamCity format.
1009
  --testdox                   Report test execution progress in TestDox format.
1010
  --testdox-group             Only include tests from the specified group(s).
1011
  --testdox-exclude-group     Exclude tests from the specified group(s).
1012
  --printer <printer>         TestListener implementation to use.
1013
1014
Configuration Options:
1015
1016
  --bootstrap <file>          A "bootstrap" PHP file that is run before the tests.
1017
  -c|--configuration <file>   Read configuration from XML file.
1018
  --no-configuration          Ignore default configuration file (phpunit.xml).
1019
  --no-coverage               Ignore code coverage configuration.
1020
  --no-extensions             Do not load PHPUnit extensions.
1021
  --include-path <path(s)>    Prepend PHP's include_path with given path(s).
1022
  -d key[=value]              Sets a php.ini value.
1023
  --generate-configuration    Generate configuration file with suggested settings.
1024
1025
Miscellaneous Options:
1026
1027
  -h|--help                   Prints this usage information.
1028
  --version                   Prints the version and exits.
1029
  --atleast-version <min>     Checks that version is greater than min and exits.
1030
  --check-version             Check whether PHPUnit is the latest version.
1031
1032
EOT;
1033
    }
1034
1035
    /**
1036
     * Custom callback for test suite discovery.
1037
     */
1038
    protected function handleCustomTestSuite()
1039
    {
1040
    }
1041
1042
    private function printVersionString()
1043
    {
1044
        if ($this->versionStringPrinted) {
1045
            return;
1046
        }
1047
1048
        print Version::getVersionString() . "\n\n";
1049
1050
        $this->versionStringPrinted = true;
1051
    }
1052
1053
    /**
1054
     * @param string $message
1055
     */
1056
    private function showError($message)
1057
    {
1058
        $this->printVersionString();
1059
1060
        print $message . "\n";
1061
1062
        exit(TestRunner::FAILURE_EXIT);
1063
    }
1064
1065
    /**
1066
     * @param string $directory
1067
     */
1068
    private function handleExtensions($directory)
1069
    {
1070
        $facade = new File_Iterator_Facade;
1071
1072
        foreach ($facade->getFilesAsArray($directory, '.phar') as $file) {
1073
            if (!\file_exists('phar://' . $file . '/manifest.xml')) {
1074
                $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit';
1075
1076
                continue;
1077
            }
1078
1079
            try {
1080
                $applicationName = new ApplicationName('phpunit/phpunit');
1081
                $version         = new PharIoVersion(Version::series());
1082
                $manifest        = ManifestLoader::fromFile('phar://' . $file . '/manifest.xml');
1083
1084
                if (!$manifest->isExtensionFor($applicationName)) {
1085
                    $this->arguments['notLoadedExtensions'][] = $file . ' is not an extension for PHPUnit';
1086
1087
                    continue;
1088
                }
1089
1090
                if (!$manifest->isExtensionFor($applicationName, $version)) {
1091
                    $this->arguments['notLoadedExtensions'][] = $file . ' is not compatible with this version of PHPUnit';
1092
1093
                    continue;
1094
                }
1095
            } catch (ManifestException $e) {
1096
                $this->arguments['notLoadedExtensions'][] = $file . ': ' . $e->getMessage();
1097
1098
                continue;
1099
            }
1100
1101
            require $file;
1102
1103
            $this->arguments['loadedExtensions'][] = $manifest->getName() . ' ' . $manifest->getVersion()->getVersionString();
1104
        }
1105
    }
1106
}
1107