3 * Base include file for SimpleTest
5 * @subpackage UnitTester
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');
24 require_once(dirname(__FILE__
) . '/reflection_php4.php');
26 if (! defined('SIMPLE_TEST')) {
28 define('SIMPLE_TEST', dirname(__FILE__
) . DIRECTORY_SEPARATOR
);
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.
38 * @subpackage UnitTester
40 class SimpleTestCase
{
44 var $_should_skip = false;
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.
52 function SimpleTestCase($label = false) {
54 $this->_label
= $label;
59 * Accessor for the test name for subclasses.
60 * @return string Name of the test.
64 return $this->_label ?
$this->_label
: get_class($this);
68 * This is a placeholder for skipping tests. In this
69 * method you place skipIf() and skipUnless() calls to
70 * set the skipping state.
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.
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());
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.
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.
107 function &createInvoker() {
108 $invoker = &new SimpleErrorTrappingInvoker(new SimpleInvoker($this));
109 if (version_compare(phpversion(), '5') >= 0) {
110 $invoker = &new SimpleExceptionTrappingInvoker($invoker);
116 * Uses reflection to run every method within itself
117 * starting with the string "test" unless a method
119 * @param SimpleReporter $reporter Current test reporter.
120 * @return boolean True if all tests passed.
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());
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.
153 function getTests() {
155 foreach (get_class_methods(get_class($this)) as $method) {
156 if ($this->_isTest($method)) {
157 $methods[] = $method;
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.
171 function _isTest($method) {
172 if (strtolower(substr($method, 0, 4)) == 'test') {
173 return ! SimpleTestCompatibility
::isA($this, strtolower($method));
179 * Announces the start of the test.
180 * @param string $method Test method just started.
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.
198 * Clears the data set in the setUp() method call.
199 * To be overridden by the user in actual user test cases.
202 function tearDown() {
206 * Announces the end of the test. Includes private clean up.
207 * @param string $method Test method just finished.
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()
223 function tell(&$observer) {
224 $this->_observers
[] = &$observer;
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());
240 * Sends a fail event with a message.
241 * @param string $message Message to send.
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());
254 * Formats a PHP error and dispatches it to the
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.
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
273 * @param Exception $exception Object thrown.
276 function exception($exception) {
277 $this->_reporter
->paintException($exception);
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
299 function assert(&$expectation, $compare, $message = '%s') {
300 if ($expectation->test($compare)) {
301 return $this->pass(sprintf(
303 $expectation->overlayMessage($compare, $this->_reporter
->getDumper())));
305 return $this->fail(sprintf(
307 $expectation->overlayMessage($compare, $this->_reporter
->getDumper())));
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.
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
333 * @param mixed $variable Variable to display.
334 * @param string $message Message to display.
335 * @return mixed The original variable.
338 function dump($variable, $message = false) {
339 $dumper = $this->_reporter
->getDumper();
340 $formatted = $dumper->dump($variable);
342 $formatted = $message . "\n" . $formatted;
344 $this->_reporter
->paintFormattedMessage($formatted);
351 function sendMessage($message) {
352 $this->_reporter
->PaintMessage($message);
356 * Accessor for the number of subtests.
357 * @return integer Number of test cases.
367 * This is a composite test class for combining
368 * test cases and other RunnableTest classes into
370 * @package SimpleTest
371 * @subpackage UnitTester
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
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.
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.
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.
422 function addTestClass($class) {
423 if ($this->_getBaseTestCase($class) == 'testsuite' ||
$this->_getBaseTestCase($class) == 'grouptest') {
424 $this->_test_cases
[] = &new $class();
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
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));
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]"));
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
459 function _requireWithError($file) {
460 $this->_enableErrorReporting();
461 global $CFG; // Moodle patch for $CFG global in unit test files
463 $error = isset($php_errormsg) ?
$php_errormsg : false;
464 $this->_disableErrorReporting();
465 $self_inflicted_errors = array(
466 '/Assigning the return value of new by reference/i',
467 '/var: Deprecated/i',
468 '/Non-static method/i');
469 foreach ($self_inflicted_errors as $pattern) {
470 if (preg_match($pattern, $error)) {
478 * Sets up detection of parse errors. Note that XDebug
479 * interferes with this and has to be disabled. This is
480 * to make sure the correct error code is returned
481 * from unattended scripts.
484 function _enableErrorReporting() {
485 if ($this->_xdebug_is_enabled
) {
488 ini_set('track_errors', true);
492 * Resets detection of parse errors to their old values.
493 * This is to make sure the correct error code is returned
494 * from unattended scripts.
497 function _disableErrorReporting() {
498 ini_set('track_errors', $this->_old_track_errors
);
499 if ($this->_xdebug_is_enabled
) {
505 * Calculates the incoming test cases from a before
506 * and after list of loaded classes. Skips abstract
508 * @param array $existing_classes Classes before require().
509 * @param array $new_classes Classes after require().
510 * @return array New classes which are test
511 * cases that shouldn't be ignored.
514 function _selectRunnableTests($existing_classes, $new_classes) {
516 foreach ($new_classes as $class) {
517 if (in_array($class, $existing_classes)) {
520 if ($this->_getBaseTestCase($class)) {
521 $reflection = new SimpleReflection($class);
522 if ($reflection->isAbstract()) {
523 SimpleTest
::ignore($class);
532 * Builds a group test from a class list.
533 * @param string $title Title of new group.
534 * @param array $classes Test classes.
535 * @return TestSuite Group loaded with the new
539 function &_createGroupFromClasses($title, $classes) {
540 SimpleTest
::ignoreParentsIfIgnored($classes);
541 $group = &new TestSuite($title);
542 foreach ($classes as $class) {
543 if (! SimpleTest
::isIgnored($class)) {
544 $group->addTestClass($class);
551 * Test to see if a class is derived from the
552 * SimpleTestCase class.
553 * @param string $class Class name.
556 function _getBaseTestCase($class) {
557 while ($class = get_parent_class($class)) {
558 $class = strtolower($class);
559 if ($class == 'simpletestcase' ||
$class == 'testsuite' ||
$class == 'grouptest') {
567 * Delegates to a visiting collector to add test
569 * @param string $path Path to scan from.
570 * @param SimpleCollector $collector Directory scanner.
573 function collect($path, &$collector) {
574 $collector->collect($this, $path);
578 * Invokes run() on all of the held test cases, instantiating
580 * @param SimpleReporter $reporter Current test reporter.
583 function run(&$reporter) {
584 $reporter->paintGroupStart($this->getLabel(), $this->getSize());
585 for ($i = 0, $count = count($this->_test_cases
); $i < $count; $i++
) {
586 if (is_string($this->_test_cases
[$i])) {
587 $class = $this->_test_cases
[$i];
588 $test = &new $class();
589 $test->run($reporter);
592 $this->_test_cases
[$i]->run($reporter);
595 $reporter->paintGroupEnd($this->getLabel());
596 return $reporter->getStatus();
600 * Number of contained test cases.
601 * @return integer Total count of cases in the group.
606 foreach ($this->_test_cases
as $case) {
607 if (is_string($case)) {
610 $count +
= $case->getSize();
620 class GroupTest
extends TestSuite
{ }
623 * This is a failing group test for when a test suite hasn't
625 * @package SimpleTest
626 * @subpackage UnitTester
633 * Sets the name of the test suite and error message.
634 * @param string $label Name sent at the start and end
638 function BadTestSuite($label, $error) {
639 $this->_label
= $label;
640 $this->_error
= $error;
644 * Accessor for the test name for subclasses.
645 * @return string Name of the test.
648 function getLabel() {
649 return $this->_label
;
653 * Sends a single error to the reporter.
654 * @param SimpleReporter $reporter Current test reporter.
657 function run(&$reporter) {
658 $reporter->paintGroupStart($this->getLabel(), $this->getSize());
659 $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
660 '] with error [' . $this->_error
. ']');
661 $reporter->paintGroupEnd($this->getLabel());
662 return $reporter->getStatus();
666 * Number of contained test cases. Always zero.
667 * @return integer Total count of cases in the group.
678 class BadGroupTest
extends BadTestSuite
{ }