source: trunk/server/www/vendors/simpletest/test_case.php @ 6

Last change on this file since 6 was 6, checked in by sander, 11 years ago

Added SimpleTest? test framework

File size: 21.8 KB
Line 
1<?php
2/**
3 *  Base include file for SimpleTest
4 *  @package    SimpleTest
5 *  @subpackage UnitTester
6 *  @version    $Id: test_case.php 1726 2008-04-08 01:20:10Z lastcraft $
7 */
8
9/**#@+
10 * Includes SimpleTest files and defined the root constant
11 * for dependent libraries.
12 */
13require_once(dirname(__FILE__) . '/invoker.php');
14require_once(dirname(__FILE__) . '/errors.php');
15require_once(dirname(__FILE__) . '/compatibility.php');
16require_once(dirname(__FILE__) . '/scorer.php');
17require_once(dirname(__FILE__) . '/expectation.php');
18require_once(dirname(__FILE__) . '/dumper.php');
19require_once(dirname(__FILE__) . '/simpletest.php');
20if (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}
26if (! 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 */
42class 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 including myelf.
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 */
378class 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        $existing_globals = get_defined_vars();
391        include_once($test_file);
392        $new_globals = get_defined_vars();
393        $this->_makeFileVariablesGlobal($existing_globals, $new_globals);
394        $new_classes = array_diff(get_declared_classes(), $existing_classes);
395        if (empty($new_classes)) {
396            $new_classes = $this->_scrapeClassesFromFile($test_file);
397        }
398        $classes = $this->selectRunnableTests($new_classes);
399        $suite = &$this->createSuiteFromClasses($test_file, $classes);
400        return $suite;
401    }
402   
403    /**
404     *    Imports new variables into the global namespace.
405     *    @param hash $existing   Variables before the file was loaded.
406     *    @param hash $new        Variables after the file was loaded.
407     *    @access private
408     */
409    function _makeFileVariablesGlobal($existing, $new) {
410        $globals = array_diff(array_keys($new), array_keys($existing));
411        foreach ($globals as $global) {
412            $_GLOBALS[$global] = $new[$global];
413        }
414    }
415   
416    /**
417     *    Lookup classnames from file contents, in case the
418     *    file may have been included before.
419     *    Note: This is probably too clever by half. Figuring this
420     *    out after a failed test case is going to be tricky for us,
421     *    never mind the user. A test case should not be included
422     *    twice anyway.
423     *    @param string $test_file        File name with classes.
424     *    @access private
425     */
426    function _scrapeClassesFromFile($test_file) {
427        preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi',
428                        file_get_contents($test_file),
429                        $matches );
430        return $matches[1];
431    }
432
433    /**
434     *    Calculates the incoming test cases. Skips abstract
435     *    and ignored classes.
436     *    @param array $candidates   Candidate classes.
437     *    @return array              New classes which are test
438     *                               cases that shouldn't be ignored.
439     *    @access public
440     */
441    function selectRunnableTests($candidates) {
442        $classes = array();
443        foreach ($candidates as $class) {
444            if (TestSuite::getBaseTestCase($class)) {
445                $reflection = new SimpleReflection($class);
446                if ($reflection->isAbstract()) {
447                    SimpleTest::ignore($class);
448                } else {
449                    $classes[] = $class;
450                }
451            }
452        }
453        return $classes;
454    }
455
456    /**
457     *    Builds a test suite from a class list.
458     *    @param string $title       Title of new group.
459     *    @param array $classes      Test classes.
460     *    @return TestSuite          Group loaded with the new
461     *                               test cases.
462     *    @access public
463     */
464    function &createSuiteFromClasses($title, $classes) {
465        if (count($classes) == 0) {
466            $suite = &new BadTestSuite($title, "No runnable test cases in [$title]");
467            return $suite;
468        }
469        SimpleTest::ignoreParentsIfIgnored($classes);
470        $suite = &new TestSuite($title);
471        foreach ($classes as $class) {
472            if (! SimpleTest::isIgnored($class)) {
473                $suite->addTestClass($class);
474            }
475        }
476        return $suite;
477    }
478}
479
480/**
481 *    This is a composite test class for combining
482 *    test cases and other RunnableTest classes into
483 *    a group test.
484 *    @package      SimpleTest
485 *    @subpackage   UnitTester
486 */
487class TestSuite {
488    var $_label;
489    var $_test_cases;
490
491    /**
492     *    Sets the name of the test suite.
493     *    @param string $label    Name sent at the start and end
494     *                            of the test.
495     *    @access public
496     */
497    function TestSuite($label = false) {
498        $this->_label = $label;
499        $this->_test_cases = array();
500    }
501
502    /**
503     *    Accessor for the test name for subclasses. If the suite
504     *    wraps a single test case the label defaults to the name of that test.
505     *    @return string           Name of the test.
506     *    @access public
507     */
508    function getLabel() {
509        if (! $this->_label) {
510            return ($this->getSize() == 1) ?
511                    get_class($this->_test_cases[0]) : get_class($this);
512        } else {
513            return $this->_label;
514        }
515    }
516
517    /**
518     *    @deprecated
519     */
520    function addTestCase(&$test_case) {
521        $this->_test_cases[] = &$test_case;
522    }
523
524    /**
525     *    @deprecated
526     */
527    function addTestClass($class) {
528        if (TestSuite::getBaseTestCase($class) == 'testsuite') {
529            $this->_test_cases[] = &new $class();
530        } else {
531            $this->_test_cases[] = $class;
532        }
533    }
534
535    /**
536     *    Adds a test into the suite by instance or class. The class will
537     *    be instantiated if it's a test suite.
538     *    @param SimpleTestCase $test_case  Suite or individual test
539     *                                      case implementing the
540     *                                      runnable test interface.
541     *    @access public
542     */
543    function add(&$test_case) {
544        if (! is_string($test_case)) {
545            $this->_test_cases[] = &$test_case;
546        } elseif (TestSuite::getBaseTestCase($class) == 'testsuite') {
547            $this->_test_cases[] = &new $class();
548        } else {
549            $this->_test_cases[] = $class;
550        }
551    }
552
553    /**
554     *    @deprecated
555     */
556    function addTestFile($test_file) {
557        $this->addFile($test_file);
558    }
559
560    /**
561     *    Builds a test suite from a library of test cases.
562     *    The new suite is composed into this one.
563     *    @param string $test_file        File name of library with
564     *                                    test case classes.
565     *    @access public
566     */
567    function addFile($test_file) {
568        $extractor = new SimpleFileLoader();
569        $this->add($extractor->load($test_file));
570    }
571
572    /**
573     *    Delegates to a visiting collector to add test
574     *    files.
575     *    @param string $path                  Path to scan from.
576     *    @param SimpleCollector $collector    Directory scanner.
577     *    @access public
578     */
579    function collect($path, &$collector) {
580        $collector->collect($this, $path);
581    }
582
583    /**
584     *    Invokes run() on all of the held test cases, instantiating
585     *    them if necessary.
586     *    @param SimpleReporter $reporter    Current test reporter.
587     *    @access public
588     */
589    function run(&$reporter) {
590        $reporter->paintGroupStart($this->getLabel(), $this->getSize());
591        for ($i = 0, $count = count($this->_test_cases); $i < $count; $i++) {
592            if (is_string($this->_test_cases[$i])) {
593                $class = $this->_test_cases[$i];
594                $test = &new $class();
595                $test->run($reporter);
596                unset($test);
597            } else {
598                $this->_test_cases[$i]->run($reporter);
599            }
600        }
601        $reporter->paintGroupEnd($this->getLabel());
602        return $reporter->getStatus();
603    }
604
605    /**
606     *    Number of contained test cases.
607     *    @return integer     Total count of cases in the group.
608     *    @access public
609     */
610    function getSize() {
611        $count = 0;
612        foreach ($this->_test_cases as $case) {
613            if (is_string($case)) {
614                if (! SimpleTest::isIgnored($case)) {
615                    $count++;
616                }
617            } else {
618                $count += $case->getSize();
619            }
620        }
621        return $count;
622    }
623
624    /**
625     *    Test to see if a class is derived from the
626     *    SimpleTestCase class.
627     *    @param string $class     Class name.
628     *    @access public
629     *    @static
630     */
631    function getBaseTestCase($class) {
632        while ($class = get_parent_class($class)) {
633            $class = strtolower($class);
634            if ($class == 'simpletestcase' || $class == 'testsuite') {
635                return $class;
636            }
637        }
638        return false;
639    }
640}
641
642/**
643 *    @package      SimpleTest
644 *    @subpackage   UnitTester
645 *    @deprecated
646 */
647class GroupTest extends TestSuite { }
648
649/**
650 *    This is a failing group test for when a test suite hasn't
651 *    loaded properly.
652 *    @package      SimpleTest
653 *    @subpackage   UnitTester
654 */
655class BadTestSuite {
656    var $_label;
657    var $_error;
658
659    /**
660     *    Sets the name of the test suite and error message.
661     *    @param string $label    Name sent at the start and end
662     *                            of the test.
663     *    @access public
664     */
665    function BadTestSuite($label, $error) {
666        $this->_label = $label;
667        $this->_error = $error;
668    }
669
670    /**
671     *    Accessor for the test name for subclasses.
672     *    @return string           Name of the test.
673     *    @access public
674     */
675    function getLabel() {
676        return $this->_label;
677    }
678
679    /**
680     *    Sends a single error to the reporter.
681     *    @param SimpleReporter $reporter    Current test reporter.
682     *    @access public
683     */
684    function run(&$reporter) {
685        $reporter->paintGroupStart($this->getLabel(), $this->getSize());
686        $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
687                '] with error [' . $this->_error . ']');
688        $reporter->paintGroupEnd($this->getLabel());
689        return $reporter->getStatus();
690    }
691
692    /**
693     *    Number of contained test cases. Always zero.
694     *    @return integer     Total count of cases in the group.
695     *    @access public
696     */
697    function getSize() {
698        return 0;
699    }
700}
701
702/**
703 *    @package      SimpleTest
704 *    @subpackage   UnitTester
705 *    @deprecated
706 */
707class BadGroupTest extends BadTestSuite { }
708?>
Note: See TracBrowser for help on using the repository browser.