5 * Copyright (c) 2002-2009, Sebastian Bergmann <sb@sebastian-bergmann.de>.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
12 * * Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * * Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in
17 * the documentation and/or other materials provided with the
20 * * Neither the name of Sebastian Bergmann nor the names of his
21 * contributors may be used to endorse or promote products derived
22 * from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
27 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
28 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
29 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
30 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
34 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35 * POSSIBILITY OF SUCH DAMAGE.
39 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
40 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
41 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
43 * @link http://www.phpunit.de/
44 * @since File available since Release 3.0.0
47 require_once 'PHPUnit/TextUI/TestRunner.php';
48 require_once 'PHPUnit/Util/Configuration.php';
49 require_once 'PHPUnit/Util/Fileloader.php';
50 require_once 'PHPUnit/Util/Filter.php';
51 require_once 'PHPUnit/Util/Getopt.php';
53 PHPUnit_Util_Filter::addFileToFilter(__FILE__, 'PHPUNIT');
56 * A TestRunner for the Command Line Interface (CLI)
61 * @author Sebastian Bergmann <sb@sebastian-bergmann.de>
62 * @copyright 2002-2009 Sebastian Bergmann <sb@sebastian-bergmann.de>
63 * @license http://www.opensource.org/licenses/bsd-license.php BSD License
64 * @version Release: 3.3.17
65 * @link http://www.phpunit.de/
66 * @since Class available since Release 3.0.0
68 class PHPUnit_TextUI_Command
72 public static function main($exit = TRUE)
74 if ( !extension_loaded('dom') )
75 self::showMissingDependency('The dom extension is not loaded.');
77 $arguments = self::handleArguments();
78 $runner = new PHPUnit_TextUI_TestRunner;
80 if (is_object($arguments['test']) && $arguments['test'] instanceof PHPUnit_Framework_Test) {
81 $suite = $arguments['test'];
83 $suite = $runner->getTest(
85 $arguments['testFile'],
86 $arguments['syntaxCheck']
90 if ($suite->testAt(0) instanceof PHPUnit_Framework_Warning &&
91 strpos($suite->testAt(0)->getMessage(), 'No tests found in class') !== FALSE) {
92 $message = $suite->testAt(0)->getMessage();
93 $start = strpos($message, '"') + 1;
94 $end = strpos($message, '"', $start);
95 $className = substr($message, $start, $end - $start);
97 require_once 'PHPUnit/Util/Skeleton/Test.php';
99 $skeleton = new PHPUnit_Util_Skeleton_Test(
101 $arguments['testFile']
104 $result = $skeleton->generate(TRUE);
106 if (!$result['incomplete']) {
107 eval(str_replace(array('<?php', '?>'), '', $result['code']));
108 $suite = new PHPUnit_Framework_TestSuite($arguments['test'] . 'Test');
112 if ($arguments['listGroups']) {
113 PHPUnit_TextUI_TestRunner::printVersionString();
115 print "Available test group(s):\n";
117 $groups = $suite->getGroups();
120 foreach ($groups as $group) {
124 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
128 $result = $runner->doRun(
134 catch (Exception $e) {
135 throw new RuntimeException(
136 'Could not create and run test suite: ' . $e->getMessage()
141 if ($result->wasSuccessful()) {
142 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
145 else if ($result->errorCount() > 0) {
146 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
150 exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
157 protected static function handleArguments()
160 'listGroups' => FALSE,
161 'syntaxCheck' => TRUE
164 $longOptions = array(
198 'test-db-log-prefix=',
210 $options = PHPUnit_Util_Getopt::getopt(
217 catch (RuntimeException $e) {
218 PHPUnit_TextUI_TestRunner::showError($e->getMessage());
221 if (isset($options[1][0])) {
222 $arguments['test'] = $options[1][0];
225 if (isset($options[1][1])) {
226 $arguments['testFile'] = $options[1][1];
228 $arguments['testFile'] = '';
231 if (isset($arguments['test']) && is_file($arguments['test'])) {
232 $arguments['testFile'] = realpath($arguments['test']);
233 $arguments['test'] = substr($arguments['test'], 0, strrpos($arguments['test'], '.'));
236 $skeletonClass = FALSE;
237 $skeletonTest = FALSE;
239 foreach ($options[0] as $option) {
240 switch ($option[0]) {
243 $arguments['colors'] = TRUE;
247 case '--bootstrap': {
248 $arguments['bootstrap'] = $option[1];
252 case '--configuration': {
253 $arguments['configuration'] = $option[1];
257 case '--coverage-clover':
258 case '--coverage-xml': {
259 if (extension_loaded('tokenizer') && extension_loaded('xdebug')) {
260 $arguments['coverageClover'] = $option[1];
262 if (!extension_loaded('tokenizer')) {
263 self::showMissingDependency('The tokenizer extension is not loaded.');
265 self::showMissingDependency('The Xdebug extension is not loaded.');
271 case '--coverage-source': {
272 if (extension_loaded('tokenizer') && extension_loaded('xdebug')) {
273 $arguments['coverageSource'] = $option[1];
275 if (!extension_loaded('tokenizer')) {
276 self::showMissingDependency('The tokenizer extension is not loaded.');
278 self::showMissingDependency('The Xdebug extension is not loaded.');
284 case '--coverage-html':
286 if (extension_loaded('tokenizer') && extension_loaded('xdebug')) {
287 $arguments['reportDirectory'] = $option[1];
289 if (!extension_loaded('tokenizer')) {
290 self::showMissingDependency('The tokenizer extension is not loaded.');
292 self::showMissingDependency('The Xdebug extension is not loaded.');
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 $arguments['debug'] = TRUE;
318 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
323 $arguments['filter'] = $option[1];
328 $arguments['groups'] = explode(',', $option[1]);
332 case '--exclude-group': {
333 $arguments['excludeGroups'] = explode(',', $option[1]);
337 case '--list-groups': {
338 $arguments['listGroups'] = TRUE;
343 self::handleLoader($option[1]);
348 $arguments['jsonLogfile'] = $option[1];
352 case '--log-graphviz': {
353 if (PHPUnit_Util_Filesystem::fileExistsInIncludePath('Image/GraphViz.php')) {
354 $arguments['graphvizLogfile'] = $option[1];
356 self::showMissingDependency('The Image_GraphViz package is not installed.');
362 $arguments['tapLogfile'] = $option[1];
367 $arguments['xmlLogfile'] = $option[1];
372 if (extension_loaded('tokenizer') && extension_loaded('xdebug')) {
373 $arguments['pmdXML'] = $option[1];
375 if (!extension_loaded('tokenizer')) {
376 self::showMissingDependency('The tokenizer extension is not loaded.');
378 self::showMissingDependency('The Xdebug extension is not loaded.');
384 case '--log-metrics': {
385 if (extension_loaded('tokenizer') && extension_loaded('xdebug')) {
386 $arguments['metricsXML'] = $option[1];
388 if (!extension_loaded('tokenizer')) {
389 self::showMissingDependency('The tokenizer extension is not loaded.');
391 self::showMissingDependency('The Xdebug extension is not loaded.');
398 $arguments['repeat'] = (int)$option[1];
402 case '--stop-on-failure': {
403 $arguments['stopOnFailure'] = TRUE;
407 case '--test-db-dsn': {
408 if (extension_loaded('pdo')) {
409 $arguments['testDatabaseDSN'] = $option[1];
411 self::showMissingDependency('The PDO extension is not loaded.');
416 case '--test-db-log-rev': {
417 if (extension_loaded('pdo')) {
418 $arguments['testDatabaseLogRevision'] = $option[1];
420 self::showMissingDependency('The PDO extension is not loaded.');
425 case '--test-db-prefix': {
426 if (extension_loaded('pdo')) {
427 $arguments['testDatabasePrefix'] = $option[1];
429 self::showMissingDependency('The PDO extension is not loaded.');
434 case '--test-db-log-info': {
435 if (extension_loaded('pdo')) {
436 $arguments['testDatabaseLogInfo'] = $option[1];
438 self::showMissingDependency('The PDO extension is not loaded.');
444 case '--skeleton-test': {
445 $skeletonTest = TRUE;
446 $skeletonClass = FALSE;
450 case '--skeleton-class': {
451 $skeletonClass = TRUE;
452 $skeletonTest = FALSE;
457 require_once 'PHPUnit/Util/Log/TAP.php';
459 $arguments['printer'] = new PHPUnit_Util_Log_TAP;
464 require_once 'PHPUnit/Extensions/Story/ResultPrinter/Text.php';
466 $arguments['printer'] = new PHPUnit_Extensions_Story_ResultPrinter_Text;
470 case '--story-html': {
471 $arguments['storyHTMLFile'] = $option[1];
475 case '--story-text': {
476 $arguments['storyTextFile'] = $option[1];
481 require_once 'PHPUnit/Util/TestDox/ResultPrinter/Text.php';
483 $arguments['printer'] = new PHPUnit_Util_TestDox_ResultPrinter_Text;
487 case '--testdox-html': {
488 $arguments['testdoxHTMLFile'] = $option[1];
492 case '--testdox-text': {
493 $arguments['testdoxTextFile'] = $option[1];
497 case '--no-syntax-check': {
498 $arguments['syntaxCheck'] = FALSE;
503 $arguments['verbose'] = TRUE;
508 PHPUnit_TextUI_TestRunner::printVersionString();
509 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
514 $arguments['wait'] = TRUE;
520 if (isset($arguments['bootstrap'])) {
521 PHPUnit_Util_Fileloader::load($arguments['bootstrap']);
524 if (!isset($arguments['configuration']) && file_exists('phpunit.xml')) {
525 $arguments['configuration'] = realpath('phpunit.xml');
528 if (isset($arguments['configuration'])) {
529 $configuration = new PHPUnit_Util_Configuration(
530 $arguments['configuration']
533 $configuration->handlePHPConfiguration();
535 if (!isset($arguments['bootstrap'])) {
536 $phpunitConfiguration = $configuration->getPHPUnitConfiguration();
538 if (isset($phpunitConfiguration['bootstrap'])) {
539 PHPUnit_Util_Fileloader::load($phpunitConfiguration['bootstrap']);
543 $browsers = $configuration->getSeleniumBrowserConfiguration();
545 if (!empty($browsers)) {
546 require_once 'PHPUnit/Extensions/SeleniumTestCase.php';
547 PHPUnit_Extensions_SeleniumTestCase::$browsers = $browsers;
550 if (!isset($arguments['test'])) {
551 $testSuite = $configuration->getTestSuiteConfiguration(
552 $arguments['syntaxCheck']
555 if ($testSuite !== NULL) {
556 $arguments['test'] = $testSuite;
561 if (isset($arguments['test']) && is_string($arguments['test']) && substr($arguments['test'], -5, 5) == '.phpt') {
562 require_once 'PHPUnit/Extensions/PhptTestCase.php';
564 $test = new PHPUnit_Extensions_PhptTestCase($arguments['test']);
566 $arguments['test'] = new PHPUnit_Framework_TestSuite;
567 $arguments['test']->addTest($test);
570 if (!isset($arguments['test']) ||
571 (isset($arguments['testDatabaseLogRevision']) && !isset($arguments['testDatabaseDSN']))) {
573 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
576 if ($skeletonClass || $skeletonTest) {
577 if (isset($arguments['test']) && $arguments['test'] !== FALSE) {
578 PHPUnit_TextUI_TestRunner::printVersionString();
580 if ($skeletonClass) {
581 require_once 'PHPUnit/Util/Skeleton/Class.php';
583 $class = 'PHPUnit_Util_Skeleton_Class';
585 require_once 'PHPUnit/Util/Skeleton/Test.php';
587 $class = 'PHPUnit_Util_Skeleton_Test';
592 $reflector = new ReflectionClass($class);
594 for ($i = 0; $i <= 3; $i++) {
595 if (isset($options[1][$i])) {
596 $args[] = $options[1][$i];
600 $skeleton = $reflector->newInstanceArgs($args);
604 catch (Exception $e) {
605 print $e->getMessage() . "\n";
606 exit(PHPUnit_TextUI_TestRunner::FAILURE_EXIT);
610 'Wrote skeleton for "%s" to "%s".' . "\n",
611 $skeleton->getOutClassName(),
612 $skeleton->getOutSourceFile()
615 exit(PHPUnit_TextUI_TestRunner::SUCCESS_EXIT);
618 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
626 * @param string $loaderName
628 protected static function handleLoader($loaderName)
630 if (!class_exists($loaderName, FALSE)) {
631 PHPUnit_Util_Fileloader::checkAndLoad(
632 str_replace('_', '/', $loaderName) . '.php'
636 if (class_exists($loaderName, FALSE)) {
637 $class = new ReflectionClass($loaderName);
639 if ($class->implementsInterface('PHPUnit_Runner_TestSuiteLoader') &&
640 $class->isInstantiable()) {
641 $loader = $class->newInstance();
645 if (!isset($loader)) {
646 PHPUnit_TextUI_TestRunner::showError(
648 'Could not use "%s" as loader.',
655 PHPUnit_TextUI_TestRunner::setLoader($loader);
659 * @param string $message
661 public static function showMissingDependency($message)
663 PHPUnit_TextUI_TestRunner::printVersionString();
664 print $message . "\n";
665 exit(PHPUnit_TextUI_TestRunner::EXCEPTION_EXIT);
670 public static function showHelp()
672 PHPUnit_TextUI_TestRunner::printVersionString();
675 Usage: phpunit [switches] UnitTest [UnitTest.php]
676 phpunit [switches] <directory>
678 --log-graphviz <file> Log test execution in GraphViz markup.
679 --log-json <file> Log test execution in JSON format.
680 --log-tap <file> Log test execution in TAP format to file.
681 --log-xml <file> Log test execution in XML format to file.
682 --log-metrics <file> Write metrics report in XML format.
683 --log-pmd <file> Write violations report in PMD XML format.
685 --coverage-html <dir> Generate code coverage report in HTML format.
686 --coverage-clover <file> Write code coverage data in Clover XML format.
687 --coverage-source <dir> Write code coverage / source data in XML format.
689 --test-db-dsn <dsn> DSN for the test database.
690 --test-db-log-rev <rev> Revision information for database logging.
691 --test-db-prefix ... Prefix that should be stripped from filenames.
692 --test-db-log-info ... Additional information for database logging.
694 --story-html <file> Write Story/BDD results in HTML format to file.
695 --story-text <file> Write Story/BDD results in Text format to file.
697 --testdox-html <file> Write agile documentation in HTML format to file.
698 --testdox-text <file> Write agile documentation in Text format to file.
700 --filter <pattern> Filter which tests to run.
701 --group ... Only runs tests from the specified group(s).
702 --exclude-group ... Exclude tests from the specified group(s).
703 --list-groups List available test groups.
705 --loader <loader> TestSuiteLoader implementation to use.
706 --repeat <times> Runs the test(s) repeatedly.
708 --story Report test execution progress in Story/BDD format.
709 --tap Report test execution progress in TAP format.
710 --testdox Report test execution progress in TestDox format.
712 --no-syntax-check Disable syntax check of test source files.
713 --stop-on-failure Stop execution upon first error or failure.
714 --colors Use colors in output.
715 --verbose Output more verbose information.
716 --wait Waits for a keystroke after each test.
718 --skeleton-class Generate Unit class for UnitTest in UnitTest.php.
719 --skeleton-test Generate UnitTest class for Unit in Unit.php.
721 --help Prints this usage information.
722 --version Prints the version and exits.
724 --bootstrap <file> A "bootstrap" PHP file that is run before the tests.
725 --configuration <file> Read configuration from XML file.
726 -d key[=value] Sets a php.ini value.