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