1<?php
2/**
3 * PHPUnit
4 *
5 * Copyright (c) 2001-2012, Sebastian Bergmann <sebastian@phpunit.de>.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 *   * Redistributions of source code must retain the above copyright
13 *     notice, this list of conditions and the following disclaimer.
14 *
15 *   * Redistributions in binary form must reproduce the above copyright
16 *     notice, this list of conditions and the following disclaimer in
17 *     the documentation and/or other materials provided with the
18 *     distribution.
19 *
20 *   * Neither the name of Sebastian Bergmann nor the names of his
21 *     contributors may be used to endorse or promote products derived
22 *     from this software without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
36 *
37 * @package    PHPUnit
38 * @subpackage TextUI
39 * @author     Sebastian Bergmann <sebastian@phpunit.de>
40 * @copyright  2001-2012 Sebastian Bergmann <sebastian@phpunit.de>
41 * @license    http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
42 * @link       http://www.phpunit.de/
43 * @since      File available since Release 3.0.0
44 */
45
46/**
47 * A TestRunner for the Command Line Interface (CLI)
48 * PHP SAPI Module.
49 *
50 * @package    PHPUnit
51 * @subpackage TextUI
52 * @author     Sebastian Bergmann <sebastian@phpunit.de>
53 * @copyright  2001-2012 Sebastian Bergmann <sebastian@phpunit.de>
54 * @license    http://www.opensource.org/licenses/BSD-3-Clause  The BSD 3-Clause License
55 * @version    Release: @package_version@
56 * @link       http://www.phpunit.de/
57 * @since      Class available since Release 3.0.0
58 */
59class PHPUnit_TextUI_Command
60{
61    /**
62     * @var array
63     */
64    protected $arguments = array(
65      'listGroups'              => FALSE,
66      'loader'                  => NULL,
67      'useDefaultConfiguration' => TRUE
68    );
69
70    /**
71     * @var array
72     */
73    protected $options = array();
74
75    /**
76     * @var array
77     */
78    protected $longOptions = array(
79      'colors' => NULL,
80      'bootstrap=' => NULL,
81      'configuration=' => NULL,
82      'coverage-html=' => NULL,
83      'coverage-clover=' => NULL,
84      'coverage-php=' => NULL,
85      'coverage-text==' => NULL,
86      'debug' => NULL,
87      'exclude-group=' => NULL,
88      'filter=' => NULL,
89      'testsuite=' => NULL,
90      'group=' => NULL,
91      'help' => NULL,
92      'include-path=' => NULL,
93      'list-groups' => NULL,
94      'loader=' => NULL,
95      'log-json=' => NULL,
96      'log-junit=' => NULL,
97      'log-tap=' => NULL,
98      'process-isolation' => NULL,
99      'repeat=' => NULL,
100      'stderr' => NULL,
101      'stop-on-error' => NULL,
102      'stop-on-failure' => NULL,
103      'stop-on-incomplete' => NULL,
104      'stop-on-skipped' => NULL,
105      'strict' => NULL,
106      'tap' => NULL,
107      'testdox' => NULL,
108      'testdox-html=' => NULL,
109      'testdox-text=' => NULL,
110      'test-suffix=' => NULL,
111      'no-configuration' => NULL,
112      'no-globals-backup' => NULL,
113      'printer=' => NULL,
114      'static-backup' => NULL,
115      'verbose' => NULL,
116      'version' => NULL
117    );
118
119    /**
120     * @var array
121     */
122    protected $missingExtensions = array();
123
124    /**
125     * @param boolean $exit
126     */
127    public static function main($exit = TRUE)
128    {
129        $command = new PHPUnit_TextUI_Command;
130        return $command->run($_SERVER['argv'], $exit);
131    }
132
133    /**
134     * @param array   $argv
135     * @param boolean $exit
136     */
137    public function run(array $argv, $exit = TRUE)
138    {
139        $this->handleArguments($argv);
140
141        $runner = $this->createRunner();
142
143        if (is_object($this->arguments['test']) &&
144            $this->arguments['test'] instanceof PHPUnit_Framework_Test) {
145            $suite = $this->arguments['test'];
146        } else {
147            $suite = $runner->getTest(
148              $this->arguments['test'],
149              $this->arguments['testFile'],
150              $this->arguments['testSuffixes']
151            );
152        }
153
154        if ($this->arguments['listGroups']) {
155            PHPUnit_TextUI_TestRunner::printVersionString();
156
157            print "Available test group(s):\n";
158
159            $groups = $suite->getGroups();
160            sort($groups);
161
162            foreach ($groups as $group) {
163                print " - $group\n";
164            }
165
166            if ($exit) {
167                exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
168            } else {
169                return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
170            }
171        }
172
173        unset($this->arguments['test']);
174        unset($this->arguments['testFile']);
175
176        try {
177            $result = $runner->doRun($suite, $this->arguments);
178        }
179
180        catch (PHPUnit_Framework_Exception $e) {
181            print $e->getMessage() . "\n";
182        }
183
184        $ret = PHPUnit_TextUI_TestRunner::FAILURE_EXIT;
185
186        if (isset($result) && $result->wasSuccessful()) {
187            $ret = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
188        }
189
190        else if (!isset($result) || $result->errorCount() > 0) {
191            $ret = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT;
192        }
193
194        if ($exit) {
195            exit($ret);
196        } else {
197            return $ret;
198        }
199    }
200
201    /**
202     * Create a TestRunner, override in subclasses.
203     *
204     * @return PHPUnit_TextUI_TestRunner
205     * @since  Method available since Release 3.6.0
206     */
207    protected function createRunner()
208    {
209        return new PHPUnit_TextUI_TestRunner($this->arguments['loader']);
210    }
211
212    /**
213     * Handles the command-line arguments.
214     *
215     * A child class of PHPUnit_TextUI_Command can hook into the argument
216     * parsing by adding the switch(es) to the $longOptions array and point to a
217     * callback method that handles the switch(es) in the child class like this
218     *
219     * <code>
220     * <?php
221     * class MyCommand extends PHPUnit_TextUI_Command
222     * {
223     *     public function __construct()
224     *     {
225     *         $this->longOptions['--my-switch'] = 'myHandler';
226     *     }
227     *
228     *     // --my-switch foo -> myHandler('foo')
229     *     protected function myHandler($value)
230     *     {
231     *     }
232     * }
233     * </code>
234     *
235     * @param array $argv
236     */
237    protected function handleArguments(array $argv)
238    {
239        try {
240            $this->options = PHPUnit_Util_Getopt::getopt(
241              $argv,
242              'd:c:hv',
243              array_keys($this->longOptions)
244            );
245        }
246
247        catch (PHPUnit_Framework_Exception $e) {
248            PHPUnit_TextUI_TestRunner::showError($e->getMessage());
249        }
250
251        foreach ($this->options[0] as $option) {
252            switch ($option[0]) {
253                case '--colors': {
254                    $this->arguments['colors'] = TRUE;
255                }
256                break;
257
258                case '--bootstrap': {
259                    $this->arguments['bootstrap'] = $option[1];
260                }
261                break;
262
263                case 'c':
264                case '--configuration': {
265                    $this->arguments['configuration'] = $option[1];
266                }
267                break;
268
269                case '--coverage-clover':
270                case '--coverage-html':
271                case '--coverage-php':
272                case '--coverage-text': {
273                    if (!extension_loaded('tokenizer')) {
274                        $this->showExtensionNotLoadedMessage(
275                          'tokenizer', 'No code coverage will be generated.'
276                        );
277
278                        continue;
279                    }
280
281                    if (!extension_loaded('xdebug')) {
282                        $this->showExtensionNotLoadedMessage(
283                          'Xdebug', 'No code coverage will be generated.'
284                        );
285
286                        continue;
287                    }
288
289                    switch ($option[0]) {
290                        case '--coverage-clover': {
291                            $this->arguments['coverageClover'] = $option[1];
292                        }
293                        break;
294
295                        case '--coverage-html': {
296                            $this->arguments['reportDirectory'] = $option[1];
297                        }
298                        break;
299
300                        case '--coverage-php': {
301                            $this->arguments['coveragePHP'] = $option[1];
302                        }
303                        break;
304
305                        case '--coverage-text': {
306                            if ($option[1] === NULL) {
307                                $option[1] = 'php://stdout';
308                            }
309
310                            $this->arguments['coverageText'] = $option[1];
311                            $this->arguments['coverageTextShowUncoveredFiles'] = FALSE;
312                        }
313                        break;
314                    }
315                }
316                break;
317
318                case 'd': {
319                    $ini = explode('=', $option[1]);
320
321                    if (isset($ini[0])) {
322                        if (isset($ini[1])) {
323                            ini_set($ini[0], $ini[1]);
324                        } else {
325                            ini_set($ini[0], TRUE);
326                        }
327                    }
328                }
329                break;
330
331                case '--debug': {
332                    $this->arguments['debug'] = TRUE;
333                }
334                break;
335
336                case 'h':
337                case '--help': {
338                    $this->showHelp();
339                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
340                }
341                break;
342
343                case '--filter': {
344                    $this->arguments['filter'] = $option[1];
345                }
346                break;
347
348                case '--testsuite': {
349                    $this->arguments['testsuite'] = $option[1];
350                }
351                break;
352
353                case '--group': {
354                    $this->arguments['groups'] = explode(',', $option[1]);
355                }
356                break;
357
358                case '--exclude-group': {
359                    $this->arguments['excludeGroups'] = explode(
360                      ',', $option[1]
361                    );
362                }
363                break;
364
365                case '--test-suffix': {
366                    $this->arguments['testSuffixes'] = explode(
367                      ',', $option[1]
368                    );
369                }
370                break;
371
372                case '--include-path': {
373                    $includePath = $option[1];
374                }
375                break;
376
377                case '--list-groups': {
378                    $this->arguments['listGroups'] = TRUE;
379                }
380                break;
381
382                case '--printer': {
383                    $this->arguments['printer'] = $option[1];
384                }
385                break;
386
387                case '--loader': {
388                    $this->arguments['loader'] = $option[1];
389                }
390                break;
391
392                case '--log-json': {
393                    $this->arguments['jsonLogfile'] = $option[1];
394                }
395                break;
396
397                case '--log-junit': {
398                    $this->arguments['junitLogfile'] = $option[1];
399                }
400                break;
401
402                case '--log-tap': {
403                    $this->arguments['tapLogfile'] = $option[1];
404                }
405                break;
406
407                case '--process-isolation': {
408                    $this->arguments['processIsolation'] = TRUE;
409                }
410                break;
411
412                case '--repeat': {
413                    $this->arguments['repeat'] = (int)$option[1];
414                }
415                break;
416
417                case '--stderr': {
418                    $this->arguments['printer'] = new PHPUnit_TextUI_ResultPrinter(
419                      'php://stderr',
420                      isset($this->arguments['verbose']) ? $this->arguments['verbose'] : FALSE
421                    );
422                }
423                break;
424
425                case '--stop-on-error': {
426                    $this->arguments['stopOnError'] = TRUE;
427                }
428                break;
429
430                case '--stop-on-failure': {
431                    $this->arguments['stopOnFailure'] = TRUE;
432                }
433                break;
434
435                case '--stop-on-incomplete': {
436                    $this->arguments['stopOnIncomplete'] = TRUE;
437                }
438                break;
439
440                case '--stop-on-skipped': {
441                    $this->arguments['stopOnSkipped'] = TRUE;
442                }
443                break;
444
445                case '--tap': {
446                    $this->arguments['printer'] = new PHPUnit_Util_Log_TAP;
447                }
448                break;
449
450                case '--testdox': {
451                    $this->arguments['printer'] = new PHPUnit_Util_TestDox_ResultPrinter_Text;
452                }
453                break;
454
455                case '--testdox-html': {
456                    $this->arguments['testdoxHTMLFile'] = $option[1];
457                }
458                break;
459
460                case '--testdox-text': {
461                    $this->arguments['testdoxTextFile'] = $option[1];
462                }
463                break;
464
465                case '--no-configuration': {
466                    $this->arguments['useDefaultConfiguration'] = FALSE;
467                }
468                break;
469
470                case '--no-globals-backup': {
471                    $this->arguments['backupGlobals'] = FALSE;
472                }
473                break;
474
475                case '--static-backup': {
476                    $this->arguments['backupStaticAttributes'] = TRUE;
477                }
478                break;
479
480                case 'v':
481                case '--verbose': {
482                    $this->arguments['verbose'] = TRUE;
483                }
484                break;
485
486                case '--version': {
487                    PHPUnit_TextUI_TestRunner::printVersionString();
488                    exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
489                }
490                break;
491
492                case '--strict': {
493                    $this->arguments['strict'] = TRUE;
494                }
495                break;
496
497                default: {
498                    $optionName = str_replace('--', '', $option[0]);
499
500                    if (isset($this->longOptions[$optionName])) {
501                        $handler = $this->longOptions[$optionName];
502                    }
503
504                    else if (isset($this->longOptions[$optionName . '='])) {
505                        $handler = $this->longOptions[$optionName . '='];
506                    }
507
508                    if (isset($handler) && is_callable(array($this, $handler))) {
509                        $this->$handler($option[1]);
510                    }
511                }
512            }
513        }
514
515        $this->handleCustomTestSuite();
516
517        if (!isset($this->arguments['test'])) {
518            if (count($this->options[1]) > 2) {
519                $this->showMessage(
520                    'More than two positional arguments provided.',
521                    FALSE
522                );
523                $this->showHelp();
524                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
525            }
526
527            if (isset($this->options[1][0])) {
528                $this->arguments['test'] = $this->options[1][0];
529            }
530
531            if (isset($this->options[1][1])) {
532                $this->arguments['testFile'] = $this->options[1][1];
533            } else {
534                $this->arguments['testFile'] = '';
535            }
536
537            if (isset($this->arguments['test']) &&
538                is_file($this->arguments['test']) &&
539                substr($this->arguments['test'], -5, 5) != '.phpt') {
540                $this->arguments['testFile'] = realpath($this->arguments['test']);
541                $this->arguments['test']     = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.'));
542            }
543        }
544
545        if (!isset($this->arguments['testSuffixes'])) {
546            $this->arguments['testSuffixes'] = array('Test.php', '.phpt');
547        }
548
549        if (isset($includePath)) {
550            ini_set(
551              'include_path',
552              $includePath . PATH_SEPARATOR . ini_get('include_path')
553            );
554        }
555
556        if (isset($this->arguments['bootstrap'])) {
557            $this->handleBootstrap($this->arguments['bootstrap']);
558        }
559
560        if (isset($this->arguments['printer']) &&
561            is_string($this->arguments['printer'])) {
562            $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
563        }
564
565        if ($this->arguments['loader'] !== NULL) {
566            $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
567        }
568
569        if (isset($this->arguments['configuration']) &&
570            is_dir($this->arguments['configuration'])) {
571            $configurationFile = $this->arguments['configuration'] .
572                                 '/phpunit.xml';
573
574            if (file_exists($configurationFile)) {
575                $this->arguments['configuration'] = realpath(
576                  $configurationFile
577                );
578            }
579
580            else if (file_exists($configurationFile . '.dist')) {
581                $this->arguments['configuration'] = realpath(
582                  $configurationFile . '.dist'
583                );
584            }
585        }
586
587        else if (!isset($this->arguments['configuration']) &&
588                 $this->arguments['useDefaultConfiguration']) {
589            if (file_exists('phpunit.xml')) {
590                $this->arguments['configuration'] = realpath('phpunit.xml');
591            } else if (file_exists('phpunit.xml.dist')) {
592                $this->arguments['configuration'] = realpath(
593                  'phpunit.xml.dist'
594                );
595            }
596        }
597
598        if (isset($this->arguments['configuration'])) {
599            try {
600                $configuration = PHPUnit_Util_Configuration::getInstance(
601                  $this->arguments['configuration']
602                );
603            }
604
605            catch (Exception $e) {
606                print $e->getMessage() . "\n";
607                exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
608            }
609
610            $phpunit = $configuration->getPHPUnitConfiguration();
611
612            $configuration->handlePHPConfiguration();
613
614            if (!isset($this->arguments['bootstrap']) && isset($phpunit['bootstrap'])) {
615                $this->handleBootstrap($phpunit['bootstrap']);
616            }
617
618            if (isset($phpunit['printerClass'])) {
619                if (isset($phpunit['printerFile'])) {
620                    $file = $phpunit['printerFile'];
621                } else {
622                    $file = '';
623                }
624
625                $this->arguments['printer'] = $this->handlePrinter(
626                  $phpunit['printerClass'], $file
627                );
628            }
629
630            if (isset($phpunit['testSuiteLoaderClass'])) {
631                if (isset($phpunit['testSuiteLoaderFile'])) {
632                    $file = $phpunit['testSuiteLoaderFile'];
633                } else {
634                    $file = '';
635                }
636
637                $this->arguments['loader'] = $this->handleLoader(
638                  $phpunit['testSuiteLoaderClass'], $file
639                );
640            }
641
642            $logging = $configuration->getLoggingConfiguration();
643
644            if (isset($logging['coverage-html']) || isset($logging['coverage-clover']) || isset($logging['coverage-text']) ) {
645                if (!extension_loaded('tokenizer')) {
646                    $this->showExtensionNotLoadedMessage(
647                      'tokenizer', 'No code coverage will be generated.'
648                    );
649                }
650
651                else if (!extension_loaded('Xdebug')) {
652                    $this->showExtensionNotLoadedMessage(
653                      'Xdebug', 'No code coverage will be generated.'
654                    );
655                }
656            }
657
658            $browsers = $configuration->getSeleniumBrowserConfiguration();
659
660            if (!empty($browsers) &&
661                class_exists('PHPUnit_Extensions_SeleniumTestCase')) {
662                PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers;
663            }
664
665            if (!isset($this->arguments['test'])) {
666                $testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null);
667
668                if ($testSuite !== NULL) {
669                    $this->arguments['test'] = $testSuite;
670                }
671            }
672        }
673
674        if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') {
675            $test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']);
676
677            $this->arguments['test'] = new PHPUnit_Framework_TestSuite;
678            $this->arguments['test']->addTest($test);
679        }
680
681        if (!isset($this->arguments['test']) ||
682            (isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) {
683            $this->showHelp();
684            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
685        }
686    }
687
688    /**
689     * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
690     *
691     * @param  string  $loaderClass
692     * @param  string  $loaderFile
693     * @return PHPUnit_Runner_TestSuiteLoader
694     */
695    protected function handleLoader($loaderClass, $loaderFile = '')
696    {
697        if (!class_exists($loaderClass, FALSE)) {
698            if ($loaderFile == '') {
699                $loaderFile = PHPUnit_Util_Filesystem::classNameToFilename(
700                  $loaderClass
701                );
702            }
703
704            $loaderFile = stream_resolve_include_path($loaderFile);
705
706            if ($loaderFile) {
707                require $loaderFile;
708            }
709        }
710
711        if (class_exists($loaderClass, FALSE)) {
712            $class = new ReflectionClass($loaderClass);
713
714            if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
715                $class->isInstantiable()) {
716                $loader = $class->newInstance();
717            }
718        }
719
720        if (!isset($loader)) {
721            PHPUnit_TextUI_TestRunner::showError(
722              sprintf(
723                'Could not use "%s" as loader.',
724
725                $loaderClass
726              )
727            );
728        }
729
730        return $loader;
731    }
732
733    /**
734     * Handles the loading of the PHPUnit_Util_Printer implementation.
735     *
736     * @param  string $printerClass
737     * @param  string $printerFile
738     * @return PHPUnit_Util_Printer
739     */
740    protected function handlePrinter($printerClass, $printerFile = '')
741    {
742        if (!class_exists($printerClass, FALSE)) {
743            if ($printerFile == '') {
744                $printerFile = PHPUnit_Util_Filesystem::classNameToFilename(
745                  $printerClass
746                );
747            }
748
749            $printerFile = stream_resolve_include_path($printerFile);
750
751            if ($printerFile) {
752                require $printerFile;
753            }
754        }
755
756        if (class_exists($printerClass, FALSE)) {
757            $class = new ReflectionClass($printerClass);
758
759            if ($class->implementsInterface('PHPUnit_Framework_TestListener') &&
760                $class->isSubclassOf('PHPUnit_Util_Printer') &&
761                $class->isInstantiable()) {
762                $printer = $class->newInstance();
763            }
764        }
765
766        if (!isset($printer)) {
767            PHPUnit_TextUI_TestRunner::showError(
768              sprintf(
769                'Could not use "%s" as printer.',
770
771                $printerClass
772              )
773            );
774        }
775
776        return $printer;
777    }
778
779    /**
780     * Loads a bootstrap file.
781     *
782     * @param string $filename
783     */
784    protected function handleBootstrap($filename)
785    {
786        try {
787            PHPUnit_Util_Fileloader::checkAndLoad($filename);
788        }
789
790        catch (PHPUnit_Framework_Exception $e) {
791            PHPUnit_TextUI_TestRunner::showError($e->getMessage());
792        }
793    }
794
795    /**
796     * @param string  $message
797     * @since Method available since Release 3.6.0
798     */
799    protected function showExtensionNotLoadedMessage($extension, $message = '')
800    {
801        if (isset($this->missingExtensions[$extension])) {
802            return;
803        }
804
805        if (!empty($message)) {
806            $message = ' ' . $message;
807        }
808
809        $this->showMessage(
810          'The ' . $extension . ' extension is not loaded.' . $message . "\n",
811          FALSE
812        );
813
814        $this->missingExtensions[$extension] = TRUE;
815    }
816
817    /**
818     * Shows a message.
819     *
820     * @param string  $message
821     * @param boolean $exit
822     */
823    protected function showMessage($message, $exit = TRUE)
824    {
825        PHPUnit_TextUI_TestRunner::printVersionString();
826        print $message . "\n";
827
828        if ($exit) {
829            exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
830        } else {
831            print "\n";
832        }
833    }
834
835    /**
836     * Show the help message.
837     */
838    protected function showHelp()
839    {
840        PHPUnit_TextUI_TestRunner::printVersionString();
841
842        print <<<EOT
843Usage: phpunit [switches] UnitTest [UnitTest.php]
844       phpunit [switches] <directory>
845
846  --log-junit <file>        Log test execution in JUnit XML format to file.
847  --log-tap <file>          Log test execution in TAP format to file.
848  --log-json <file>         Log test execution in JSON format.
849
850  --coverage-clover <file>  Generate code coverage report in Clover XML format.
851  --coverage-html <dir>     Generate code coverage report in HTML format.
852  --coverage-php <file>     Serialize PHP_CodeCoverage object to file.
853  --coverage-text=<file>    Generate code coverage report in text format.
854                            Default to writing to the standard output.
855
856  --testdox-html <file>     Write agile documentation in HTML format to file.
857  --testdox-text <file>     Write agile documentation in Text format to file.
858
859  --filter <pattern>        Filter which tests to run.
860  --testsuite <pattern>     Filter which testsuite to run.
861  --group ...               Only runs tests from the specified group(s).
862  --exclude-group ...       Exclude tests from the specified group(s).
863  --list-groups             List available test groups.
864  --test-suffix ...         Only search for test in files with specified
865                            suffix(es). Default: Test.php,.phpt
866
867  --loader <loader>         TestSuiteLoader implementation to use.
868  --printer <printer>       TestSuiteListener implementation to use.
869  --repeat <times>          Runs the test(s) repeatedly.
870
871  --tap                     Report test execution progress in TAP format.
872  --testdox                 Report test execution progress in TestDox format.
873
874  --colors                  Use colors in output.
875  --stderr                  Write to STDERR instead of STDOUT.
876  --stop-on-error           Stop execution upon first error.
877  --stop-on-failure         Stop execution upon first error or failure.
878  --stop-on-skipped         Stop execution upon first skipped test.
879  --stop-on-incomplete      Stop execution upon first incomplete test.
880  --strict                  Run tests in strict mode.
881  -v|--verbose              Output more verbose information.
882  --debug                   Display debugging information during test execution.
883
884  --process-isolation       Run each test in a separate PHP process.
885  --no-globals-backup       Do not backup and restore \$GLOBALS for each test.
886  --static-backup           Backup and restore static attributes for each test.
887
888  --bootstrap <file>        A "bootstrap" PHP file that is run before the tests.
889  -c|--configuration <file> Read configuration from XML file.
890  --no-configuration        Ignore default configuration file (phpunit.xml).
891  --include-path <path(s)>  Prepend PHP's include_path with given path(s).
892  -d key[=value]            Sets a php.ini value.
893
894  -h|--help                 Prints this usage information.
895  --version                 Prints the version and exits.
896
897EOT;
898    }
899
900    /**
901     * Custom callback for test suite discovery.
902     */
903    protected function handleCustomTestSuite()
904    {
905    }
906}
907