3 * This file is part of PHPUnit.
5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code.
12 * A TestRunner for the Command Line Interface (CLI)
15 * @since Class available since Release 3.0.0
17 class PHPUnit_TextUI_Command
22 protected $arguments = array(
23 'listGroups' => false,
25 'useDefaultConfiguration' => true
31 protected $options = array();
36 protected $longOptions = array(
40 'configuration=' => null,
41 'coverage-clover=' => null,
42 'coverage-crap4j=' => null,
43 'coverage-html=' => null,
44 'coverage-php=' => null,
45 'coverage-text==' => null,
46 'coverage-xml=' => null,
48 'exclude-group=' => null,
53 'include-path=' => null,
54 'list-groups' => null,
59 'process-isolation' => null,
62 'stop-on-error' => null,
63 'stop-on-failure' => null,
64 'stop-on-incomplete' => null,
65 'stop-on-risky' => null,
66 'stop-on-skipped' => null,
67 'report-useless-tests' => null,
68 'strict-coverage' => null,
69 'disallow-test-output' => null,
70 'enforce-time-limit' => null,
71 'disallow-todo-tests' => null,
72 'strict-global-state' => null,
76 'testdox-html=' => null,
77 'testdox-text=' => null,
78 'test-suffix=' => null,
79 'no-configuration' => null,
80 'no-coverage' => null,
81 'no-globals-backup' => null,
83 'static-backup' => null,
91 private $versionStringPrinted = false;
96 public static function main($exit = true)
98 $command = new static;
100 return $command->run($_SERVER['argv'], $exit);
109 public function run(array $argv, $exit = true)
111 $this->handleArguments($argv);
113 $runner = $this->createRunner();
115 if (is_object($this->arguments['test']) &&
116 $this->arguments['test'] instanceof PHPUnit_Framework_Test) {
117 $suite = $this->arguments['test'];
119 $suite = $runner->getTest(
120 $this->arguments['test'],
121 $this->arguments['testFile'],
122 $this->arguments['testSuffixes']
126 if ($this->arguments['listGroups']) {
127 $this->printVersionString();
129 print "Available test group(s):\n";
131 $groups = $suite->getGroups();
134 foreach ($groups as $group) {
139 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
141 return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
145 unset($this->arguments['test']);
146 unset($this->arguments['testFile']);
149 $result = $runner->doRun($suite, $this->arguments);
150 } catch (PHPUnit_Framework_Exception $e) {
151 print $e->getMessage() . "\n";
154 $ret = PHPUnit_TextUI_TestRunner::FAILURE_EXIT;
156 if (isset($result) && $result->wasSuccessful()) {
157 $ret = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
158 } elseif (!isset($result) || $result->errorCount() > 0) {
159 $ret = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT;
170 * Create a TestRunner, override in subclasses.
172 * @return PHPUnit_TextUI_TestRunner
174 * @since Method available since Release 3.6.0
176 protected function createRunner()
178 return new PHPUnit_TextUI_TestRunner($this->arguments['loader']);
182 * Handles the command-line arguments.
184 * A child class of PHPUnit_TextUI_Command can hook into the argument
185 * parsing by adding the switch(es) to the $longOptions array and point to a
186 * callback method that handles the switch(es) in the child class like this
190 * class MyCommand extends PHPUnit_TextUI_Command
192 * public function __construct()
194 * // my-switch won't accept a value, it's an on/off
195 * $this->longOptions['my-switch'] = 'myHandler';
196 * // my-secondswitch will accept a value - note the equals sign
197 * $this->longOptions['my-secondswitch='] = 'myOtherHandler';
200 * // --my-switch -> myHandler()
201 * protected function myHandler()
205 * // --my-secondswitch foo -> myOtherHandler('foo')
206 * protected function myOtherHandler ($value)
210 * // You will also need this - the static keyword in the
211 * // PHPUnit_TextUI_Command will mean that it'll be
212 * // PHPUnit_TextUI_Command that gets instantiated,
214 * public static function main($exit = true)
216 * $command = new static;
218 * return $command->run($_SERVER['argv'], $exit);
226 protected function handleArguments(array $argv)
228 if (defined('__PHPUNIT_PHAR__')) {
229 $this->longOptions['check-version'] = null;
230 $this->longOptions['selfupdate'] = null;
231 $this->longOptions['self-update'] = null;
232 $this->longOptions['selfupgrade'] = null;
233 $this->longOptions['self-upgrade'] = null;
237 $this->options = PHPUnit_Util_Getopt::getopt(
240 array_keys($this->longOptions)
242 } catch (PHPUnit_Framework_Exception $e) {
243 $this->showError($e->getMessage());
246 foreach ($this->options[0] as $option) {
247 switch ($option[0]) {
249 $this->arguments['colors'] = $option[1] ?: PHPUnit_TextUI_ResultPrinter::COLOR_AUTO;
253 $this->arguments['bootstrap'] = $option[1];
257 if (is_numeric($option[1])) {
258 $this->arguments['columns'] = (int) $option[1];
259 } elseif ($option[1] == 'max') {
260 $this->arguments['columns'] = 'max';
265 case '--configuration':
266 $this->arguments['configuration'] = $option[1];
269 case '--coverage-clover':
270 $this->arguments['coverageClover'] = $option[1];
273 case '--coverage-crap4j':
274 $this->arguments['coverageCrap4J'] = $option[1];
277 case '--coverage-html':
278 $this->arguments['coverageHtml'] = $option[1];
281 case '--coverage-php':
282 $this->arguments['coveragePHP'] = $option[1];
285 case '--coverage-text':
286 if ($option[1] === null) {
287 $option[1] = 'php://stdout';
290 $this->arguments['coverageText'] = $option[1];
291 $this->arguments['coverageTextShowUncoveredFiles'] = false;
292 $this->arguments['coverageTextShowOnlySummary'] = false;
295 case '--coverage-xml':
296 $this->arguments['coverageXml'] = $option[1];
300 $ini = explode('=', $option[1]);
302 if (isset($ini[0])) {
303 if (isset($ini[1])) {
304 ini_set($ini[0], $ini[1]);
306 ini_set($ini[0], true);
312 $this->arguments['debug'] = true;
318 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
322 $this->arguments['filter'] = $option[1];
326 $this->arguments['testsuite'] = $option[1];
330 $this->arguments['groups'] = explode(',', $option[1]);
333 case '--exclude-group':
334 $this->arguments['excludeGroups'] = explode(
340 case '--test-suffix':
341 $this->arguments['testSuffixes'] = explode(
347 case '--include-path':
348 $includePath = $option[1];
351 case '--list-groups':
352 $this->arguments['listGroups'] = true;
356 $this->arguments['printer'] = $option[1];
360 $this->arguments['loader'] = $option[1];
364 $this->arguments['jsonLogfile'] = $option[1];
368 $this->arguments['junitLogfile'] = $option[1];
372 $this->arguments['tapLogfile'] = $option[1];
375 case '--process-isolation':
376 $this->arguments['processIsolation'] = true;
380 $this->arguments['repeat'] = (int) $option[1];
384 $this->arguments['stderr'] = true;
387 case '--stop-on-error':
388 $this->arguments['stopOnError'] = true;
391 case '--stop-on-failure':
392 $this->arguments['stopOnFailure'] = true;
395 case '--stop-on-incomplete':
396 $this->arguments['stopOnIncomplete'] = true;
399 case '--stop-on-risky':
400 $this->arguments['stopOnRisky'] = true;
403 case '--stop-on-skipped':
404 $this->arguments['stopOnSkipped'] = true;
408 $this->arguments['printer'] = 'PHPUnit_Util_Log_TAP';
412 $this->arguments['printer'] = 'PHPUnit_Util_TestDox_ResultPrinter_Text';
415 case '--testdox-html':
416 $this->arguments['testdoxHTMLFile'] = $option[1];
419 case '--testdox-text':
420 $this->arguments['testdoxTextFile'] = $option[1];
423 case '--no-configuration':
424 $this->arguments['useDefaultConfiguration'] = false;
427 case '--no-coverage':
428 $this->arguments['noCoverage'] = true;
431 case '--no-globals-backup':
432 $this->arguments['backupGlobals'] = false;
435 case '--static-backup':
436 $this->arguments['backupStaticAttributes'] = true;
441 $this->arguments['verbose'] = true;
445 $this->printVersionString();
446 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
449 case '--report-useless-tests':
450 $this->arguments['reportUselessTests'] = true;
453 case '--strict-coverage':
454 $this->arguments['strictCoverage'] = true;
457 case '--strict-global-state':
458 $this->arguments['disallowChangesToGlobalState'] = true;
461 case '--disallow-test-output':
462 $this->arguments['disallowTestOutput'] = true;
465 case '--enforce-time-limit':
466 $this->arguments['enforceTimeLimit'] = true;
469 case '--disallow-todo-tests':
470 $this->arguments['disallowTodoAnnotatedTests'] = true;
474 $this->arguments['reportUselessTests'] = true;
475 $this->arguments['strictCoverage'] = true;
476 $this->arguments['disallowTestOutput'] = true;
477 $this->arguments['enforceTimeLimit'] = true;
478 $this->arguments['disallowTodoAnnotatedTests'] = true;
479 $this->arguments['deprecatedStrictModeOption'] = true;
482 case '--check-version':
483 $this->handleVersionCheck();
487 case '--self-update':
488 $this->handleSelfUpdate();
491 case '--selfupgrade':
492 case '--self-upgrade':
493 $this->handleSelfUpdate(true);
497 $this->arguments['whitelist'] = $option[1];
501 $optionName = str_replace('--', '', $option[0]);
503 if (isset($this->longOptions[$optionName])) {
504 $handler = $this->longOptions[$optionName];
505 } elseif (isset($this->longOptions[$optionName . '='])) {
506 $handler = $this->longOptions[$optionName . '='];
509 if (isset($handler) && is_callable(array($this, $handler))) {
510 $this->$handler($option[1]);
515 $this->handleCustomTestSuite();
517 if (!isset($this->arguments['test'])) {
518 if (isset($this->options[1][0])) {
519 $this->arguments['test'] = $this->options[1][0];
522 if (isset($this->options[1][1])) {
523 $this->arguments['testFile'] = realpath($this->options[1][1]);
525 $this->arguments['testFile'] = '';
528 if (isset($this->arguments['test']) &&
529 is_file($this->arguments['test']) &&
530 substr($this->arguments['test'], -5, 5) != '.phpt') {
531 $this->arguments['testFile'] = realpath($this->arguments['test']);
532 $this->arguments['test'] = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.'));
536 if (!isset($this->arguments['testSuffixes'])) {
537 $this->arguments['testSuffixes'] = array('Test.php', '.phpt');
540 if (isset($includePath)) {
543 $includePath . PATH_SEPARATOR . ini_get('include_path')
547 if ($this->arguments['loader'] !== null) {
548 $this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
551 if (isset($this->arguments['configuration']) &&
552 is_dir($this->arguments['configuration'])) {
553 $configurationFile = $this->arguments['configuration'] . '/phpunit.xml';
555 if (file_exists($configurationFile)) {
556 $this->arguments['configuration'] = realpath(
559 } elseif (file_exists($configurationFile . '.dist')) {
560 $this->arguments['configuration'] = realpath(
561 $configurationFile . '.dist'
564 } elseif (!isset($this->arguments['configuration']) &&
565 $this->arguments['useDefaultConfiguration']) {
566 if (file_exists('phpunit.xml')) {
567 $this->arguments['configuration'] = realpath('phpunit.xml');
568 } elseif (file_exists('phpunit.xml.dist')) {
569 $this->arguments['configuration'] = realpath(
575 if (isset($this->arguments['configuration'])) {
577 $configuration = PHPUnit_Util_Configuration::getInstance(
578 $this->arguments['configuration']
580 } catch (Throwable $e) {
581 print $e->getMessage() . "\n";
582 exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
583 } catch (Exception $e) {
584 print $e->getMessage() . "\n";
585 exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
588 $phpunit = $configuration->getPHPUnitConfiguration();
590 $configuration->handlePHPConfiguration();
595 if (isset($this->arguments['bootstrap'])) {
596 $this->handleBootstrap($this->arguments['bootstrap']);
597 } elseif (isset($phpunit['bootstrap'])) {
598 $this->handleBootstrap($phpunit['bootstrap']);
604 if (isset($phpunit['stderr']) && ! isset($this->arguments['stderr'])) {
605 $this->arguments['stderr'] = $phpunit['stderr'];
608 if (isset($phpunit['columns']) && ! isset($this->arguments['columns'])) {
609 $this->arguments['columns'] = $phpunit['columns'];
612 if (isset($phpunit['printerClass'])) {
613 if (isset($phpunit['printerFile'])) {
614 $file = $phpunit['printerFile'];
619 $this->arguments['printer'] = $this->handlePrinter(
620 $phpunit['printerClass'],
625 if (isset($phpunit['testSuiteLoaderClass'])) {
626 if (isset($phpunit['testSuiteLoaderFile'])) {
627 $file = $phpunit['testSuiteLoaderFile'];
632 $this->arguments['loader'] = $this->handleLoader(
633 $phpunit['testSuiteLoaderClass'],
638 $browsers = $configuration->getSeleniumBrowserConfiguration();
640 if (!empty($browsers)) {
641 $this->arguments['deprecatedSeleniumConfiguration'] = true;
643 if (class_exists('PHPUnit_Extensions_SeleniumTestCase')) {
644 PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers;
648 if (!isset($this->arguments['test'])) {
649 $testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null);
651 if ($testSuite !== null) {
652 $this->arguments['test'] = $testSuite;
655 } elseif (isset($this->arguments['bootstrap'])) {
656 $this->handleBootstrap($this->arguments['bootstrap']);
659 if (isset($this->arguments['printer']) &&
660 is_string($this->arguments['printer'])) {
661 $this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
664 if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') {
665 $test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']);
667 $this->arguments['test'] = new PHPUnit_Framework_TestSuite;
668 $this->arguments['test']->addTest($test);
671 if (!isset($this->arguments['test']) ||
672 (isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) {
674 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
679 * Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
681 * @param string $loaderClass
682 * @param string $loaderFile
684 * @return PHPUnit_Runner_TestSuiteLoader
686 protected function handleLoader($loaderClass, $loaderFile = '')
688 if (!class_exists($loaderClass, false)) {
689 if ($loaderFile == '') {
690 $loaderFile = PHPUnit_Util_Filesystem::classNameToFilename(
695 $loaderFile = stream_resolve_include_path($loaderFile);
702 if (class_exists($loaderClass, false)) {
703 $class = new ReflectionClass($loaderClass);
705 if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
706 $class->isInstantiable()) {
707 return $class->newInstance();
711 if ($loaderClass == 'PHPUnit_Runner_StandardTestSuiteLoader') {
717 'Could not use "%s" as loader.',
724 * Handles the loading of the PHPUnit_Util_Printer implementation.
726 * @param string $printerClass
727 * @param string $printerFile
729 * @return PHPUnit_Util_Printer|string
731 protected function handlePrinter($printerClass, $printerFile = '')
733 if (!class_exists($printerClass, false)) {
734 if ($printerFile == '') {
735 $printerFile = PHPUnit_Util_Filesystem::classNameToFilename(
740 $printerFile = stream_resolve_include_path($printerFile);
743 require $printerFile;
747 if (class_exists($printerClass)) {
748 $class = new ReflectionClass($printerClass);
750 if ($class->implementsInterface('PHPUnit_Framework_TestListener') &&
751 $class->isSubclassOf('PHPUnit_Util_Printer') &&
752 $class->isInstantiable()) {
753 if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) {
754 return $printerClass;
757 $outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
759 return $class->newInstance($outputStream);
765 'Could not use "%s" as printer.',
772 * Loads a bootstrap file.
774 * @param string $filename
776 protected function handleBootstrap($filename)
779 PHPUnit_Util_Fileloader::checkAndLoad($filename);
780 } catch (PHPUnit_Framework_Exception $e) {
781 $this->showError($e->getMessage());
786 * @since Method available since Release 4.0.0
788 protected function handleSelfUpdate($upgrade = false)
790 $this->printVersionString();
792 $localFilename = realpath($_SERVER['argv'][0]);
794 if (!is_writable($localFilename)) {
795 print 'No write permission to update ' . $localFilename . "\n";
796 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
799 if (!extension_loaded('openssl')) {
800 print "The OpenSSL extension is not loaded.\n";
801 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
805 $remoteFilename = sprintf(
806 'https://phar.phpunit.de/phpunit-%s.phar',
809 'https://phar.phpunit.de/latest-version-of/phpunit-%s',
810 PHPUnit_Runner_Version::series()
815 $remoteFilename = sprintf(
816 'https://phar.phpunit.de/phpunit%s.phar',
817 PHPUnit_Runner_Version::getReleaseChannel()
821 $tempFilename = tempnam(sys_get_temp_dir(), 'phpunit') . '.phar';
823 // Workaround for https://bugs.php.net/bug.php?id=65538
824 $caFile = dirname($tempFilename) . '/ca.pem';
825 copy(__PHPUNIT_PHAR_ROOT__ . '/ca.pem', $caFile);
827 print 'Updating the PHPUnit PHAR ... ';
831 'allow_self_signed' => false,
833 'verify_peer' => true
837 if (PHP_VERSION_ID < 50600) {
838 $options['ssl']['CN_match'] = 'phar.phpunit.de';
839 $options['ssl']['SNI_server_name'] = 'phar.phpunit.de';
847 stream_context_create($options)
851 chmod($tempFilename, 0777 & ~umask());
854 $phar = new Phar($tempFilename);
856 rename($tempFilename, $localFilename);
858 } catch (Throwable $_e) {
860 } catch (Exception $_e) {
866 unlink($tempFilename);
867 print " done\n\n" . $e->getMessage() . "\n";
872 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
876 * @since Method available since Release 4.8.0
878 protected function handleVersionCheck()
880 $this->printVersionString();
882 $latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
883 $isOutdated = version_compare($latestVersion, PHPUnit_Runner_Version::id(), '>');
886 print "You are not using the latest version of PHPUnit.\n";
887 print 'Use "phpunit --self-upgrade" to install PHPUnit ' . $latestVersion . "\n";
889 print "You are using the latest version of PHPUnit.\n";
892 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
896 * Show the help message.
898 protected function showHelp()
900 $this->printVersionString();
903 Usage: phpunit [options] UnitTest [UnitTest.php]
904 phpunit [options] <directory>
906 Code Coverage Options:
908 --coverage-clover <file> Generate code coverage report in Clover XML format.
909 --coverage-crap4j <file> Generate code coverage report in Crap4J XML format.
910 --coverage-html <dir> Generate code coverage report in HTML format.
911 --coverage-php <file> Export PHP_CodeCoverage object to file.
912 --coverage-text=<file> Generate code coverage report in text format.
913 Default: Standard output.
914 --coverage-xml <dir> Generate code coverage report in PHPUnit XML format.
918 --log-junit <file> Log test execution in JUnit XML format to file.
919 --log-tap <file> Log test execution in TAP format to file.
920 --log-json <file> Log test execution in JSON format.
921 --testdox-html <file> Write agile documentation in HTML format to file.
922 --testdox-text <file> Write agile documentation in Text format to file.
924 Test Selection Options:
926 --filter <pattern> Filter which tests to run.
927 --testsuite <name> Filter which testsuite to run.
928 --group ... Only runs tests from the specified group(s).
929 --exclude-group ... Exclude tests from the specified group(s).
930 --list-groups List available test groups.
931 --test-suffix ... Only search for test in files with specified
932 suffix(es). Default: Test.php,.phpt
934 Test Execution Options:
936 --report-useless-tests Be strict about tests that do not test anything.
937 --strict-coverage Be strict about unintentionally covered code.
938 --strict-global-state Be strict about changes to global state
939 --disallow-test-output Be strict about output during tests.
940 --enforce-time-limit Enforce time limit based on test size.
941 --disallow-todo-tests Disallow @todo-annotated tests.
943 --process-isolation Run each test in a separate PHP process.
944 --no-globals-backup Do not backup and restore \$GLOBALS for each test.
945 --static-backup Backup and restore static attributes for each test.
947 --colors=<flag> Use colors in output ("never", "auto" or "always").
948 --columns <n> Number of columns to use for progress output.
949 --columns max Use maximum number of columns for progress output.
950 --stderr Write to STDERR instead of STDOUT.
951 --stop-on-error Stop execution upon first error.
952 --stop-on-failure Stop execution upon first error or failure.
953 --stop-on-risky Stop execution upon first risky test.
954 --stop-on-skipped Stop execution upon first skipped test.
955 --stop-on-incomplete Stop execution upon first incomplete test.
956 -v|--verbose Output more verbose information.
957 --debug Display debugging information during test execution.
959 --loader <loader> TestSuiteLoader implementation to use.
960 --repeat <times> Runs the test(s) repeatedly.
961 --tap Report test execution progress in TAP format.
962 --testdox Report test execution progress in TestDox format.
963 --printer <printer> TestListener implementation to use.
965 Configuration Options:
967 --bootstrap <file> A "bootstrap" PHP file that is run before the tests.
968 -c|--configuration <file> Read configuration from XML file.
969 --no-configuration Ignore default configuration file (phpunit.xml).
970 --no-coverage Ignore code coverage configuration.
971 --include-path <path(s)> Prepend PHP's include_path with given path(s).
972 -d key[=value] Sets a php.ini value.
974 Miscellaneous Options:
976 -h|--help Prints this usage information.
977 --version Prints the version and exits.
981 if (defined('__PHPUNIT_PHAR__')) {
982 print "\n --check-version Check whether PHPUnit is the latest version.";
983 print "\n --self-update Update PHPUnit to the latest version within the same\n release series.\n";
984 print "\n --self-upgrade Upgrade PHPUnit to the latest version.\n";
989 * Custom callback for test suite discovery.
991 protected function handleCustomTestSuite()
995 private function printVersionString()
997 if ($this->versionStringPrinted) {
1001 print PHPUnit_Runner_Version::getVersionString() . "\n\n";
1003 $this->versionStringPrinted = true;
1008 private function showError($message)
1010 $this->printVersionString();
1012 print $message . "\n";
1014 exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);