1<?php
2/*
3 * This file is part of PHPUnit.
4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 *
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
9 */
10
11/**
12 * A TestRunner for the Command Line Interface (CLI)
13 * PHP SAPI Module.
14 */
15class PHPUnit_TextUI_Command
16{
17    /**
18     * @var array
19     */
20    protected $arguments = [
21        'listGroups'              => false,
22        'listSuites'              => false,
23        'loader'                  => null,
24        'useDefaultConfiguration' => true,
25        'loadedExtensions'        => [],
26        'notLoadedExtensions'     => []
27    ];
28
29    /**
30     * @var array
31     */
32    protected $options = [];
33
34    /**
35     * @var array
36     */
37    protected $longOptions = [
38        'atleast-version='        => null,
39        'bootstrap='              => null,
40        'colors=='                => null,
41        'columns='                => null,
42        'configuration='          => null,
43        'coverage-clover='        => null,
44        'coverage-crap4j='        => null,
45        'coverage-html='          => null,
46        'coverage-php='           => null,
47        'coverage-text=='         => null,
48        'coverage-xml='           => null,
49        'debug'                   => null,
50        'disallow-test-output'    => null,
51        'disallow-resource-usage' => null,
52        'disallow-todo-tests'     => null,
53        'enforce-time-limit'      => null,
54        'exclude-group='          => null,
55        'filter='                 => null,
56        'generate-configuration'  => null,
57        'group='                  => null,
58        'help'                    => null,
59        'include-path='           => null,
60        'list-groups'             => null,
61        'list-suites'             => null,
62        'loader='                 => null,
63        'log-json='               => null,
64        'log-junit='              => null,
65        'log-tap='                => null,
66        'log-teamcity='           => null,
67        'no-configuration'        => null,
68        'no-coverage'             => null,
69        'no-extensions'           => null,
70        'no-globals-backup'       => null,
71        'printer='                => null,
72        'process-isolation'       => null,
73        'repeat='                 => null,
74        'report-useless-tests'    => null,
75        'reverse-list'            => null,
76        'static-backup'           => null,
77        'stderr'                  => null,
78        'stop-on-error'           => null,
79        'stop-on-failure'         => null,
80        'stop-on-warning'         => null,
81        'stop-on-incomplete'      => null,
82        'stop-on-risky'           => null,
83        'stop-on-skipped'         => null,
84        'fail-on-warning'         => null,
85        'fail-on-risky'           => null,
86        'strict-coverage'         => null,
87        'disable-coverage-ignore' => null,
88        'strict-global-state'     => null,
89        'tap'                     => null,
90        'teamcity'                => null,
91        'testdox'                 => null,
92        'testdox-group='          => null,
93        'testdox-exclude-group='  => null,
94        'testdox-html='           => null,
95        'testdox-text='           => null,
96        'testdox-xml='            => null,
97        'test-suffix='            => null,
98        'testsuite='              => null,
99        'verbose'                 => null,
100        'version'                 => null,
101        'whitelist='              => null
102    ];
103
104    /**
105     * @var bool
106     */
107    private $versionStringPrinted = false;
108
109    /**
110     * @param bool $exit
111     */
112    public static function main($exit = true)
113    {
114        $command = new static;
115
116        return $command->run($_SERVER['argv'], $exit);
117    }
118
119    /**
120     * @param array $argv
121     * @param bool  $exit
122     *
123     * @return int
124     */
125    public function run(array $argv, $exit = true)
126    {
127        $this->handleArguments($argv);
128
129        $runner = $this->createRunner();
130
131        if (is_object($this->arguments['test']) &&
132            $this->arguments['test'] instanceof PHPUnit_Framework_Test) {
133            $suite = $this->arguments['test'];
134        } else {
135            $suite = $runner->getTest(
136                $this->arguments['test'],
137                $this->arguments['testFile'],
138                $this->arguments['testSuffixes']
139            );
140        }
141
142        if ($this->arguments['listGroups']) {
143            $this->printVersionString();
144
145            print "Available test group(s):\n";
146
147            $groups = $suite->getGroups();
148            sort($groups);
149
150            foreach ($groups as $group) {
151                print " - $group\n";
152            }
153
154            if ($exit) {
155                exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
156            } else {
157                return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
158            }
159        }
160
161        if ($this->arguments['listSuites']) {
162            $this->printVersionString();
163
164            print "Available test suite(s):\n";
165
166            $configuration = PHPUnit_Util_Configuration::getInstance(
167                $this->arguments['configuration']
168            );
169
170            $suiteNames = $configuration->getTestSuiteNames();
171            foreach ($suiteNames as $suiteName) {
172                print " - $suiteName\n";
173            }
174
175            if ($exit) {
176                exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
177            } else {
178                return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
179            }
180        }
181
182        unset($this->arguments['test']);
183        unset($this->arguments['testFile']);
184
185        try {
186            $result = $runner->doRun($suite, $this->arguments, $exit);
187        } catch (PHPUnit_Framework_Exception $e) {
188            print $e->getMessage() . "\n";
189        }
190
191        $return = PHPUnit_TextUI_TestRunner::FAILURE_EXIT;
192
193        if (isset($result) && $result->wasSuccessful(false)) {
194            $return = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
195        } elseif (!isset($result) || $result->errorCount() > 0) {
196            $return = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT;
197        }
198
199        if ($exit) {
200            exit($return);
201        }
202
203        return $return;
204    }
205
206    /**
207     * Create a TestRunner, override in subclasses.
208     *
209     * @return PHPUnit_TextUI_TestRunner
210     */
211    protected function createRunner()
212    {
213        return new PHPUnit_TextUI_TestRunner($this->arguments['loader']);
214    }
215
216    /**
217     * Handles the command-line arguments.
218     *
219     * A child class of PHPUnit_TextUI_Command can hook into the argument
220     * parsing by adding the switch(es) to the $longOptions array and point to a
221     * callback method that handles the switch(es) in the child class like this
222     *
223     * <code>
224     * <?php
225     * class MyCommand extends PHPUnit_TextUI_Command
226     * {
227     *     public function __construct()
228     *     {
229     *         // my-switch won't accept a value, it's an on/off
230     *         $this->longOptions['my-switch'] = 'myHandler';
231     *         // my-secondswitch will accept a value - note the equals sign
232     *         $this->longOptions['my-secondswitch='] = 'myOtherHandler';
233     *     }
234     *
235     *     // --my-switch  -> myHandler()
236     *     protected function myHandler()
237     *     {
238     *     }
239     *
240     *     // --my-secondswitch foo -> myOtherHandler('foo')
241     *     protected function myOtherHandler ($value)
242     *     {
243     *     }
244     *
245     *     // You will also need this - the static keyword in the
246     *     // PHPUnit_TextUI_Command will mean that it'll be
247     *     // PHPUnit_TextUI_Command that gets instantiated,
248     *     // not MyCommand
249     *     public static function main($exit = true)
250     *     {
251     *         $command = new static;
252     *
253     *         return $command->run($_SERVER['argv'], $exit);
254     *     }
255     *
256     * }
257     * </code>
258     *
259     * @param array $argv
260     */
261    protected function handleArguments(array $argv)
262    {
263        if (defined('__PHPUNIT_PHAR__')) {
264            $this->longOptions['check-version'] = null;
265            $this->longOptions['selfupdate']    = null;
266            $this->longOptions['self-update']   = null;
267            $this->longOptions['selfupgrade']   = null;
268            $this->longOptions['self-upgrade']  = null;
269        }
270
271        try {
272            $this->options = PHPUnit_Util_Getopt::getopt(
273                $argv,
274                'd:c:hv',
275                array_keys($this->longOptions)
276            );
277        } catch (PHPUnit_Framework_Exception $e) {
278            $this->showError($e->getMessage());
279        }
280
281        foreach ($this->options[0] as $option) {
282            switch ($option[0]) {
283                case '--colors':
284                    $this->arguments['colors'] = $option[1] ?: PHPUnit_TextUI_ResultPrinter::COLOR_AUTO;
285                    break;
286
287                case '--bootstrap':
288                    $this->arguments['bootstrap'] = $option[1];
289                    break;
290
291                case '--columns':
292                    if (is_numeric($option[1])) {
293                        $this->arguments['columns'] = (int) $option[1];
294                    } elseif ($option[1] == 'max') {
295                        $this->arguments['columns'] = 'max';
296                    }
297                    break;
298
299                case 'c':
300                case '--configuration':
301                    $this->arguments['configuration'] = $option[1];
302                    break;
303
304                case '--coverage-clover':
305                    $this->arguments['coverageClover'] = $option[1];
306                    break;
307
308                case '--coverage-crap4j':
309                    $this->arguments['coverageCrap4J'] = $option[1];
310                    break;
311
312                case '--coverage-html':
313                    $this->arguments['coverageHtml'] = $option[1];
314                    break;
315
316                case '--coverage-php':
317                    $this->arguments['coveragePHP'] = $option[1];
318                    break;
319
320                case '--coverage-text':
321                    if ($option[1] === null) {
322                        $option[1] = 'php://stdout';
323                    }
324
325                    $this->arguments['coverageText']                   = $option[1];
326                    $this->arguments['coverageTextShowUncoveredFiles'] = false;
327                    $this->arguments['coverageTextShowOnlySummary']    = false;
328                    break;
329
330                case '--coverage-xml':
331                    $this->arguments['coverageXml'] = $option[1];
332                    break;
333
334                case 'd':
335                    $ini = explode('=', $option[1]);
336
337                    if (isset($ini[0])) {
338                        if (isset($ini[1])) {
339                            ini_set($ini[0], $ini[1]);
340                        } else {
341                            ini_set($ini[0], true);
342                        }
343                    }
344                    break;
345
346                case '--debug':
347                    $this->arguments['debug'] = true;
348                    break;
349
350                case 'h':
351                case '--help':
352                    $this->showHelp();
353                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
354                    break;
355
356                case '--filter':
357                    $this->arguments['filter'] = $option[1];
358                    break;
359
360                case '--testsuite':
361                    $this->arguments['testsuite'] = $option[1];
362                    break;
363
364                case '--generate-configuration':
365                    $this->printVersionString();
366
367                    printf(
368                        "Generating phpunit.xml in %s\n\n",
369                        getcwd()
370                    );
371
372                    print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): ';
373                    $bootstrapScript = trim(fgets(STDIN));
374
375                    print 'Tests directory (relative to path shown above; default: tests): ';
376                    $testsDirectory = trim(fgets(STDIN));
377
378                    print 'Source directory (relative to path shown above; default: src): ';
379                    $src = trim(fgets(STDIN));
380
381                    if ($bootstrapScript == '') {
382                        $bootstrapScript = 'vendor/autoload.php';
383                    }
384
385                    if ($testsDirectory == '') {
386                        $testsDirectory = 'tests';
387                    }
388
389                    if ($src == '') {
390                        $src = 'src';
391                    }
392
393                    $generator = new PHPUnit_Util_ConfigurationGenerator;
394
395                    file_put_contents(
396                        'phpunit.xml',
397                        $generator->generateDefaultConfiguration(
398                            PHPUnit_Runner_Version::series(),
399                            $bootstrapScript,
400                            $testsDirectory,
401                            $src
402                        )
403                    );
404
405                    printf(
406                        "\nGenerated phpunit.xml in %s\n",
407                        getcwd()
408                    );
409
410                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
411                    break;
412
413                case '--group':
414                    $this->arguments['groups'] = explode(',', $option[1]);
415                    break;
416
417                case '--exclude-group':
418                    $this->arguments['excludeGroups'] = explode(
419                        ',',
420                        $option[1]
421                    );
422                    break;
423
424                case '--test-suffix':
425                    $this->arguments['testSuffixes'] = explode(
426                        ',',
427                        $option[1]
428                    );
429                    break;
430
431                case '--include-path':
432                    $includePath = $option[1];
433                    break;
434
435                case '--list-groups':
436                    $this->arguments['listGroups'] = true;
437                    break;
438
439                case '--list-suites':
440                    $this->arguments['listSuites'] = true;
441                    break;
442
443                case '--printer':
444                    $this->arguments['printer'] = $option[1];
445                    break;
446
447                case '--loader':
448                    $this->arguments['loader'] = $option[1];
449                    break;
450
451                case '--log-json':
452                    $this->arguments['jsonLogfile'] = $option[1];
453                    break;
454
455                case '--log-junit':
456                    $this->arguments['junitLogfile'] = $option[1];
457                    break;
458
459                case '--log-tap':
460                    $this->arguments['tapLogfile'] = $option[1];
461                    break;
462
463                case '--log-teamcity':
464                    $this->arguments['teamcityLogfile'] = $option[1];
465                    break;
466
467                case '--process-isolation':
468                    $this->arguments['processIsolation'] = true;
469                    break;
470
471                case '--repeat':
472                    $this->arguments['repeat'] = (int) $option[1];
473                    break;
474
475                case '--stderr':
476                    $this->arguments['stderr'] = true;
477                    break;
478
479                case '--stop-on-error':
480                    $this->arguments['stopOnError'] = true;
481                    break;
482
483                case '--stop-on-failure':
484                    $this->arguments['stopOnFailure'] = true;
485                    break;
486
487                case '--stop-on-warning':
488                    $this->arguments['stopOnWarning'] = true;
489                    break;
490
491                case '--stop-on-incomplete':
492                    $this->arguments['stopOnIncomplete'] = true;
493                    break;
494
495                case '--stop-on-risky':
496                    $this->arguments['stopOnRisky'] = true;
497                    break;
498
499                case '--stop-on-skipped':
500                    $this->arguments['stopOnSkipped'] = true;
501                    break;
502
503                case '--fail-on-warning':
504                    $this->arguments['failOnWarning'] = true;
505                    break;
506
507                case '--fail-on-risky':
508                    $this->arguments['failOnRisky'] = true;
509                    break;
510
511                case '--tap':
512                    $this->arguments['printer'] = 'PHPUnit_Util_Log_TAP';
513                    break;
514
515                case '--teamcity':
516                    $this->arguments['printer'] = 'PHPUnit_Util_Log_TeamCity';
517                    break;
518
519                case '--testdox':
520                    $this->arguments['printer'] = 'PHPUnit_Util_TestDox_ResultPrinter_Text';
521                    break;
522
523                case '--testdox-group':
524                    $this->arguments['testdoxGroups'] = explode(
525                        ',',
526                        $option[1]
527                    );
528                    break;
529
530                case '--testdox-exclude-group':
531                    $this->arguments['testdoxExcludeGroups'] = explode(
532                        ',',
533                        $option[1]
534                    );
535                    break;
536
537                case '--testdox-html':
538                    $this->arguments['testdoxHTMLFile'] = $option[1];
539                    break;
540
541                case '--testdox-text':
542                    $this->arguments['testdoxTextFile'] = $option[1];
543                    break;
544
545                case '--testdox-xml':
546                    $this->arguments['testdoxXMLFile'] = $option[1];
547                    break;
548
549                case '--no-configuration':
550                    $this->arguments['useDefaultConfiguration'] = false;
551                    break;
552
553                case '--no-extensions':
554                    $this->arguments['noExtensions'] = true;
555                    break;
556
557                case '--no-coverage':
558                    $this->arguments['noCoverage'] = true;
559                    break;
560
561                case '--no-globals-backup':
562                    $this->arguments['backupGlobals'] = false;
563                    break;
564
565                case '--static-backup':
566                    $this->arguments['backupStaticAttributes'] = true;
567                    break;
568
569                case 'v':
570                case '--verbose':
571                    $this->arguments['verbose'] = true;
572                    break;
573
574                case '--atleast-version':
575                    exit(version_compare(PHPUnit_Runner_Version::id(), $option[1], '>=')
576                        ? PHPUnit_TextUI_TestRunner::SUCCESS_EXIT
577                        : PHPUnit_TextUI_TestRunner::FAILURE_EXIT
578                    );
579                    break;
580
581                case '--version':
582                    $this->printVersionString();
583                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
584                    break;
585
586                case '--report-useless-tests':
587                    $this->arguments['reportUselessTests'] = true;
588                    break;
589
590                case '--strict-coverage':
591                    $this->arguments['strictCoverage'] = true;
592                    break;
593
594                case '--disable-coverage-ignore':
595                    $this->arguments['disableCodeCoverageIgnore'] = true;
596                    break;
597
598                case '--strict-global-state':
599                    $this->arguments['beStrictAboutChangesToGlobalState'] = true;
600                    break;
601
602                case '--disallow-test-output':
603                    $this->arguments['disallowTestOutput'] = true;
604                    break;
605
606                case '--disallow-resource-usage':
607                    $this->arguments['beStrictAboutResourceUsageDuringSmallTests'] = true;
608                    break;
609
610                case '--enforce-time-limit':
611                    $this->arguments['enforceTimeLimit'] = true;
612                    break;
613
614                case '--disallow-todo-tests':
615                    $this->arguments['disallowTodoAnnotatedTests'] = true;
616                    break;
617
618                case '--reverse-list':
619                    $this->arguments['reverseList'] = true;
620                    break;
621
622                case '--check-version':
623                    $this->handleVersionCheck();
624                    break;
625
626                case '--selfupdate':
627                case '--self-update':
628                    $this->handleSelfUpdate();
629                    break;
630
631                case '--selfupgrade':
632                case '--self-upgrade':
633                    $this->handleSelfUpdate(true);
634                    break;
635
636                case '--whitelist':
637                    $this->arguments['whitelist'] = $option[1];
638                    break;
639
640                default:
641                    $optionName = str_replace('--', '', $option[0]);
642
643                    $handler = null;
644                    if (isset($this->longOptions[$optionName])) {
645                        $handler = $this->longOptions[$optionName];
646                    } elseif (isset($this->longOptions[$optionName . '='])) {
647                        $handler = $this->longOptions[$optionName . '='];
648                    }
649
650                    if (isset($handler) && is_callable([$this, $handler])) {
651                        $this->$handler($option[1]);
652                    }
653            }
654        }
655
656        $this->handleCustomTestSuite();
657
658        if (!isset($this->arguments['test'])) {
659            if (isset($this->options[1][0])) {
660                $this->arguments['test'] = $this->options[1][0];
661            }
662
663            if (isset($this->options[1][1])) {
664                $this->arguments['testFile'] = realpath($this->options[1][1]);
665            } else {
666                $this->arguments['testFile'] = '';
667            }
668
669            if (isset($this->arguments['test']) &&
670                is_file($this->arguments['test']) &&
671                substr($this->arguments['test'], -5, 5) != '.phpt') {
672                $this->arguments['testFile'] = realpath($this->arguments['test']);
673                $this->arguments['test']     = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.'));
674            }
675        }
676
677        if (!isset($this->arguments['testSuffixes'])) {
678            $this->arguments['testSuffixes'] = ['Test.php', '.phpt'];
679        }
680
681        if (isset($includePath)) {
682            ini_set(
683                'include_path',
684                $includePath . PATH_SEPARATOR . ini_get('include_path')
685            );
686        }
687
688        if ($this->arguments['loader'] !== null) {
689            $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
690        }
691
692        if (isset($this->arguments['configuration']) &&
693            is_dir($this->arguments['configuration'])) {
694            $configurationFile = $this->arguments['configuration'] . '/phpunit.xml';
695
696            if (file_exists($configurationFile)) {
697                $this->arguments['configuration'] = realpath(
698                    $configurationFile
699                );
700            } elseif (file_exists($configurationFile . '.dist')) {
701                $this->arguments['configuration'] = realpath(
702                    $configurationFile . '.dist'
703                );
704            }
705        } elseif (!isset($this->arguments['configuration']) &&
706                  $this->arguments['useDefaultConfiguration']) {
707            if (file_exists('phpunit.xml')) {
708                $this->arguments['configuration'] = realpath('phpunit.xml');
709            } elseif (file_exists('phpunit.xml.dist')) {
710                $this->arguments['configuration'] = realpath(
711                    'phpunit.xml.dist'
712                );
713            }
714        }
715
716        if (isset($this->arguments['configuration'])) {
717            try {
718                $configuration = PHPUnit_Util_Configuration::getInstance(
719                    $this->arguments['configuration']
720                );
721            } catch (Throwable $e) {
722                print $e->getMessage() . "\n";
723                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
724            } catch (Exception $e) {
725                print $e->getMessage() . "\n";
726                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
727            }
728
729            $phpunitConfiguration = $configuration->getPHPUnitConfiguration();
730
731            $configuration->handlePHPConfiguration();
732
733            /*
734             * Issue #1216
735             */
736            if (isset($this->arguments['bootstrap'])) {
737                $this->handleBootstrap($this->arguments['bootstrap']);
738            } elseif (isset($phpunitConfiguration['bootstrap'])) {
739                $this->handleBootstrap($phpunitConfiguration['bootstrap']);
740            }
741
742            /*
743             * Issue #657
744             */
745            if (isset($phpunitConfiguration['stderr']) && ! isset($this->arguments['stderr'])) {
746                $this->arguments['stderr'] = $phpunitConfiguration['stderr'];
747            }
748
749            if (isset($phpunitConfiguration['extensionsDirectory']) && !isset($this->arguments['noExtensions']) && extension_loaded('phar')) {
750                $this->handleExtensions($phpunitConfiguration['extensionsDirectory']);
751            }
752
753            if (isset($phpunitConfiguration['columns']) && ! isset($this->arguments['columns'])) {
754                $this->arguments['columns'] = $phpunitConfiguration['columns'];
755            }
756
757            if (!isset($this->arguments['printer']) && isset($phpunitConfiguration['printerClass'])) {
758                if (isset($phpunitConfiguration['printerFile'])) {
759                    $file = $phpunitConfiguration['printerFile'];
760                } else {
761                    $file = '';
762                }
763
764                $this->arguments['printer'] = $this->handlePrinter(
765                    $phpunitConfiguration['printerClass'],
766                    $file
767                );
768            }
769
770            if (isset($phpunitConfiguration['testSuiteLoaderClass'])) {
771                if (isset($phpunitConfiguration['testSuiteLoaderFile'])) {
772                    $file = $phpunitConfiguration['testSuiteLoaderFile'];
773                } else {
774                    $file = '';
775                }
776
777                $this->arguments['loader'] = $this->handleLoader(
778                    $phpunitConfiguration['testSuiteLoaderClass'],
779                    $file
780                );
781            }
782
783            if (!isset($this->arguments['test'])) {
784                $testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null);
785
786                if ($testSuite !== null) {
787                    $this->arguments['test'] = $testSuite;
788                }
789            }
790        } elseif (isset($this->arguments['bootstrap'])) {
791            $this->handleBootstrap($this->arguments['bootstrap']);
792        }
793
794        if (isset($this->arguments['printer']) &&
795            is_string($this->arguments['printer'])) {
796            $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
797        }
798
799        if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') {
800            $test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']);
801
802            $this->arguments['test'] = new PHPUnit_Framework_TestSuite;
803            $this->arguments['test']->addTest($test);
804        }
805
806        if (!isset($this->arguments['test']) ||
807            (isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) {
808            $this->showHelp();
809            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
810        }
811    }
812
813    /**
814     * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
815     *
816     * @param string $loaderClass
817     * @param string $loaderFile
818     *
819     * @return PHPUnit_Runner_TestSuiteLoader
820     */
821    protected function handleLoader($loaderClass, $loaderFile = '')
822    {
823        if (!class_exists($loaderClass, false)) {
824            if ($loaderFile == '') {
825                $loaderFile = PHPUnit_Util_Filesystem::classNameToFilename(
826                    $loaderClass
827                );
828            }
829
830            $loaderFile = stream_resolve_include_path($loaderFile);
831
832            if ($loaderFile) {
833                require $loaderFile;
834            }
835        }
836
837        if (class_exists($loaderClass, false)) {
838            $class = new ReflectionClass($loaderClass);
839
840            if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
841                $class->isInstantiable()) {
842                return $class->newInstance();
843            }
844        }
845
846        if ($loaderClass == 'PHPUnit_Runner_StandardTestSuiteLoader') {
847            return;
848        }
849
850        $this->showError(
851            sprintf(
852                'Could not use "%s" as loader.',
853                $loaderClass
854            )
855        );
856    }
857
858    /**
859     * Handles the loading of the PHPUnit_Util_Printer implementation.
860     *
861     * @param string $printerClass
862     * @param string $printerFile
863     *
864     * @return PHPUnit_Util_Printer|string
865     */
866    protected function handlePrinter($printerClass, $printerFile = '')
867    {
868        if (!class_exists($printerClass, false)) {
869            if ($printerFile == '') {
870                $printerFile = PHPUnit_Util_Filesystem::classNameToFilename(
871                    $printerClass
872                );
873            }
874
875            $printerFile = stream_resolve_include_path($printerFile);
876
877            if ($printerFile) {
878                require $printerFile;
879            }
880        }
881
882        if (class_exists($printerClass)) {
883            $class = new ReflectionClass($printerClass);
884
885            if ($class->implementsInterface('PHPUnit_Framework_TestListener') &&
886                $class->isSubclassOf('PHPUnit_Util_Printer') &&
887                $class->isInstantiable()) {
888                if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) {
889                    return $printerClass;
890                }
891
892                $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
893
894                return $class->newInstance($outputStream);
895            }
896        }
897
898        $this->showError(
899            sprintf(
900                'Could not use "%s" as printer.',
901                $printerClass
902            )
903        );
904    }
905
906    /**
907     * Loads a bootstrap file.
908     *
909     * @param string $filename
910     */
911    protected function handleBootstrap($filename)
912    {
913        try {
914            PHPUnit_Util_Fileloader::checkAndLoad($filename);
915        } catch (PHPUnit_Framework_Exception $e) {
916            $this->showError($e->getMessage());
917        }
918    }
919
920    protected function handleSelfUpdate($upgrade = false)
921    {
922        $this->printVersionString();
923
924        if ($upgrade) {
925            print "Warning: Deprecated --self-upgrade used\n\n";
926        } else {
927            print "Warning: Deprecated --self-update used\n\n";
928        }
929
930        $localFilename = realpath($_SERVER['argv'][0]);
931
932        if (!is_writable($localFilename)) {
933            print 'No write permission to update ' . $localFilename . "\n";
934            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
935        }
936
937        if (!extension_loaded('openssl')) {
938            print "The OpenSSL extension is not loaded.\n";
939            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
940        }
941
942        if (!$upgrade) {
943            $remoteFilename = sprintf(
944                'https://phar.phpunit.de/phpunit-%s.phar',
945                file_get_contents(
946                    sprintf(
947                        'https://phar.phpunit.de/latest-version-of/phpunit-%s',
948                        PHPUnit_Runner_Version::series()
949                    )
950                )
951            );
952        } else {
953            $remoteFilename = sprintf(
954                'https://phar.phpunit.de/phpunit%s.phar',
955                PHPUnit_Runner_Version::getReleaseChannel()
956            );
957        }
958
959        $tempFilename = tempnam(sys_get_temp_dir(), 'phpunit') . '.phar';
960
961        // Workaround for https://bugs.php.net/bug.php?id=65538
962        $caFile = dirname($tempFilename) . '/ca.pem';
963        copy(__PHPUNIT_PHAR_ROOT__ . '/ca.pem', $caFile);
964
965        print 'Updating the PHPUnit PHAR ... ';
966
967        $options = [
968            'ssl' => [
969                'allow_self_signed' => false,
970                'cafile'            => $caFile,
971                'verify_peer'       => true
972            ]
973        ];
974
975        file_put_contents(
976            $tempFilename,
977            file_get_contents(
978                $remoteFilename,
979                false,
980                stream_context_create($options)
981            )
982        );
983
984        chmod($tempFilename, 0777 & ~umask());
985
986        try {
987            $phar = new Phar($tempFilename);
988            unset($phar);
989            rename($tempFilename, $localFilename);
990            unlink($caFile);
991        } catch (Throwable $_e) {
992            $e = $_e;
993        } catch (Exception $_e) {
994            $e = $_e;
995        }
996
997        if (isset($e)) {
998            unlink($caFile);
999            unlink($tempFilename);
1000            print " done\n\n" . $e->getMessage() . "\n";
1001            exit(2);
1002        }
1003
1004        print " done\n";
1005        exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
1006    }
1007
1008    protected function handleVersionCheck()
1009    {
1010        $this->printVersionString();
1011
1012        $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
1013        $isOutdated    = version_compare($latestVersion, PHPUnit_Runner_Version::id(), '>');
1014
1015        if ($isOutdated) {
1016            print "You are not using the latest version of PHPUnit.\n";
1017            print 'Use "phpunit --self-upgrade" to install PHPUnit ' . $latestVersion . "\n";
1018        } else {
1019            print "You are using the latest version of PHPUnit.\n";
1020        }
1021
1022        exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
1023    }
1024
1025    /**
1026     * Show the help message.
1027     */
1028    protected function showHelp()
1029    {
1030        $this->printVersionString();
1031
1032        print <<<EOT
1033Usage: phpunit [options] UnitTest [UnitTest.php]
1034       phpunit [options] <directory>
1035
1036Code Coverage Options:
1037
1038  --coverage-clover <file>  Generate code coverage report in Clover XML format.
1039  --coverage-crap4j <file>  Generate code coverage report in Crap4J XML format.
1040  --coverage-html <dir>     Generate code coverage report in HTML format.
1041  --coverage-php <file>     Export PHP_CodeCoverage object to file.
1042  --coverage-text=<file>    Generate code coverage report in text format.
1043                            Default: Standard output.
1044  --coverage-xml <dir>      Generate code coverage report in PHPUnit XML format.
1045  --whitelist <dir>         Whitelist <dir> for code coverage analysis.
1046  --disable-coverage-ignore Disable annotations for ignoring code coverage.
1047
1048Logging Options:
1049
1050  --log-junit <file>        Log test execution in JUnit XML format to file.
1051  --log-teamcity <file>     Log test execution in TeamCity format to file.
1052  --testdox-html <file>     Write agile documentation in HTML format to file.
1053  --testdox-text <file>     Write agile documentation in Text format to file.
1054  --testdox-xml <file>      Write agile documentation in XML format to file.
1055  --reverse-list            Print defects in reverse order
1056
1057Test Selection Options:
1058
1059  --filter <pattern>        Filter which tests to run.
1060  --testsuite <name>        Filter which testsuite to run.
1061  --group ...               Only runs tests from the specified group(s).
1062  --exclude-group ...       Exclude tests from the specified group(s).
1063  --list-groups             List available test groups.
1064  --list-suites             List available test suites.
1065  --test-suffix ...         Only search for test in files with specified
1066                            suffix(es). Default: Test.php,.phpt
1067
1068Test Execution Options:
1069
1070  --report-useless-tests    Be strict about tests that do not test anything.
1071  --strict-coverage         Be strict about @covers annotation usage.
1072  --strict-global-state     Be strict about changes to global state
1073  --disallow-test-output    Be strict about output during tests.
1074  --disallow-resource-usage Be strict about resource usage during small tests.
1075  --enforce-time-limit      Enforce time limit based on test size.
1076  --disallow-todo-tests     Disallow @todo-annotated tests.
1077
1078  --process-isolation       Run each test in a separate PHP process.
1079  --no-globals-backup       Do not backup and restore \$GLOBALS for each test.
1080  --static-backup           Backup and restore static attributes for each test.
1081
1082  --colors=<flag>           Use colors in output ("never", "auto" or "always").
1083  --columns <n>             Number of columns to use for progress output.
1084  --columns max             Use maximum number of columns for progress output.
1085  --stderr                  Write to STDERR instead of STDOUT.
1086  --stop-on-error           Stop execution upon first error.
1087  --stop-on-failure         Stop execution upon first error or failure.
1088  --stop-on-warning         Stop execution upon first warning.
1089  --stop-on-risky           Stop execution upon first risky test.
1090  --stop-on-skipped         Stop execution upon first skipped test.
1091  --stop-on-incomplete      Stop execution upon first incomplete test.
1092  --fail-on-warning         Treat tests with warnings as failures.
1093  --fail-on-risky           Treat risky tests as failures.
1094  -v|--verbose              Output more verbose information.
1095  --debug                   Display debugging information during test execution.
1096
1097  --loader <loader>         TestSuiteLoader implementation to use.
1098  --repeat <times>          Runs the test(s) repeatedly.
1099  --teamcity                Report test execution progress in TeamCity format.
1100  --testdox                 Report test execution progress in TestDox format.
1101  --testdox-group           Only include tests from the specified group(s).
1102  --testdox-exclude-group   Exclude tests from the specified group(s).
1103  --printer <printer>       TestListener implementation to use.
1104
1105Configuration Options:
1106
1107  --bootstrap <file>        A "bootstrap" PHP file that is run before the tests.
1108  -c|--configuration <file> Read configuration from XML file.
1109  --no-configuration        Ignore default configuration file (phpunit.xml).
1110  --no-coverage             Ignore code coverage configuration.
1111  --no-extensions           Do not load PHPUnit extensions.
1112  --include-path <path(s)>  Prepend PHP's include_path with given path(s).
1113  -d key[=value]            Sets a php.ini value.
1114  --generate-configuration  Generate configuration file with suggested settings.
1115
1116Miscellaneous Options:
1117
1118  -h|--help                 Prints this usage information.
1119  --version                 Prints the version and exits.
1120  --atleast-version <min>   Checks that version is greater than min and exits.
1121
1122EOT;
1123
1124        if (defined('__PHPUNIT_PHAR__')) {
1125            print "\n  --check-version           Check whether PHPUnit is the latest version.";
1126        }
1127    }
1128
1129    /**
1130     * Custom callback for test suite discovery.
1131     */
1132    protected function handleCustomTestSuite()
1133    {
1134    }
1135
1136    private function printVersionString()
1137    {
1138        if ($this->versionStringPrinted) {
1139            return;
1140        }
1141
1142        print PHPUnit_Runner_Version::getVersionString() . "\n\n";
1143
1144        $this->versionStringPrinted = true;
1145    }
1146
1147    /**
1148     * @param string $message
1149     */
1150    private function showError($message)
1151    {
1152        $this->printVersionString();
1153
1154        print $message . "\n";
1155
1156        exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
1157    }
1158
1159    /**
1160     * @param string $directory
1161     */
1162    private function handleExtensions($directory)
1163    {
1164        $facade = new File_Iterator_Facade;
1165
1166        foreach ($facade->getFilesAsArray($directory, '.phar') as $file) {
1167            require $file;
1168
1169            $this->arguments['loadedExtensions'][] = $file;
1170        }
1171    }
1172}
1173