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