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)
17
* @author Sebastian Bergmann <sebastian@phpunit.de>
18
* @copyright Sebastian Bergmann <sebastian@phpunit.de>
19
* @license http://www.opensource.org/licenses/BSD-3-Clause The BSD 3-Clause License
20
* @link http://www.phpunit.de/
21
* @since Class available since Release 3.0.0
23
class PHPUnit_TextUI_Command
28
protected $arguments = array(
29
'listGroups' => false,
31
'useDefaultConfiguration' => true
37
protected $options = array();
42
protected $longOptions = array(
45
'configuration=' => null,
46
'coverage-clover=' => null,
47
'coverage-crap4j=' => null,
48
'coverage-html=' => null,
49
'coverage-php=' => null,
50
'coverage-text==' => null,
51
'coverage-xml=' => null,
53
'exclude-group=' => null,
58
'include-path=' => null,
59
'list-groups' => null,
64
'process-isolation' => null,
67
'stop-on-error' => null,
68
'stop-on-failure' => null,
69
'stop-on-incomplete' => null,
70
'stop-on-risky' => null,
71
'stop-on-skipped' => null,
72
'report-useless-tests' => null,
73
'strict-coverage' => null,
74
'disallow-test-output' => null,
75
'enforce-time-limit' => null,
76
'disallow-todo-tests' => null,
80
'testdox-html=' => null,
81
'testdox-text=' => null,
82
'test-suffix=' => null,
83
'no-configuration' => null,
84
'no-globals-backup' => null,
86
'static-backup' => null,
94
private $versionStringPrinted = false;
97
* @param boolean $exit
99
public static function main($exit = true)
101
$command = new static;
103
return $command->run($_SERVER['argv'], $exit);
108
* @param boolean $exit
111
public function run(array $argv, $exit = true)
113
$this->handleArguments($argv);
115
$runner = $this->createRunner();
117
if (is_object($this->arguments['test']) &&
118
$this->arguments['test'] instanceof PHPUnit_Framework_Test) {
119
$suite = $this->arguments['test'];
121
$suite = $runner->getTest(
122
$this->arguments['test'],
123
$this->arguments['testFile'],
124
$this->arguments['testSuffixes']
128
if ($this->arguments['listGroups']) {
129
$this->printVersionString();
131
print "Available test group(s):\n";
133
$groups = $suite->getGroups();
136
foreach ($groups as $group) {
141
exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
143
return PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
147
unset($this->arguments['test']);
148
unset($this->arguments['testFile']);
151
$result = $runner->doRun($suite, $this->arguments);
152
} catch (PHPUnit_Framework_Exception $e) {
153
print $e->getMessage() . "\n";
156
$ret = PHPUnit_TextUI_TestRunner::FAILURE_EXIT;
158
if (isset($result) && $result->wasSuccessful()) {
159
$ret = PHPUnit_TextUI_TestRunner::SUCCESS_EXIT;
160
} elseif (!isset($result) || $result->errorCount() > 0) {
161
$ret = PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT;
172
* Create a TestRunner, override in subclasses.
174
* @return PHPUnit_TextUI_TestRunner
175
* @since Method available since Release 3.6.0
177
protected function createRunner()
179
return new PHPUnit_TextUI_TestRunner($this->arguments['loader']);
183
* Handles the command-line arguments.
185
* A child class of PHPUnit_TextUI_Command can hook into the argument
186
* parsing by adding the switch(es) to the $longOptions array and point to a
187
* callback method that handles the switch(es) in the child class like this
191
* class MyCommand extends PHPUnit_TextUI_Command
193
* public function __construct()
195
* // my-switch won't accept a value, it's an on/off
196
* $this->longOptions['my-switch'] = 'myHandler';
197
* // my-secondswitch will accept a value - note the equals sign
198
* $this->longOptions['my-secondswitch='] = 'myOtherHandler';
201
* // --my-switch -> myHandler()
202
* protected function myHandler()
206
* // --my-secondswitch foo -> myOtherHandler('foo')
207
* protected function myOtherHandler ($value)
211
* // You will also need this - the static keyword in the
212
* // PHPUnit_TextUI_Command will mean that it'll be
213
* // PHPUnit_TextUI_Command that gets instantiated,
215
* public static function main($exit = true)
217
* $command = new static;
219
* return $command->run($_SERVER['argv'], $exit);
227
protected function handleArguments(array $argv)
229
if (defined('__PHPUNIT_PHAR__')) {
230
$this->longOptions['selfupdate'] = null;
231
$this->longOptions['self-update'] = null;
235
$this->options = PHPUnit_Util_Getopt::getopt(
238
array_keys($this->longOptions)
240
} catch (PHPUnit_Framework_Exception $e) {
241
$this->showError($e->getMessage());
244
foreach ($this->options[0] as $option) {
245
switch ($option[0]) {
247
$this->arguments['colors'] = true;
251
case '--bootstrap': {
252
$this->arguments['bootstrap'] = $option[1];
257
case '--configuration': {
258
$this->arguments['configuration'] = $option[1];
262
case '--coverage-clover': {
263
$this->arguments['coverageClover'] = $option[1];
267
case '--coverage-crap4j': {
268
$this->arguments['coverageCrap4J'] = $option[1];
272
case '--coverage-html': {
273
$this->arguments['coverageHtml'] = $option[1];
277
case '--coverage-php': {
278
$this->arguments['coveragePHP'] = $option[1];
282
case '--coverage-text': {
283
if ($option[1] === null) {
284
$option[1] = 'php://stdout';
287
$this->arguments['coverageText'] = $option[1];
288
$this->arguments['coverageTextShowUncoveredFiles'] = false;
289
$this->arguments['coverageTextShowOnlySummary'] = false;
293
case '--coverage-xml': {
294
$this->arguments['coverageXml'] = $option[1];
299
$ini = explode('=', $option[1]);
301
if (isset($ini[0])) {
302
if (isset($ini[1])) {
303
ini_set($ini[0], $ini[1]);
305
ini_set($ini[0], true);
312
$this->arguments['debug'] = true;
319
exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
324
$this->arguments['filter'] = $option[1];
328
case '--testsuite': {
329
$this->arguments['testsuite'] = $option[1];
334
$this->arguments['groups'] = explode(',', $option[1]);
338
case '--exclude-group': {
339
$this->arguments['excludeGroups'] = explode(
345
case '--test-suffix': {
346
$this->arguments['testSuffixes'] = explode(
352
case '--include-path': {
353
$includePath = $option[1];
357
case '--list-groups': {
358
$this->arguments['listGroups'] = true;
363
$this->arguments['printer'] = $option[1];
368
$this->arguments['loader'] = $option[1];
373
$this->arguments['jsonLogfile'] = $option[1];
377
case '--log-junit': {
378
$this->arguments['junitLogfile'] = $option[1];
383
$this->arguments['tapLogfile'] = $option[1];
387
case '--process-isolation': {
388
$this->arguments['processIsolation'] = true;
393
$this->arguments['repeat'] = (int) $option[1];
398
$this->arguments['stderr'] = true;
402
case '--stop-on-error': {
403
$this->arguments['stopOnError'] = true;
407
case '--stop-on-failure': {
408
$this->arguments['stopOnFailure'] = true;
412
case '--stop-on-incomplete': {
413
$this->arguments['stopOnIncomplete'] = true;
417
case '--stop-on-risky': {
418
$this->arguments['stopOnRisky'] = true;
422
case '--stop-on-skipped': {
423
$this->arguments['stopOnSkipped'] = true;
428
$this->arguments['printer'] = 'PHPUnit_Util_Log_TAP';
433
$this->arguments['printer'] = 'PHPUnit_Util_TestDox_ResultPrinter_Text';
437
case '--testdox-html': {
438
$this->arguments['testdoxHTMLFile'] = $option[1];
442
case '--testdox-text': {
443
$this->arguments['testdoxTextFile'] = $option[1];
447
case '--no-configuration': {
448
$this->arguments['useDefaultConfiguration'] = false;
452
case '--no-globals-backup': {
453
$this->arguments['backupGlobals'] = false;
457
case '--static-backup': {
458
$this->arguments['backupStaticAttributes'] = true;
464
$this->arguments['verbose'] = true;
469
$this->printVersionString();
470
exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
474
case '--report-useless-tests': {
475
$this->arguments['reportUselessTests'] = true;
479
case '--strict-coverage': {
480
$this->arguments['strictCoverage'] = true;
484
case '--disallow-test-output': {
485
$this->arguments['disallowTestOutput'] = true;
489
case '--enforce-time-limit': {
490
$this->arguments['enforceTimeLimit'] = true;
494
case '--disallow-todo-tests': {
495
$this->arguments['disallowTodoAnnotatedTests'] = true;
500
$this->arguments['reportUselessTests'] = true;
501
$this->arguments['strictCoverage'] = true;
502
$this->arguments['disallowTestOutput'] = true;
503
$this->arguments['enforceTimeLimit'] = true;
504
$this->arguments['disallowTodoAnnotatedTests'] = true;
509
case '--self-update': {
510
$this->handleSelfUpdate();
515
$optionName = str_replace('--', '', $option[0]);
517
if (isset($this->longOptions[$optionName])) {
518
$handler = $this->longOptions[$optionName];
519
} elseif (isset($this->longOptions[$optionName . '='])) {
520
$handler = $this->longOptions[$optionName . '='];
523
if (isset($handler) && is_callable(array($this, $handler))) {
524
$this->$handler($option[1]);
530
$this->handleCustomTestSuite();
532
if (!isset($this->arguments['test'])) {
533
if (isset($this->options[1][0])) {
534
$this->arguments['test'] = $this->options[1][0];
537
if (isset($this->options[1][1])) {
538
$this->arguments['testFile'] = realpath($this->options[1][1]);
540
$this->arguments['testFile'] = '';
543
if (isset($this->arguments['test']) &&
544
is_file($this->arguments['test']) &&
545
substr($this->arguments['test'], -5, 5) != '.phpt') {
546
$this->arguments['testFile'] = realpath($this->arguments['test']);
547
$this->arguments['test'] = substr($this->arguments['test'], 0, strrpos($this->arguments['test'], '.'));
551
if (!isset($this->arguments['testSuffixes'])) {
552
$this->arguments['testSuffixes'] = array('Test.php', '.phpt');
555
if (isset($includePath)) {
558
$includePath . PATH_SEPARATOR . ini_get('include_path')
562
if ($this->arguments['loader'] !== null) {
563
$this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
566
if (isset($this->arguments['configuration']) &&
567
is_dir($this->arguments['configuration'])) {
568
$configurationFile = $this->arguments['configuration'] .
571
if (file_exists($configurationFile)) {
572
$this->arguments['configuration'] = realpath(
575
} elseif (file_exists($configurationFile . '.dist')) {
576
$this->arguments['configuration'] = realpath(
577
$configurationFile . '.dist'
580
} elseif (!isset($this->arguments['configuration']) &&
581
$this->arguments['useDefaultConfiguration']) {
582
if (file_exists('phpunit.xml')) {
583
$this->arguments['configuration'] = realpath('phpunit.xml');
584
} elseif (file_exists('phpunit.xml.dist')) {
585
$this->arguments['configuration'] = realpath(
591
if (isset($this->arguments['configuration'])) {
593
$configuration = PHPUnit_Util_Configuration::getInstance(
594
$this->arguments['configuration']
596
} catch (Exception $e) {
597
print $e->getMessage() . "\n";
598
exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
601
$phpunit = $configuration->getPHPUnitConfiguration();
603
$configuration->handlePHPConfiguration();
608
if (isset($this->arguments['bootstrap'])) {
609
$this->handleBootstrap($this->arguments['bootstrap']);
610
} elseif (isset($phpunit['bootstrap'])) {
611
$this->handleBootstrap($phpunit['bootstrap']);
617
if (isset($phpunit['stderr']) && ! isset($this->arguments['stderr'])) {
618
$this->arguments['stderr'] = $phpunit['stderr'];
621
if (isset($phpunit['printerClass'])) {
622
if (isset($phpunit['printerFile'])) {
623
$file = $phpunit['printerFile'];
628
$this->arguments['printer'] = $this->handlePrinter(
629
$phpunit['printerClass'], $file
633
if (isset($phpunit['testSuiteLoaderClass'])) {
634
if (isset($phpunit['testSuiteLoaderFile'])) {
635
$file = $phpunit['testSuiteLoaderFile'];
640
$this->arguments['loader'] = $this->handleLoader(
641
$phpunit['testSuiteLoaderClass'], $file
645
$browsers = $configuration->getSeleniumBrowserConfiguration();
647
if (!empty($browsers) &&
648
class_exists('PHPUnit_Extensions_SeleniumTestCase')) {
649
PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers;
652
if (!isset($this->arguments['test'])) {
653
$testSuite = $configuration->getTestSuiteConfiguration(isset($this->arguments['testsuite']) ? $this->arguments['testsuite'] : null);
655
if ($testSuite !== null) {
656
$this->arguments['test'] = $testSuite;
659
} elseif (isset($this->arguments['bootstrap'])) {
660
$this->handleBootstrap($this->arguments['bootstrap']);
663
if (isset($this->arguments['printer']) &&
664
is_string($this->arguments['printer'])) {
665
$this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
668
if (isset($this->arguments['test']) && is_string($this->arguments['test']) && substr($this->arguments['test'], -5, 5) == '.phpt') {
669
$test = new PHPUnit_Extensions_PhptTestCase($this->arguments['test']);
671
$this->arguments['test'] = new PHPUnit_Framework_TestSuite;
672
$this->arguments['test']->addTest($test);
675
if (!isset($this->arguments['test']) ||
676
(isset($this->arguments['testDatabaseLogRevision']) && !isset($this->arguments['testDatabaseDSN']))) {
678
exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
683
* Handles the loading of the PHPUnit_Runner_TestSuiteLoader implementation.
685
* @param string $loaderClass
686
* @param string $loaderFile
687
* @return PHPUnit_Runner_TestSuiteLoader
689
protected function handleLoader($loaderClass, $loaderFile = '')
691
if (!class_exists($loaderClass, false)) {
692
if ($loaderFile == '') {
693
$loaderFile = PHPUnit_Util_Filesystem::classNameToFilename(
698
$loaderFile = stream_resolve_include_path($loaderFile);
705
if (class_exists($loaderClass, false)) {
706
$class = new ReflectionClass($loaderClass);
708
if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
709
$class->isInstantiable()) {
710
return $class->newInstance();
714
if ($loaderClass == 'PHPUnit_Runner_StandardTestSuiteLoader') {
720
'Could not use "%s" as loader.',
727
* Handles the loading of the PHPUnit_Util_Printer implementation.
729
* @param string $printerClass
730
* @param string $printerFile
731
* @return PHPUnit_Util_Printer
733
protected function handlePrinter($printerClass, $printerFile = '')
735
if (!class_exists($printerClass, false)) {
736
if ($printerFile == '') {
737
$printerFile = PHPUnit_Util_Filesystem::classNameToFilename(
742
$printerFile = stream_resolve_include_path($printerFile);
745
require $printerFile;
749
if (class_exists($printerClass)) {
750
$class = new ReflectionClass($printerClass);
752
if ($class->implementsInterface('PHPUnit_Framework_TestListener') &&
753
$class->isSubclassOf('PHPUnit_Util_Printer') &&
754
$class->isInstantiable()) {
755
if ($class->isSubclassOf('PHPUnit_TextUI_ResultPrinter')) {
756
return $printerClass;
759
$outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
761
return $class->newInstance($outputStream);
767
'Could not use "%s" as printer.',
774
* Loads a bootstrap file.
776
* @param string $filename
778
protected function handleBootstrap($filename)
781
PHPUnit_Util_Fileloader::checkAndLoad($filename);
782
} catch (PHPUnit_Framework_Exception $e) {
783
$this->showError($e->getMessage());
788
* @since Method available since Release 4.0.0
790
protected function handleSelfUpdate()
792
$this->printVersionString();
794
if (!extension_loaded('openssl')) {
795
print "The OpenSSL extension is not loaded.\n";
796
exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
799
$remoteFilename = sprintf(
800
'https://phar.phpunit.de/phpunit%s.phar',
801
PHPUnit_Runner_Version::getReleaseChannel()
804
$localFilename = realpath($_SERVER['argv'][0]);
805
$tempFilename = basename($localFilename, '.phar') . '-temp.phar';
807
// Workaround for https://bugs.php.net/bug.php?id=65538
808
$caFile = dirname($tempFilename) . '/ca.pem';
809
copy(__PHPUNIT_PHAR_ROOT__ . '/ca.pem', $caFile);
811
print 'Updating the PHPUnit PHAR ... ';
815
'allow_self_signed' => false,
817
'verify_peer' => true
821
if (PHP_VERSION_ID < 50600) {
822
$options['ssl']['CN_match'] = 'phar.phpunit.de';
823
$options['ssl']['SNI_server_name'] = 'phar.phpunit.de';
831
stream_context_create($options)
835
chmod($tempFilename, 0777 & ~umask());
838
$phar = new Phar($tempFilename);
840
rename($tempFilename, $localFilename);
842
} catch (Exception $e) {
844
unlink($tempFilename);
845
print " done\n\n" . $e->getMessage() . "\n";
854
* Show the help message.
856
protected function showHelp()
858
$this->printVersionString();
861
Usage: phpunit [options] UnitTest [UnitTest.php]
862
phpunit [options] <directory>
864
Code Coverage Options:
866
--coverage-clover <file> Generate code coverage report in Clover XML format.
867
--coverage-crap4j <file> Generate code coverage report in Crap4J XML format.
868
--coverage-html <dir> Generate code coverage report in HTML format.
869
--coverage-php <file> Export PHP_CodeCoverage object to file.
870
--coverage-text=<file> Generate code coverage report in text format.
871
Default: Standard output.
872
--coverage-xml <dir> Generate code coverage report in PHPUnit XML format.
876
--log-junit <file> Log test execution in JUnit XML format to file.
877
--log-tap <file> Log test execution in TAP format to file.
878
--log-json <file> Log test execution in JSON format.
879
--testdox-html <file> Write agile documentation in HTML format to file.
880
--testdox-text <file> Write agile documentation in Text format to file.
882
Test Selection Options:
884
--filter <pattern> Filter which tests to run.
885
--testsuite <pattern> Filter which testsuite to run.
886
--group ... Only runs tests from the specified group(s).
887
--exclude-group ... Exclude tests from the specified group(s).
888
--list-groups List available test groups.
889
--test-suffix ... Only search for test in files with specified
890
suffix(es). Default: Test.php,.phpt
892
Test Execution Options:
894
--report-useless-tests Be strict about tests that do not test anything.
895
--strict-coverage Be strict about unintentionally covered code.
896
--disallow-test-output Be strict about output during tests.
897
--enforce-time-limit Enforce time limit based on test size.
898
--disallow-todo-tests Disallow @todo-annotated tests.
899
--strict Run tests in strict mode (enables all of the above).
901
--process-isolation Run each test in a separate PHP process.
902
--no-globals-backup Do not backup and restore \$GLOBALS for each test.
903
--static-backup Backup and restore static attributes for each test.
905
--colors Use colors in output.
906
--stderr Write to STDERR instead of STDOUT.
907
--stop-on-error Stop execution upon first error.
908
--stop-on-failure Stop execution upon first error or failure.
909
--stop-on-risky Stop execution upon first risky test.
910
--stop-on-skipped Stop execution upon first skipped test.
911
--stop-on-incomplete Stop execution upon first incomplete test.
912
-v|--verbose Output more verbose information.
913
--debug Display debugging information during test execution.
915
--loader <loader> TestSuiteLoader implementation to use.
916
--repeat <times> Runs the test(s) repeatedly.
917
--tap Report test execution progress in TAP format.
918
--testdox Report test execution progress in TestDox format.
919
--printer <printer> TestListener implementation to use.
921
Configuration Options:
923
--bootstrap <file> A "bootstrap" PHP file that is run before the tests.
924
-c|--configuration <file> Read configuration from XML file.
925
--no-configuration Ignore default configuration file (phpunit.xml).
926
--include-path <path(s)> Prepend PHP's include_path with given path(s).
927
-d key[=value] Sets a php.ini value.
929
Miscellaneous Options:
931
-h|--help Prints this usage information.
932
--version Prints the version and exits.
936
if (defined('__PHPUNIT_PHAR__')) {
937
print "\n --self-update Update PHPUnit to the latest version.\n";
942
* Custom callback for test suite discovery.
944
protected function handleCustomTestSuite()
948
private function printVersionString()
950
if ($this->versionStringPrinted) {
954
print PHPUnit_Runner_Version::getVersionString() . "\n\n";
956
$this->versionStringPrinted = true;
961
private function showError($message)
963
$this->printVersionString();
965
print $message . "\n";
967
exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);