be65e81f7f534546985d1028bab973a589e7b5d5
[moodle.git] / lib / simpletestlib / test_case.php
1 <?php
2 /**
3  *  Base include file for SimpleTest
4  *  @package    SimpleTest
5  *  @subpackage UnitTester
6  *  @version    $Id$
7  */
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 /**#@-*/
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;
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     }
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     }
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     }
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     }
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     }
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     }
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 //moodlefix begins
152         $context->unsetTest();
153 //moodlefix ends
154         return $reporter->getStatus();
155     }
157     /**
158      *    Gets a list of test names. Normally that will
159      *    be all internal methods that start with the
160      *    name "test". This method should be overridden
161      *    if you want a different rule.
162      *    @return array        List of test names.
163      *    @access public
164      */
165     function getTests() {
166         $methods = array();
167         foreach (get_class_methods(get_class($this)) as $method) {
168             if ($this->_isTest($method)) {
169                 $methods[] = $method;
170             }
171         }
172         return $methods;
173     }
175     /**
176      *    Tests to see if the method is a test that should
177      *    be run. Currently any method that starts with 'test'
178      *    is a candidate unless it is the constructor.
179      *    @param string $method        Method name to try.
180      *    @return boolean              True if test method.
181      *    @access protected
182      */
183     function _isTest($method) {
184         if (strtolower(substr($method, 0, 4)) == 'test') {
185             return ! SimpleTestCompatibility::isA($this, strtolower($method));
186         }
187         return false;
188     }
190     /**
191      *    Announces the start of the test.
192      *    @param string $method    Test method just started.
193      *    @access public
194      */
195     function before($method) {
196         $this->_reporter->paintMethodStart($method);
197         $this->_observers = array();
198     }
200     /**
201      *    Sets up unit test wide variables at the start
202      *    of each test method. To be overridden in
203      *    actual user test cases.
204      *    @access public
205      */
206     function setUp() {
207     }
209     /**
210      *    Clears the data set in the setUp() method call.
211      *    To be overridden by the user in actual user test cases.
212      *    @access public
213      */
214     function tearDown() {
215     }
217     /**
218      *    Announces the end of the test. Includes private clean up.
219      *    @param string $method    Test method just finished.
220      *    @access public
221      */
222     function after($method) {
223         for ($i = 0; $i < count($this->_observers); $i++) {
224             $this->_observers[$i]->atTestEnd($method, $this);
225         }
226         $this->_reporter->paintMethodEnd($method);
227     }
229     /**
230      *    Sets up an observer for the test end.
231      *    @param object $observer    Must have atTestEnd()
232      *                               method.
233      *    @access public
234      */
235     function tell(&$observer) {
236         $this->_observers[] = &$observer;
237     }
239     /**
240      *    @deprecated
241      */
242     function pass($message = "Pass") {
243         if (! isset($this->_reporter)) {
244             trigger_error('Can only make assertions within test methods');
245         }
246         $this->_reporter->paintPass(
247                 $message . $this->getAssertionLine());
248         return true;
249     }
251     /**
252      *    Sends a fail event with a message.
253      *    @param string $message        Message to send.
254      *    @access public
255      */
256     function fail($message = "Fail") {
257         if (! isset($this->_reporter)) {
258             trigger_error('Can only make assertions within test methods');
259         }
260         $this->_reporter->paintFail(
261                 $message . $this->getAssertionLine());
262         return false;
263     }
265     /**
266      *    Formats a PHP error and dispatches it to the
267      *    reporter.
268      *    @param integer $severity  PHP error code.
269      *    @param string $message    Text of error.
270      *    @param string $file       File error occoured in.
271      *    @param integer $line      Line number of error.
272      *    @access public
273      */
274     function error($severity, $message, $file, $line) {
275         if (! isset($this->_reporter)) {
276             trigger_error('Can only make assertions within test methods');
277         }
278         $this->_reporter->paintError(
279                 "Unexpected PHP error [$message] severity [$severity] in [$file line $line]");
280     }
282     /**
283      *    Formats an exception and dispatches it to the
284      *    reporter.
285      *    @param Exception $exception    Object thrown.
286      *    @access public
287      */
288     function exception($exception) {
289         $this->_reporter->paintException($exception);
290     }
292     /**
293      *    @deprecated
294      */
295     function signal($type, &$payload) {
296         if (! isset($this->_reporter)) {
297             trigger_error('Can only make assertions within test methods');
298         }
299         $this->_reporter->paintSignal($type, $payload);
300     }
302     /**
303      *    Runs an expectation directly, for extending the
304      *    tests with new expectation classes.
305      *    @param SimpleExpectation $expectation  Expectation subclass.
306      *    @param mixed $compare               Value to compare.
307      *    @param string $message                 Message to display.
308      *    @return boolean                        True on pass
309      *    @access public
310      */
311     function assert(&$expectation, $compare, $message = '%s') {
312         if ($expectation->test($compare)) {
313             return $this->pass(sprintf(
314                     $message,
315                     $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
316         } else {
317             return $this->fail(sprintf(
318                     $message,
319                     $expectation->overlayMessage($compare, $this->_reporter->getDumper())));
320         }
321     }
323     /**
324      *    @deprecated
325      */
326     function assertExpectation(&$expectation, $compare, $message = '%s') {
327         return $this->assert($expectation, $compare, $message);
328     }
330     /**
331      *    Uses a stack trace to find the line of an assertion.
332      *    @return string           Line number of first assert*
333      *                             method embedded in format string.
334      *    @access public
335      */
336     function getAssertionLine() {
337         $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip'));
338         return $trace->traceMethod();
339     }
341     /**
342      *    Sends a formatted dump of a variable to the
343      *    test suite for those emergency debugging
344      *    situations.
345      *    @param mixed $variable    Variable to display.
346      *    @param string $message    Message to display.
347      *    @return mixed             The original variable.
348      *    @access public
349      */
350     function dump($variable, $message = false) {
351         $dumper = $this->_reporter->getDumper();
352         $formatted = $dumper->dump($variable);
353         if ($message) {
354             $formatted = $message . "\n" . $formatted;
355         }
356         $this->_reporter->paintFormattedMessage($formatted);
357         return $variable;
358     }
360     /**
361      *    @deprecated
362      */
363     function sendMessage($message) {
364         $this->_reporter->PaintMessage($message);
365     }
367     /**
368      *    Accessor for the number of subtests including myelf.
369      *    @return integer           Number of test cases.
370      *    @access public
371      *    @static
372      */
373     function getSize() {
374         return 1;
375     }
378 /**
379  *  Helps to extract test cases automatically from a file.
380  */
381 class SimpleFileLoader {
383     /**
384      *    Builds a test suite from a library of test cases.
385      *    The new suite is composed into this one.
386      *    @param string $test_file        File name of library with
387      *                                    test case classes.
388      *    @return TestSuite               The new test suite.
389      *    @access public
390      */
391     function &load($test_file) {
392         $existing_classes = get_declared_classes();
393         $existing_globals = get_defined_vars();
394         global $CFG;
395         include_once($test_file);
396         $new_globals = get_defined_vars();
397         $this->_makeFileVariablesGlobal($existing_globals, $new_globals);
398         $new_classes = array_diff(get_declared_classes(), $existing_classes);
399         if (empty($new_classes)) {
400             $new_classes = $this->_scrapeClassesFromFile($test_file);
401         }
402         $classes = $this->selectRunnableTests($new_classes);
403         $suite = &$this->createSuiteFromClasses($test_file, $classes);
404         return $suite;
405     }
407     /**
408      *    Imports new variables into the global namespace.
409      *    @param hash $existing   Variables before the file was loaded.
410      *    @param hash $new        Variables after the file was loaded.
411      *    @access private
412      */
413     function _makeFileVariablesGlobal($existing, $new) {
414         $globals = array_diff(array_keys($new), array_keys($existing));
415         foreach ($globals as $global) {
416             $_GLOBALS[$global] = $new[$global];
417         }
418     }
420     /**
421      *    Lookup classnames from file contents, in case the
422      *    file may have been included before.
423      *    Note: This is probably too clever by half. Figuring this
424      *    out after a failed test case is going to be tricky for us,
425      *    never mind the user. A test case should not be included
426      *    twice anyway.
427      *    @param string $test_file        File name with classes.
428      *    @access private
429      */
430     function _scrapeClassesFromFile($test_file) {
431         preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi',
432                         file_get_contents($test_file),
433                         $matches );
434         return $matches[1];
435     }
437     /**
438      *    Calculates the incoming test cases. Skips abstract
439      *    and ignored classes.
440      *    @param array $candidates   Candidate classes.
441      *    @return array              New classes which are test
442      *                               cases that shouldn't be ignored.
443      *    @access public
444      */
445     function selectRunnableTests($candidates) {
446         $classes = array();
447         foreach ($candidates as $class) {
448             if (TestSuite::getBaseTestCase($class)) {
449                 $reflection = new SimpleReflection($class);
450                 if ($reflection->isAbstract()) {
451                     SimpleTest::ignore($class);
452                 } else {
453                     $classes[] = $class;
454                 }
455             }
456         }
457         return $classes;
458     }
460     /**
461      *    Builds a test suite from a class list.
462      *    @param string $title       Title of new group.
463      *    @param array $classes      Test classes.
464      *    @return TestSuite          Group loaded with the new
465      *                               test cases.
466      *    @access public
467      */
468     function &createSuiteFromClasses($title, $classes) {
469         if (count($classes) == 0) {
470             $suite = new BadTestSuite($title, "No runnable test cases in [$title]");
471             return $suite;
472         }
473         SimpleTest::ignoreParentsIfIgnored($classes);
474         $suite = new TestSuite($title);
475         foreach ($classes as $class) {
476             if (! SimpleTest::isIgnored($class)) {
477                 $suite->addTestClass($class);
478             }
479         }
480         return $suite;
481     }
484 /**
485  *    This is a composite test class for combining
486  *    test cases and other RunnableTest classes into
487  *    a group test.
488  *    @package      SimpleTest
489  *    @subpackage   UnitTester
490  */
491 class TestSuite {
492     var $_label;
493     var $_test_cases;
495     /**
496      *    Sets the name of the test suite.
497      *    @param string $label    Name sent at the start and end
498      *                            of the test.
499      *    @access public
500      */
501     function TestSuite($label = false) {
502         $this->_label = $label;
503         $this->_test_cases = array();
504     }
506     /**
507      *    Accessor for the test name for subclasses. If the suite
508      *    wraps a single test case the label defaults to the name of that test.
509      *    @return string           Name of the test.
510      *    @access public
511      */
512     function getLabel() {
513         if (! $this->_label) {
514             return ($this->getSize() == 1) ?
515                     get_class($this->_test_cases[0]) : get_class($this);
516         } else {
517             return $this->_label;
518         }
519     }
521     /**
522      *    @deprecated
523      */
524     function addTestCase(&$test_case) {
525         $this->_test_cases[] = &$test_case;
526     }
528     /**
529      *    @deprecated
530      */
531     function addTestClass($class) {
532         if (TestSuite::getBaseTestCase($class) == 'testsuite') {
533             $this->_test_cases[] = new $class();
534         } else {
535             $this->_test_cases[] = $class;
536         }
537     }
539     /**
540      *    Adds a test into the suite by instance or class. The class will
541      *    be instantiated if it's a test suite.
542      *    @param SimpleTestCase $test_case  Suite or individual test
543      *                                      case implementing the
544      *                                      runnable test interface.
545      *    @access public
546      */
547     function add(&$test_case) {
548         if (! is_string($test_case)) {
549             $this->_test_cases[] = &$test_case;
550         } elseif (TestSuite::getBaseTestCase($class) == 'testsuite') {
551             $this->_test_cases[] = new $class();
552         } else {
553             $this->_test_cases[] = $class;
554         }
555     }
557     /**
558      *    @deprecated
559      */
560     function addTestFile($test_file) {
561         $this->addFile($test_file);
562     }
564     /**
565      *    Builds a test suite from a library of test cases.
566      *    The new suite is composed into this one.
567      *    @param string $test_file        File name of library with
568      *                                    test case classes.
569      *    @access public
570      */
571     function addFile($test_file) {
572         $extractor = new SimpleFileLoader();
573         $this->add($extractor->load($test_file));
574     }
576     /**
577      *    Delegates to a visiting collector to add test
578      *    files.
579      *    @param string $path                  Path to scan from.
580      *    @param SimpleCollector $collector    Directory scanner.
581      *    @access public
582      */
583     function collect($path, &$collector) {
584         $collector->collect($this, $path);
585     }
587     /**
588      *    Invokes run() on all of the held test cases, instantiating
589      *    them if necessary.
590      *    @param SimpleReporter $reporter    Current test reporter.
591      *    @access public
592      */
593     function run(&$reporter) {
594         $reporter->paintGroupStart($this->getLabel(), $this->getSize());
595         for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
596             if (is_string($this->_test_cases[$i])) {
597                 $class = $this->_test_cases[$i];
598                 // moodle hack start - need to do this before the constructor call, because of FakeDBUnitTestCase.
599                 global $CFG;
600                 if (is_subclass_of($class, 'FakeDBUnitTestCase')) {
601                     // Do not execute this test because the test tables system no longer works, reporting it as exception
602                     $reporter->paintError("Unit test \"{$class}\" of type FakeDBUnitTestCase no longer supported. Must be migrated to UnitTestCaseUsingDatabase.");
603                     continue;
604                 }
605                 if (is_subclass_of($class, 'UnitTestCaseUsingDatabase') && empty($CFG->unittestprefix)) {
606                     // Do not execute this test because $CFG->unittestprefix is not set, but it will be required.
607                     $reporter->paintSkip("Unit test \"{$class}\" of type UnitTestCaseUsingDatabase skipped. Must define different, non-conflicting \$CFG->unittestprefix to be runnable.");
608                     continue;
609                 }
610                 if ($currenttl = @ini_get('max_execution_time')) {
611                     @ini_set('max_execution_time', $currenttl);
612                 }
613                 // moodle hack end
614                 $test = new $class();
615                 $test->run($reporter);
616                 unset($test);
617             } else {
618                 $this->_test_cases[$i]->run($reporter);
619             }
620         }
621         $reporter->paintGroupEnd($this->getLabel());
622         return $reporter->getStatus();
623     }
625     /**
626      *    Number of contained test cases.
627      *    @return integer     Total count of cases in the group.
628      *    @access public
629      */
630     function getSize() {
631         $count = 0;
632         foreach ($this->_test_cases as $case) {
633             if (is_string($case)) {
634                 if (! SimpleTest::isIgnored($case)) {
635                     $count++;
636                 }
637             } else {
638                 $count += $case->getSize();
639             }
640         }
641         return $count;
642     }
644     /**
645      *    Test to see if a class is derived from the
646      *    SimpleTestCase class.
647      *    @param string $class     Class name.
648      *    @access public
649      *    @static
650      */
651     function getBaseTestCase($class) {
652         while ($class = get_parent_class($class)) {
653             $class = strtolower($class);
654             if ($class == 'simpletestcase' || $class == 'testsuite') {
655                 return $class;
656             }
657         }
658         return false;
659     }
662 /**
663  *    @package      SimpleTest
664  *    @subpackage   UnitTester
665  *    @deprecated
666  */
667 class GroupTest extends TestSuite {
668     // moodle fix: adding old style constructor to make it compatible with PHP 5.3
669     function GroupTest($label = false) {
670         parent::TestSuite($label);
671     }
674 /**
675  *    This is a failing group test for when a test suite hasn't
676  *    loaded properly.
677  *    @package      SimpleTest
678  *    @subpackage   UnitTester
679  */
680 class BadTestSuite {
681     var $_label;
682     var $_error;
684     /**
685      *    Sets the name of the test suite and error message.
686      *    @param string $label    Name sent at the start and end
687      *                            of the test.
688      *    @access public
689      */
690     function BadTestSuite($label, $error) {
691         $this->_label = $label;
692         $this->_error = $error;
693     }
695     /**
696      *    Accessor for the test name for subclasses.
697      *    @return string           Name of the test.
698      *    @access public
699      */
700     function getLabel() {
701         return $this->_label;
702     }
704     /**
705      *    Sends a single error to the reporter.
706      *    @param SimpleReporter $reporter    Current test reporter.
707      *    @access public
708      */
709     function run(&$reporter) {
710         $reporter->paintGroupStart($this->getLabel(), $this->getSize());
711         $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
712                 '] with error [' . $this->_error . ']');
713         $reporter->paintGroupEnd($this->getLabel());
714         return $reporter->getStatus();
715     }
717     /**
718      *    Number of contained test cases. Always zero.
719      *    @return integer     Total count of cases in the group.
720      *    @access public
721      */
722     function getSize() {
723         return 0;
724     }
727 /**
728  *    @package      SimpleTest
729  *    @subpackage   UnitTester
730  *    @deprecated
731  */
732 class BadGroupTest extends BadTestSuite { }
733 ?>