| [ Index ] |
PHP Cross Reference of Limb3 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Base include file for SimpleTest 4 * @package SimpleTest 5 * @subpackage UnitTester 6 * @version $Id: test_case.php 5999 2007-06-18 13:13:08Z pachanga $ 7 */ 8 9 /**#@+ 10 * Includes SimpleTest files and defined the root constant 11 * for dependent libraries. 12 */ 13 require_once(dirname(__FILE__) . '/invoker.php'); 14 require_once(dirname(__FILE__) . '/errors.php'); 15 require_once(dirname(__FILE__) . '/compatibility.php'); 16 require_once(dirname(__FILE__) . '/scorer.php'); 17 require_once(dirname(__FILE__) . '/expectation.php'); 18 require_once(dirname(__FILE__) . '/dumper.php'); 19 require_once(dirname(__FILE__) . '/simpletest.php'); 20 if (version_compare(phpversion(), '5') >= 0) { 21 require_once(dirname(__FILE__) . '/exceptions.php'); 22 require_once(dirname(__FILE__) . '/reflection_php5.php'); 23 } else { 24 require_once(dirname(__FILE__) . '/reflection_php4.php'); 25 } 26 if (! defined('SIMPLE_TEST')) { 27 /** 28 * @ignore 29 */ 30 define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR); 31 } 32 /**#@-*/ 33 34 /** 35 * Basic test case. This is the smallest unit of a test 36 * suite. It searches for 37 * all methods that start with the the string "test" and 38 * runs them. Working test cases extend this class. 39 * @package SimpleTest 40 * @subpackage UnitTester 41 */ 42 class SimpleTestCase { 43 var $_label = false; 44 var $_reporter; 45 var $_observers; 46 var $_should_skip = false; 47 48 /** 49 * Sets up the test with no display. 50 * @param string $label If no test name is given then 51 * the class name is used. 52 * @access public 53 */ 54 function SimpleTestCase($label = false) { 55 if ($label) { 56 $this->_label = $label; 57 } 58 } 59 60 /** 61 * Accessor for the test name for subclasses. 62 * @return string Name of the test. 63 * @access public 64 */ 65 function getLabel() { 66 return $this->_label ? $this->_label : get_class($this); 67 } 68 69 /** 70 * This is a placeholder for skipping tests. In this 71 * method you place skipIf() and skipUnless() calls to 72 * set the skipping state. 73 * @access public 74 */ 75 function skip() { 76 } 77 78 /** 79 * Will issue a message to the reporter and tell the test 80 * case to skip if the incoming flag is true. 81 * @param string $should_skip Condition causing the tests to be skipped. 82 * @param string $message Text of skip condition. 83 * @access public 84 */ 85 function skipIf($should_skip, $message = '%s') { 86 if ($should_skip && ! $this->_should_skip) { 87 $this->_should_skip = true; 88 $message = sprintf($message, 'Skipping [' . get_class($this) . ']'); 89 $this->_reporter->paintSkip($message . $this->getAssertionLine()); 90 } 91 } 92 93 /** 94 * Will issue a message to the reporter and tell the test 95 * case to skip if the incoming flag is false. 96 * @param string $shouldnt_skip Condition causing the tests to be run. 97 * @param string $message Text of skip condition. 98 * @access public 99 */ 100 function skipUnless($shouldnt_skip, $message = false) { 101 $this->skipIf(! $shouldnt_skip, $message); 102 } 103 104 /** 105 * Used to invoke the single tests. 106 * @return SimpleInvoker Individual test runner. 107 * @access public 108 */ 109 function &createInvoker() { 110 $invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this)); 111 if (version_compare(phpversion(), '5') >= 0) { 112 $invoker = &new SimpleExceptionTrappingInvoker($invoker); 113 } 114 return $invoker; 115 } 116 117 /** 118 * Uses reflection to run every method within itself 119 * starting with the string "test" unless a method 120 * is specified. 121 * @param SimpleReporter $reporter Current test reporter. 122 * @return boolean True if all tests passed. 123 * @access public 124 */ 125 function run(&$reporter) { 126 $context = &SimpleTest::getContext(); 127 $context->setTest($this); 128 $context->setReporter($reporter); 129 $this->_reporter = &$reporter; 130 $started = false; 131 foreach ($this->getTests() as $method) { 132 if ($reporter->shouldInvoke($this->getLabel(), $method)) { 133 $this->skip(); 134 if ($this->_should_skip) { 135 break; 136 } 137 if (! $started) { 138 $reporter->paintCaseStart($this->getLabel()); 139 $started = true; 140 } 141 $invoker = &$this->_reporter->createInvoker($this->createInvoker()); 142 $invoker->before($method); 143 $invoker->invoke($method); 144 $invoker->after($method); 145 } 146 } 147 if ($started) { 148 $reporter->paintCaseEnd($this->getLabel()); 149 } 150 unset($this->_reporter); 151 return $reporter->getStatus(); 152 } 153 154 /** 155 * Gets a list of test names. Normally that will 156 * be all internal methods that start with the 157 * name "test". This method should be overridden 158 * if you want a different rule. 159 * @return array List of test names. 160 * @access public 161 */ 162 function getTests() { 163 $methods = array(); 164 foreach (get_class_methods(get_class($this)) as $method) { 165 if ($this->_isTest($method)) { 166 $methods[] = $method; 167 } 168 } 169 return $methods; 170 } 171 172 /** 173 * Tests to see if the method is a test that should 174 * be run. Currently any method that starts with 'test' 175 * is a candidate unless it is the constructor. 176 * @param string $method Method name to try. 177 * @return boolean True if test method. 178 * @access protected 179 */ 180 function _isTest($method) { 181 if (strtolower(substr($method, 0, 4)) == 'test') { 182 return ! SimpleTestCompatibility::isA($this, strtolower($method)); 183 } 184 return false; 185 } 186 187 /** 188 * Announces the start of the test. 189 * @param string $method Test method just started. 190 * @access public 191 */ 192 function before($method) { 193 $this->_reporter->paintMethodStart($method); 194 $this->_observers = array(); 195 } 196 197 /** 198 * Sets up unit test wide variables at the start 199 * of each test method. To be overridden in 200 * actual user test cases. 201 * @access public 202 */ 203 function setUp() { 204 } 205 206 /** 207 * Clears the data set in the setUp() method call. 208 * To be overridden by the user in actual user test cases. 209 * @access public 210 */ 211 function tearDown() { 212 } 213 214 /** 215 * Announces the end of the test. Includes private clean up. 216 * @param string $method Test method just finished. 217 * @access public 218 */ 219 function after($method) { 220 for ($i = 0; $i < count($this->_observers); $i++) { 221 $this->_observers[$i]->atTestEnd($method, $this); 222 } 223 $this->_reporter->paintMethodEnd($method); 224 } 225 226 /** 227 * Sets up an observer for the test end. 228 * @param object $observer Must have atTestEnd() 229 * method. 230 * @access public 231 */ 232 function tell(&$observer) { 233 $this->_observers[] = &$observer; 234 } 235 236 /** 237 * @deprecated 238 */ 239 function pass($message = "Pass") { 240 if (! isset($this->_reporter)) { 241 trigger_error('Can only make assertions within test methods'); 242 } 243 $this->_reporter->paintPass( 244 $message . $this->getAssertionLine()); 245 return true; 246 } 247 248 /** 249 * Sends a fail event with a message. 250 * @param string $message Message to send. 251 * @access public 252 */ 253 function fail($message = "Fail") { 254 if (! isset($this->_reporter)) { 255 trigger_error('Can only make assertions within test methods'); 256 } 257 $this->_reporter->paintFail( 258 $message . $this->getAssertionLine()); 259 return false; 260 } 261 262 /** 263 * Formats a PHP error and dispatches it to the 264 * reporter. 265 * @param integer $severity PHP error code. 266 * @param string $message Text of error. 267 * @param string $file File error occoured in. 268 * @param integer $line Line number of error. 269 * @access public 270 */ 271 function error($severity, $message, $file, $line) { 272 if (! isset($this->_reporter)) { 273 trigger_error('Can only make assertions within test methods'); 274 } 275 $this->_reporter->paintError( 276 "Unexpected PHP error [$message] severity [$severity] in [$file line $line]"); 277 } 278 279 /** 280 * Formats an exception and dispatches it to the 281 * reporter. 282 * @param Exception $exception Object thrown. 283 * @access public 284 */ 285 function exception($exception) { 286 $this->_reporter->paintException($exception); 287 } 288 289 /** 290 * @deprecated 291 */ 292 function signal($type, &$payload) { 293 if (! isset($this->_reporter)) { 294 trigger_error('Can only make assertions within test methods'); 295 } 296 $this->_reporter->paintSignal($type, $payload); 297 } 298 299 /** 300 * Runs an expectation directly, for extending the 301 * tests with new expectation classes. 302 * @param SimpleExpectation $expectation Expectation subclass. 303 * @param mixed $compare Value to compare. 304 * @param string $message Message to display. 305 * @return boolean True on pass 306 * @access public 307 */ 308 function assert(&$expectation, $compare, $message = '%s') { 309 if ($expectation->test($compare)) { 310 return $this->pass(sprintf( 311 $message, 312 $expectation->overlayMessage($compare, $this->_reporter->getDumper()))); 313 } else { 314 return $this->fail(sprintf( 315 $message, 316 $expectation->overlayMessage($compare, $this->_reporter->getDumper()))); 317 } 318 } 319 320 /** 321 * @deprecated 322 */ 323 function assertExpectation(&$expectation, $compare, $message = '%s') { 324 return $this->assert($expectation, $compare, $message); 325 } 326 327 /** 328 * Uses a stack trace to find the line of an assertion. 329 * @return string Line number of first assert* 330 * method embedded in format string. 331 * @access public 332 */ 333 function getAssertionLine() { 334 $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip')); 335 return $trace->traceMethod(); 336 } 337 338 /** 339 * Sends a formatted dump of a variable to the 340 * test suite for those emergency debugging 341 * situations. 342 * @param mixed $variable Variable to display. 343 * @param string $message Message to display. 344 * @return mixed The original variable. 345 * @access public 346 */ 347 function dump($variable, $message = false) { 348 $dumper = $this->_reporter->getDumper(); 349 $formatted = $dumper->dump($variable); 350 if ($message) { 351 $formatted = $message . "\n" . $formatted; 352 } 353 $this->_reporter->paintFormattedMessage($formatted); 354 return $variable; 355 } 356 357 /** 358 * @deprecated 359 */ 360 function sendMessage($message) { 361 $this->_reporter->PaintMessage($message); 362 } 363 364 /** 365 * Accessor for the number of subtests. 366 * @return integer Number of test cases. 367 * @access public 368 * @static 369 */ 370 function getSize() { 371 return 1; 372 } 373 } 374 375 /** 376 * Helps to extract test cases automatically from a file. 377 */ 378 class SimpleFileLoader { 379 380 /** 381 * Builds a test suite from a library of test cases. 382 * The new suite is composed into this one. 383 * @param string $test_file File name of library with 384 * test case classes. 385 * @return TestSuite The new test suite. 386 * @access public 387 */ 388 function &load($test_file) { 389 $existing_classes = get_declared_classes(); 390 include_once($test_file); 391 $classes = $this->selectRunnableTests( 392 array_diff(get_declared_classes(), $existing_classes)); 393 $suite = &$this->createSuiteFromClasses($test_file, $classes); 394 return $suite; 395 } 396 397 /** 398 * Calculates the incoming test cases. Skips abstract 399 * and ignored classes. 400 * @param array $candidates Candidate classes. 401 * @return array New classes which are test 402 * cases that shouldn't be ignored. 403 * @access public 404 */ 405 function selectRunnableTests($candidates) { 406 $classes = array(); 407 foreach ($candidates as $class) { 408 if (TestSuite::getBaseTestCase($class)) { 409 $reflection = new SimpleReflection($class); 410 if ($reflection->isAbstract()) { 411 SimpleTest::ignore($class); 412 } 413 $classes[] = $class; 414 } 415 } 416 return $classes; 417 } 418 419 /** 420 * Builds a test suite from a class list. 421 * @param string $title Title of new group. 422 * @param array $classes Test classes. 423 * @return TestSuite Group loaded with the new 424 * test cases. 425 * @access public 426 */ 427 function &createSuiteFromClasses($title, $classes) { 428 if (count($classes) == 0) { 429 $suite = &new BadTestSuite($title, "No runnable test cases in [$title]"); 430 return $suite; 431 } 432 SimpleTest::ignoreParentsIfIgnored($classes); 433 $suite = &new TestSuite($title); 434 foreach ($classes as $class) { 435 if (! SimpleTest::isIgnored($class)) { 436 $suite->addTestClass($class); 437 } 438 } 439 return $suite; 440 } 441 } 442 443 /** 444 * This is a composite test class for combining 445 * test cases and other RunnableTest classes into 446 * a group test. 447 * @package SimpleTest 448 * @subpackage UnitTester 449 */ 450 class TestSuite { 451 var $_label; 452 var $_test_cases; 453 454 /** 455 * Sets the name of the test suite. 456 * @param string $label Name sent at the start and end 457 * of the test. 458 * @access public 459 */ 460 function TestSuite($label = false) { 461 $this->_label = $label; 462 $this->_test_cases = array(); 463 } 464 465 /** 466 * Accessor for the test name for subclasses. If the suite 467 * wraps a single test case the label defaults to the name of that test. 468 * @return string Name of the test. 469 * @access public 470 */ 471 function getLabel() { 472 if (! $this->_label) { 473 return ($this->getSize() == 1) ? 474 get_class($this->_test_cases[0]) : get_class($this); 475 } else { 476 return $this->_label; 477 } 478 } 479 480 /** 481 * @deprecated 482 */ 483 function addTestCase(&$test_case) { 484 $this->_test_cases[] = &$test_case; 485 } 486 487 /** 488 * @deprecated 489 */ 490 function addTestClass($class) { 491 if (TestSuite::getBaseTestCase($class) == 'testsuite') { 492 $this->_test_cases[] = &new $class(); 493 } else { 494 $this->_test_cases[] = $class; 495 } 496 } 497 498 /** 499 * Adds a test into the suite by instance or class. The class will 500 * be instantiated if it's a test suite. 501 * @param SimpleTestCase $test_case Suite or individual test 502 * case implementing the 503 * runnable test interface. 504 * @access public 505 */ 506 function add(&$test_case) { 507 if (! is_string($test_case)) { 508 $this->_test_cases[] = &$test_case; 509 } elseif (TestSuite::getBaseTestCase($class) == 'testsuite') { 510 $this->_test_cases[] = &new $class(); 511 } else { 512 $this->_test_cases[] = $class; 513 } 514 } 515 516 /** 517 * @deprecated 518 */ 519 function addTestFile($test_file) { 520 $this->addFile($test_file); 521 } 522 523 /** 524 * Builds a test suite from a library of test cases. 525 * The new suite is composed into this one. 526 * @param string $test_file File name of library with 527 * test case classes. 528 * @access public 529 */ 530 function addFile($test_file) { 531 $extractor = new SimpleFileLoader(); 532 $this->add($extractor->load($test_file)); 533 } 534 535 /** 536 * Delegates to a visiting collector to add test 537 * files. 538 * @param string $path Path to scan from. 539 * @param SimpleCollector $collector Directory scanner. 540 * @access public 541 */ 542 function collect($path, &$collector) { 543 $collector->collect($this, $path); 544 } 545 546 /** 547 * Invokes run() on all of the held test cases, instantiating 548 * them if necessary. 549 * @param SimpleReporter $reporter Current test reporter. 550 * @access public 551 */ 552 function run(&$reporter) { 553 $reporter->paintGroupStart($this->getLabel(), $this->getSize()); 554 for (