premier commit
[bazdig.git] / test / simpletest / test_case.php
blobf49e6f99a91cf1fa49a1abc91137fbfc322bf76c
1 <?php
2 /**
3 * Base include file for SimpleTest
4 * @package SimpleTest
5 * @subpackage UnitTester
6 * @version $Id: test_case.php,v 1.34 2006/11/21 01:20:18 lastcraft Exp $
7 */
9 /**#@+
10 * Includes SimpleTest files and defined the root constant
11 * for dependent libraries.
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');
26 if (! defined('SIMPLE_TEST')) {
27 /** @ignore */
28 define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR);
30 /**#@-*/
32 /**
33 * Basic test case. This is the smallest unit of a test
34 * suite. It searches for
35 * all methods that start with the the string "test" and
36 * runs them. Working test cases extend this class.
37 * @package SimpleTest
38 * @subpackage UnitTester
40 class SimpleTestCase {
41 var $_label = false;
42 var $_reporter;
43 var $_observers;
44 var $_should_skip = false;
46 /**
47 * Sets up the test with no display.
48 * @param string $label If no test name is given then
49 * the class name is used.
50 * @access public
52 function SimpleTestCase($label = false) {
53 if ($label) {
54 $this->_label = $label;
58 /**
59 * Accessor for the test name for subclasses.
60 * @return string Name of the test.
61 * @access public
63 function getLabel() {
64 return $this->_label ? $this->_label : get_class($this);
67 /**
68 * This is a placeholder for skipping tests. In this
69 * method you place skipIf() and skipUnless() calls to
70 * set the skipping state.
71 * @access public
73 function skip() {
76 /**
77 * Will issue a message to the reporter and tell the test
78 * case to skip if the incoming flag is true.
79 * @param string $should_skip Condition causing the tests to be skipped.
80 * @param string $message Text of skip condition.
81 * @access public
83 function skipIf($should_skip, $message = '%s') {
84 if ($should_skip && ! $this->_should_skip) {
85 $this->_should_skip = true;
86 $message = sprintf($message, 'Skipping [' . get_class($this) . ']');
87 $this->_reporter->paintSkip($message . $this->getAssertionLine());
91 /**
92 * Will issue a message to the reporter and tell the test
93 * case to skip if the incoming flag is false.
94 * @param string $shouldnt_skip Condition causing the tests to be run.
95 * @param string $message Text of skip condition.
96 * @access public
98 function skipUnless($shouldnt_skip, $message = false) {
99 $this->skipIf(! $shouldnt_skip, $message);
103 * Used to invoke the single tests.
104 * @return SimpleInvoker Individual test runner.
105 * @access public
107 function &createInvoker() {
108 $invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this));
109 if (version_compare(phpversion(), '5') >= 0) {
110 $invoker = &new SimpleExceptionTrappingInvoker($invoker);
112 return $invoker;
116 * Uses reflection to run every method within itself
117 * starting with the string "test" unless a method
118 * is specified.
119 * @param SimpleReporter $reporter Current test reporter.
120 * @return boolean True if all tests passed.
121 * @access public
123 function run(&$reporter) {
124 $context = &SimpleTest::getContext();
125 $context->setTest($this);
126 $context->setReporter($reporter);
127 $this->_reporter = &$reporter;
128 $reporter->paintCaseStart($this->getLabel());
129 $this->skip();
130 if (! $this->_should_skip) {
131 foreach ($this->getTests() as $method) {
132 if ($reporter->shouldInvoke($this->getLabel(), $method)) {
133 $invoker = &$this->_reporter->createInvoker($this->createInvoker());
134 $invoker->before($method);
135 $invoker->invoke($method);
136 $invoker->after($method);
140 $reporter->paintCaseEnd($this->getLabel());
141 unset($this->_reporter);
142 return $reporter->getStatus();
146 * Gets a list of test names. Normally that will
147 * be all internal methods that start with the
148 * name "test". This method should be overridden
149 * if you want a different rule.
150 * @return array List of test names.
151 * @access public
153 function getTests() {
154 $methods = array();
155 foreach (get_class_methods(get_class($this)) as $method) {
156 if ($this->_isTest($method)) {
157 $methods[] = $method;
160 return $methods;
164 * Tests to see if the method is a test that should
165 * be run. Currently any method that starts with 'test'
166 * is a candidate unless it is the constructor.
167 * @param string $method Method name to try.
168 * @return boolean True if test method.
169 * @access protected
171 function _isTest($method) {
172 if (strtolower(substr($method, 0, 4)) == 'test') {
173 return ! SimpleTestCompatibility::isA($this, strtolower($method));
175 return false;
179 * Announces the start of the test.
180 * @param string $method Test method just started.
181 * @access public
183 function before($method) {
184 $this->_reporter->paintMethodStart($method);
185 $this->_observers = array();
189 * Sets up unit test wide variables at the start
190 * of each test method. To be overridden in
191 * actual user test cases.
192 * @access public
194 function setUp() {
198 * Clears the data set in the setUp() method call.
199 * To be overridden by the user in actual user test cases.
200 * @access public
202 function tearDown() {
206 * Announces the end of the test. Includes private clean up.
207 * @param string $method Test method just finished.
208 * @access public
210 function after($method) {
211 for ($i = 0; $i < count($this->_observers); $i++) {
212 $this->_observers[$i]->atTestEnd($method, $this);
214 $this->_reporter->paintMethodEnd($method);
218 * Sets up an observer for the test end.
219 * @param object $observer Must have atTestEnd()
220 * method.
221 * @access public
223 function tell(&$observer) {
224 $this->_observers[] = &$observer;
228 * @deprecated
230 function pass($message = "Pass") {
231 if (! isset($this->_reporter)) {
232 trigger_error('Can only make assertions within test methods');
234 $this->_reporter->paintPass(
235 $message . $this->getAssertionLine());
236 return true;
240 * Sends a fail event with a message.
241 * @param string $message Message to send.
242 * @access public
244 function fail($message = "Fail") {
245 if (! isset($this->_reporter)) {
246 trigger_error('Can only make assertions within test methods');
248 $this->_reporter->paintFail(
249 $message . $this->getAssertionLine());
250 return false;
254 * Formats a PHP error and dispatches it to the
255 * reporter.
256 * @param integer $severity PHP error code.
257 * @param string $message Text of error.
258 * @param string $file File error occoured in.
259 * @param integer $line Line number of error.
260 * @access public
262 function error($severity, $message, $file, $line) {
263 if (! isset($this->_reporter)) {
264 trigger_error('Can only make assertions within test methods');
266 $this->_reporter->paintError(
267 "Unexpected PHP error [$message] severity [$severity] in [$file line $line]");
271 * Formats an exception and dispatches it to the
272 * reporter.
273 * @param Exception $exception Object thrown.
274 * @access public
276 function exception($exception) {
277 $this->_reporter->paintException($exception);
281 * @deprecated
283 function signal($type, &$payload) {
284 if (! isset($this->_reporter)) {
285 trigger_error('Can only make assertions within test methods');
287 $this->_reporter->paintSignal($type, $payload);
291 * Runs an expectation directly, for extending the
292 * tests with new expectation classes.
293 * @param SimpleExpectation $expectation Expectation subclass.
294 * @param mixed $compare Value to compare.
295 * @param string $message Message to display.
296 * @return boolean True on pass
297 * @access public
299 function assert(&$expectation, $compare, $message = '%s') {
300 if ($expectation->test($compare)) {
301 return $this->pass(sprintf(
302 $message,
303 $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
304 } else {
305 return $this->fail(sprintf(
306 $message,
307 $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
312 * @deprecated
314 function assertExpectation(&$expectation, $compare, $message = '%s') {
315 return $this->assert($expectation, $compare, $message);
319 * Uses a stack trace to find the line of an assertion.
320 * @return string Line number of first assert*
321 * method embedded in format string.
322 * @access public
324 function getAssertionLine() {
325 $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip'));
326 return $trace->traceMethod();
330 * Sends a formatted dump of a variable to the
331 * test suite for those emergency debugging
332 * situations.
333 * @param mixed $variable Variable to display.
334 * @param string $message Message to display.
335 * @return mixed The original variable.
336 * @access public
338 function dump($variable, $message = false) {
339 $dumper = $this->_reporter->getDumper();
340 $formatted = $dumper->dump($variable);
341 if ($message) {
342 $formatted = $message . "\n" . $formatted;
344 $this->_reporter->paintFormattedMessage($formatted);
345 return $variable;
349 * @deprecated
351 function sendMessage($message) {
352 $this->_reporter->PaintMessage($message);
356 * Accessor for the number of subtests.
357 * @return integer Number of test cases.
358 * @access public
359 * @static
361 function getSize() {
362 return 1;
367 * This is a composite test class for combining
368 * test cases and other RunnableTest classes into
369 * a group test.
370 * @package SimpleTest
371 * @subpackage UnitTester
373 class TestSuite {
374 var $_label;
375 var $_test_cases;
376 var $_old_track_errors;
377 var $_xdebug_is_enabled;
380 * Sets the name of the test suite.
381 * @param string $label Name sent at the start and end
382 * of the test.
383 * @access public
385 function TestSuite($label = false) {
386 $this->_label = $label ? $label : get_class($this);
387 $this->_test_cases = array();
388 $this->_old_track_errors = ini_get('track_errors');
389 $this->_xdebug_is_enabled = function_exists('xdebug_is_enabled') ?
390 xdebug_is_enabled() : false;
394 * Accessor for the test name for subclasses.
395 * @return string Name of the test.
396 * @access public
398 function getLabel() {
399 return $this->_label;
403 * Adds a test into the suite. Can be either a group
404 * test or some other unit test.
405 * @param SimpleTestCase $test_case Suite or individual test
406 * case implementing the
407 * runnable test interface.
408 * @access public
410 function addTestCase(&$test_case) {
411 $this->_test_cases[] = &$test_case;
415 * Adds a test into the suite by class name. The class will
416 * be instantiated as needed.
417 * @param SimpleTestCase $test_case Suite or individual test
418 * case implementing the
419 * runnable test interface.
420 * @access public
422 function addTestClass($class) {
423 if ($this->_getBaseTestCase($class) == 'testsuite' || $this->_getBaseTestCase($class) == 'grouptest') {
424 $this->_test_cases[] = &new $class();
425 } else {
426 $this->_test_cases[] = $class;
431 * Builds a group test from a library of test cases.
432 * The new group is composed into this one.
433 * @param string $test_file File name of library with
434 * test case classes.
435 * @access public
437 function addTestFile($test_file) {
438 $existing_classes = get_declared_classes();
439 if ($error = $this->_requireWithError($test_file)) {
440 $this->addTestCase(new BadTestSuite($test_file, $error));
441 return;
443 $classes = $this->_selectRunnableTests($existing_classes, get_declared_classes());
444 if (count($classes) == 0) {
445 $this->addTestCase(new BadTestSuite($test_file, "No runnable test cases in [$test_file]"));
446 return;
448 $group = &$this->_createGroupFromClasses($test_file, $classes);
449 $this->addTestCase($group);
453 * Requires a source file recording any syntax errors.
454 * @param string $file File name to require in.
455 * @return string/boolean An error message on failure or false
456 * if no errors.
457 * @access private
459 function _requireWithError($file) {
460 $this->_enableErrorReporting();
461 include($file);
462 $error = isset($php_errormsg) ? $php_errormsg : false;
463 $this->_disableErrorReporting();
464 $self_inflicted_errors = array(
465 '/Assigning the return value of new by reference/i',
466 '/var: Deprecated/i',
467 '/Non-static method/i');
468 foreach ($self_inflicted_errors as $pattern) {
469 if (preg_match($pattern, $error)) {
470 return false;
473 return $error;
477 * Sets up detection of parse errors. Note that XDebug
478 * interferes with this and has to be disabled. This is
479 * to make sure the correct error code is returned
480 * from unattended scripts.
481 * @access private
483 function _enableErrorReporting() {
484 if ($this->_xdebug_is_enabled) {
485 xdebug_disable();
487 ini_set('track_errors', true);
491 * Resets detection of parse errors to their old values.
492 * This is to make sure the correct error code is returned
493 * from unattended scripts.
494 * @access private
496 function _disableErrorReporting() {
497 ini_set('track_errors', $this->_old_track_errors);
498 if ($this->_xdebug_is_enabled) {
499 xdebug_enable();
504 * Calculates the incoming test cases from a before
505 * and after list of loaded classes. Skips abstract
506 * classes.
507 * @param array $existing_classes Classes before require().
508 * @param array $new_classes Classes after require().
509 * @return array New classes which are test
510 * cases that shouldn't be ignored.
511 * @access private
513 function _selectRunnableTests($existing_classes, $new_classes) {
514 $classes = array();
515 foreach ($new_classes as $class) {
516 if (in_array($class, $existing_classes)) {
517 continue;
519 if ($this->_getBaseTestCase($class)) {
520 $reflection = new SimpleReflection($class);
521 if ($reflection->isAbstract()) {
522 SimpleTest::ignore($class);
524 $classes[] = $class;
527 return $classes;
531 * Builds a group test from a class list.
532 * @param string $title Title of new group.
533 * @param array $classes Test classes.
534 * @return TestSuite Group loaded with the new
535 * test cases.
536 * @access private
538 function &_createGroupFromClasses($title, $classes) {
539 SimpleTest::ignoreParentsIfIgnored($classes);
540 $group = &new TestSuite($title);
541 foreach ($classes as $class) {
542 if (! SimpleTest::isIgnored($class)) {
543 $group->addTestClass($class);
546 return $group;
550 * Test to see if a class is derived from the
551 * SimpleTestCase class.
552 * @param string $class Class name.
553 * @access private
555 function _getBaseTestCase($class) {
556 while ($class = get_parent_class($class)) {
557 $class = strtolower($class);
558 if ($class == 'simpletestcase' || $class == 'testsuite' || $class == 'grouptest') {
559 return $class;
562 return false;
566 * Delegates to a visiting collector to add test
567 * files.
568 * @param string $path Path to scan from.
569 * @param SimpleCollector $collector Directory scanner.
570 * @access public
572 function collect($path, &$collector) {
573 $collector->collect($this, $path);
577 * Invokes run() on all of the held test cases, instantiating
578 * them if necessary.
579 * @param SimpleReporter $reporter Current test reporter.
580 * @access public
582 function run(&$reporter) {
583 $reporter->paintGroupStart($this->getLabel(), $this->getSize());
584 for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
585 if (is_string($this->_test_cases[$i])) {
586 $class = $this->_test_cases[$i];
587 $test = &new $class();
588 $test->run($reporter);
589 unset($test);
590 } else {
591 $this->_test_cases[$i]->run($reporter);
594 $reporter->paintGroupEnd($this->getLabel());
595 return $reporter->getStatus();
599 * Number of contained test cases.
600 * @return integer Total count of cases in the group.
601 * @access public
603 function getSize() {
604 $count = 0;
605 foreach ($this->_test_cases as $case) {
606 if (is_string($case)) {
607 $count++;
608 } else {
609 $count += $case->getSize();
612 return $count;
617 * @deprecated
619 class GroupTest extends TestSuite { }
622 * This is a failing group test for when a test suite hasn't
623 * loaded properly.
624 * @package SimpleTest
625 * @subpackage UnitTester
627 class BadTestSuite {
628 var $_label;
629 var $_error;
632 * Sets the name of the test suite and error message.
633 * @param string $label Name sent at the start and end
634 * of the test.
635 * @access public
637 function BadTestSuite($label, $error) {
638 $this->_label = $label;
639 $this->_error = $error;
643 * Accessor for the test name for subclasses.
644 * @return string Name of the test.
645 * @access public
647 function getLabel() {
648 return $this->_label;
652 * Sends a single error to the reporter.
653 * @param SimpleReporter $reporter Current test reporter.
654 * @access public
656 function run(&$reporter) {
657 $reporter->paintGroupStart($this->getLabel(), $this->getSize());
658 $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
659 '] with error [' . $this->_error . ']');
660 $reporter->paintGroupEnd($this->getLabel());
661 return $reporter->getStatus();
665 * Number of contained test cases. Always zero.
666 * @return integer Total count of cases in the group.
667 * @access public
669 function getSize() {
670 return 0;
675 * @deprecated
677 class BadGroupTest extends BadTestSuite { }