0337d704 |
1 | <?php |
2 | /** |
3 | * Base include file for SimpleTest |
4 | * @package SimpleTest |
5 | * @subpackage UnitTester |
6 | * @version $Id: simple_test.php,v 1.71 2004/08/21 00:38:04 lastcraft Exp $ |
7 | */ |
8 | |
9 | /**#@+ |
10 | * Includes SimpleTest files and defined the root constant |
11 | * for dependent libraries. |
12 | */ |
13 | require_once(dirname(__FILE__) . '/errors.php'); |
14 | require_once(dirname(__FILE__) . '/options.php'); |
15 | require_once(dirname(__FILE__) . '/scorer.php'); |
16 | require_once(dirname(__FILE__) . '/expectation.php'); |
17 | require_once(dirname(__FILE__) . '/dumper.php'); |
18 | if (! defined('SIMPLE_TEST')) { |
19 | define('SIMPLE_TEST', dirname(__FILE__) . '/'); |
20 | } |
21 | /**#@-*/ |
22 | |
23 | /** |
24 | * The standard runner. Will run every method starting |
25 | * with test as well as the setUp() and tearDown() |
26 | * before and after each test method. Basically the |
27 | * Mediator pattern. |
28 | * @package SimpleTest |
29 | * @subpackage UnitTester |
30 | */ |
31 | class SimpleRunner { |
32 | var $_test_case; |
33 | var $_scorer; |
34 | |
35 | /** |
36 | * Takes in the test case and reporter to mediate between. |
37 | * @param SimpleTestCase $test_case Test case to run. |
38 | * @param SimpleScorer $scorer Reporter to receive events. |
39 | */ |
40 | function SimpleRunner(&$test_case, &$scorer) { |
41 | $this->_test_case = &$test_case; |
42 | $this->_scorer = &$scorer; |
43 | } |
44 | |
45 | /** |
46 | * Accessor for test case being run. |
47 | * @return SimpleTestCase Test case. |
48 | * @access protected |
49 | */ |
50 | function &_getTestCase() { |
51 | return $this->_test_case; |
52 | } |
53 | |
54 | /** |
55 | * Runs the test methods in the test case. |
56 | * @param SimpleTest $test_case Test case to run test on. |
57 | * @param string $method Name of test method. |
58 | * @access public |
59 | */ |
60 | function run() { |
61 | $methods = get_class_methods(get_class($this->_test_case)); |
62 | foreach ($methods as $method) { |
63 | if (! $this->_isTest($method)) { |
64 | continue; |
65 | } |
66 | if ($this->_isConstructor($method)) { |
67 | continue; |
68 | } |
69 | $this->_scorer->paintMethodStart($method); |
70 | $this->_scorer->invoke($this, $method); |
71 | $this->_scorer->paintMethodEnd($method); |
72 | } |
73 | } |
74 | |
75 | /** |
76 | * Tests to see if the method is the constructor and |
77 | * so should be ignored. |
78 | * @param string $method Method name to try. |
79 | * @return boolean True if constructor. |
80 | * @access protected |
81 | */ |
82 | function _isConstructor($method) { |
83 | return SimpleTestCompatibility::isA( |
84 | $this->_test_case, |
85 | strtolower($method)); |
86 | } |
87 | |
88 | /** |
89 | * Tests to see if the method is a test that should |
90 | * be run. Currently any method that starts with 'test' |
91 | * is a candidate. |
92 | * @param string $method Method name to try. |
93 | * @return boolean True if test method. |
94 | * @access protected |
95 | */ |
96 | function _isTest($method) { |
97 | return strtolower(substr($method, 0, 4)) == 'test'; |
98 | } |
99 | |
100 | /** |
101 | * Invokes a test method and buffered with setUp() |
102 | * and tearDown() calls. |
103 | * @param string $method Test method to call. |
104 | * @access public |
105 | */ |
106 | function invoke($method) { |
107 | $this->_test_case->before(); |
108 | $this->_test_case->setUp(); |
109 | $this->_test_case->$method(); |
110 | $this->_test_case->tearDown(); |
111 | $this->_test_case->after(); |
112 | } |
113 | |
114 | /** |
115 | * Paints the start of a test method. |
116 | * @param string $test_name Name of test or other label. |
117 | * @access public |
118 | */ |
119 | function paintMethodStart($test_name) { |
120 | $this->_scorer->paintMethodStart($test_name); |
121 | } |
122 | |
123 | /** |
124 | * Paints the end of a test method. |
125 | * @param string $test_name Name of test or other label. |
126 | * @access public |
127 | */ |
128 | function paintMethodEnd($test_name) { |
129 | $this->_scorer->paintMethodEnd($test_name); |
130 | } |
131 | |
132 | /** |
133 | * Chains to the wrapped reporter. |
134 | * @param string $message Message is ignored. |
135 | * @access public |
136 | */ |
137 | function paintPass($message) { |
138 | $this->_scorer->paintPass($message); |
139 | } |
140 | |
141 | /** |
142 | * Chains to the wrapped reporter. |
143 | * @param string $message Message is ignored. |
144 | * @access public |
145 | */ |
146 | function paintFail($message) { |
147 | $this->_scorer->paintFail($message); |
148 | } |
149 | |
150 | /** |
151 | * Chains to the wrapped reporter. |
152 | * @param string $message Text of error formatted by |
153 | * the test case. |
154 | * @access public |
155 | */ |
156 | function paintError($message) { |
157 | $this->_scorer->paintError($message); |
158 | } |
159 | |
160 | /** |
161 | * Chains to the wrapped reporter. |
162 | * @param Exception $exception Object thrown. |
163 | * @access public |
164 | */ |
165 | function paintException($exception) { |
166 | $this->_scorer->paintException($exception); |
167 | } |
168 | |
169 | /** |
170 | * Chains to the wrapped reporter. |
171 | * @param string $message Text to display. |
172 | * @access public |
173 | */ |
174 | function paintMessage($message) { |
175 | $this->_scorer->paintMessage($message); |
176 | } |
177 | |
178 | /** |
179 | * Chains to the wrapped reporter. |
180 | * @param string $message Text to display. |
181 | * @access public |
182 | */ |
183 | function paintFormattedMessage($message) { |
184 | $this->_scorer->paintFormattedMessage($message); |
185 | } |
186 | |
187 | /** |
188 | * Chains to the wrapped reporter. |
189 | * @param string $type Event type as text. |
190 | * @param mixed $payload Message or object. |
191 | * @return boolean Should return false if this |
192 | * type of signal should fail the |
193 | * test suite. |
194 | * @access public |
195 | */ |
196 | function paintSignal($type, &$payload) { |
197 | $this->_scorer->paintSignal($type, $payload); |
198 | } |
199 | } |
200 | |
201 | /** |
202 | * Extension that traps errors into an error queue. |
203 | * @package SimpleTest |
204 | * @subpackage UnitTester |
205 | */ |
206 | class SimpleErrorTrappingRunner extends SimpleRunner { |
207 | |
208 | /** |
209 | * Takes in the test case and reporter to mediate between. |
210 | * @param SimpleTestCase $test_case Test case to run. |
211 | * @param SimpleScorer $scorer Reporter to receive events. |
212 | */ |
213 | function SimpleErrorTrappingRunner(&$test_case, &$scorer) { |
214 | $this->SimpleRunner($test_case, $scorer); |
215 | } |
216 | |
217 | /** |
218 | * Invokes a test method and dispatches any |
219 | * untrapped errors. Called back from |
220 | * the visiting runner. |
221 | * @param string $method Test method to call. |
222 | * @access public |
223 | */ |
224 | function invoke($method) { |
225 | set_error_handler('simpleTestErrorHandler'); |
226 | parent::invoke($method); |
227 | $queue = &SimpleErrorQueue::instance(); |
228 | while (list($severity, $message, $file, $line, $globals) = $queue->extract()) { |
229 | $test_case = &$this->_getTestCase(); |
230 | $test_case->error($severity, $message, $file, $line, $globals); |
231 | } |
232 | restore_error_handler(); |
233 | } |
234 | } |
235 | |
236 | /** |
237 | * Basic test case. This is the smallest unit of a test |
238 | * suite. It searches for |
239 | * all methods that start with the the string "test" and |
240 | * runs them. Working test cases extend this class. |
241 | * @package SimpleTest |
242 | * @subpackage UnitTester |
243 | */ |
244 | class SimpleTestCase { |
245 | var $_label; |
246 | var $_runner; |
247 | |
248 | /** |
249 | * Sets up the test with no display. |
250 | * @param string $label If no test name is given then |
251 | * the class name is used. |
252 | * @access public |
253 | */ |
254 | function SimpleTestCase($label = false) { |
255 | $this->_label = $label ? $label : get_class($this); |
256 | $this->_runner = false; |
257 | } |
258 | |
259 | /** |
260 | * Accessor for the test name for subclasses. |
261 | * @return string Name of the test. |
262 | * @access public |
263 | * @static |
264 | */ |
265 | function getLabel() { |
266 | return $this->_label; |
267 | } |
268 | |
269 | /** |
270 | * Can modify the incoming reporter so as to run |
271 | * the tests differently. This version simply |
272 | * passes it straight through. |
273 | * @param SimpleReporter $reporter Incoming observer. |
274 | * @return SimpleReporter |
275 | * @access protected |
276 | */ |
277 | function &_createRunner(&$reporter) { |
278 | return new SimpleErrorTrappingRunner($this, $reporter); |
279 | } |
280 | |
281 | /** |
282 | * Uses reflection to run every method within itself |
283 | * starting with the string "test". |
284 | * @param SimpleReporter $reporter Current test reporter. |
285 | * @access public |
286 | */ |
287 | function run(&$reporter) { |
288 | $reporter->paintCaseStart($this->getLabel()); |
289 | $this->_runner = &$this->_createRunner($reporter); |
290 | $this->_runner->run(); |
291 | $reporter->paintCaseEnd($this->getLabel()); |
292 | return $reporter->getStatus(); |
293 | } |
294 | |
295 | /** |
296 | * Runs test case specific code before the user setUp(). |
297 | * For extension writers not wanting to interfere with setUp(). |
298 | * @access protected |
299 | */ |
300 | function before() { |
301 | } |
302 | |
303 | /** |
304 | * Runs test case specific code after the user tearDown(). |
305 | * For extension writers not wanting to interfere with user tests. |
306 | * @access protected |
307 | */ |
308 | function after() { |
309 | } |
310 | |
311 | /** |
312 | * Sets up unit test wide variables at the start |
313 | * of each test method. To be overridden in |
314 | * actual user test cases. |
315 | * @access public |
316 | */ |
317 | function setUp() { |
318 | } |
319 | |
320 | /** |
321 | * Clears the data set in the setUp() method call. |
322 | * To be overridden by the user in actual user test cases. |
323 | * @access public |
324 | */ |
325 | function tearDown() { |
326 | } |
327 | |
328 | /** |
329 | * Sends a pass event with a message. |
330 | * @param string $message Message to send. |
331 | * @access public |
332 | */ |
333 | function pass($message = "Pass") { |
334 | $this->_runner->paintPass($message . $this->getAssertionLine(' at line [%d]')); |
335 | } |
336 | |
337 | /** |
338 | * Sends a fail event with a message. |
339 | * @param string $message Message to send. |
340 | * @access public |
341 | */ |
342 | function fail($message = "Fail") { |
343 | $this->_runner->paintFail($message . $this->getAssertionLine(' at line [%d]')); |
344 | } |
345 | |
346 | /** |
347 | * Formats a PHP error and dispatches it to the |
348 | * runner. |
349 | * @param integer $severity PHP error code. |
350 | * @param string $message Text of error. |
351 | * @param string $file File error occoured in. |
352 | * @param integer $line Line number of error. |
353 | * @param hash $globals PHP super global arrays. |
354 | * @access public |
355 | */ |
356 | function error($severity, $message, $file, $line, $globals) { |
357 | $severity = SimpleErrorQueue::getSeverityAsString($severity); |
358 | $this->_runner->paintError( |
359 | "Unexpected PHP error [$message] severity [$severity] in [$file] line [$line]"); |
360 | } |
361 | |
362 | /** |
363 | * Sends a user defined event to the test runner. |
364 | * This is for small scale extension where |
365 | * both the test case and either the runner or |
366 | * display are subclassed. |
367 | * @param string $type Type of event. |
368 | * @param mixed $payload Object or message to deliver. |
369 | * @access public |
370 | */ |
371 | function signal($type, &$payload) { |
372 | $this->_runner->paintSignal($type, $payload); |
373 | } |
374 | |
375 | /** |
376 | * Cancels any outstanding errors. |
377 | * @access public |
378 | */ |
379 | function swallowErrors() { |
380 | $queue = &SimpleErrorQueue::instance(); |
381 | $queue->clear(); |
382 | } |
383 | |
384 | /** |
385 | * Runs an expectation directly, for extending the |
386 | * tests with new expectation classes. |
387 | * @param SimpleExpectation $expectation Expectation subclass. |
388 | * @param mixed $test_value Value to compare. |
389 | * @param string $message Message to display. |
390 | * @return boolean True on pass |
391 | * @access public |
392 | */ |
393 | function assertExpectation(&$expectation, $test_value, $message = '%s') { |
394 | return $this->assertTrue( |
395 | $expectation->test($test_value), |
396 | sprintf($message, $expectation->overlayMessage($test_value))); |
397 | } |
398 | |
399 | /** |
400 | * Called from within the test methods to register |
401 | * passes and failures. |
402 | * @param boolean $result Pass on true. |
403 | * @param string $message Message to display describing |
404 | * the test state. |
405 | * @return boolean True on pass |
406 | * @access public |
407 | */ |
408 | function assertTrue($result, $message = false) { |
409 | if (! $message) { |
410 | $message = 'True assertion got ' . ($result ? 'True' : 'False'); |
411 | } |
412 | if ($result) { |
413 | $this->pass($message); |
414 | return true; |
415 | } else { |
416 | $this->fail($message); |
417 | return false; |
418 | } |
419 | } |
420 | |
421 | /** |
422 | * Will be true on false and vice versa. False |
423 | * is the PHP definition of false, so that null, |
424 | * empty strings, zero and an empty array all count |
425 | * as false. |
426 | * @param boolean $result Pass on false. |
427 | * @param string $message Message to display. |
428 | * @return boolean True on pass |
429 | * @access public |
430 | */ |
431 | function assertFalse($result, $message = false) { |
432 | if (! $message) { |
433 | $message = 'False assertion got ' . ($result ? 'True' : 'False'); |
434 | } |
435 | return ! $this->assertTrue(! $result, $message); |
436 | } |
437 | |
438 | /** |
439 | * Uses a stack trace to find the line of an assertion. |
440 | * @param string $format String formatting. |
441 | * @param array $stack Stack frames top most first. Only |
442 | * needed if not using the PHP |
443 | * backtrace function. |
444 | * @return string Line number of first assert* |
445 | * method embedded in format string. |
446 | * @access public |
447 | */ |
448 | function getAssertionLine($format = '%d', $stack = false) { |
449 | if ($stack === false) { |
450 | $stack = SimpleTestCompatibility::getStackTrace(); |
451 | } |
452 | return SimpleDumper::getFormattedAssertionLine($stack, $format); |
453 | } |
454 | |
455 | /** |
456 | * Sends a formatted dump of a variable to the |
457 | * test suite for those emergency debugging |
458 | * situations. |
459 | * @param mixed $variable Variable to display. |
460 | * @param string $message Message to display. |
461 | * @return mixed The original variable. |
462 | * @access public |
463 | */ |
464 | function dump($variable, $message = false) { |
465 | $formatted = SimpleDumper::dump($variable); |
466 | if ($message) { |
467 | $formatted = $message . "\n" . $formatted; |
468 | } |
469 | $this->_runner->paintFormattedMessage($formatted); |
470 | return $variable; |
471 | } |
472 | |
473 | /** |
474 | * Dispatches a text message straight to the |
475 | * test suite. Useful for status bar displays. |
476 | * @param string $message Message to show. |
477 | * @access public |
478 | */ |
479 | function sendMessage($message) { |
480 | $this->_runner->PaintMessage($message); |
481 | } |
482 | |
483 | /** |
484 | * Accessor for the number of subtests. |
485 | * @return integer Number of test cases. |
486 | * @access public |
487 | * @static |
488 | */ |
489 | function getSize() { |
490 | return 1; |
491 | } |
492 | } |
493 | |
494 | /** |
495 | * This is a composite test class for combining |
496 | * test cases and other RunnableTest classes into |
497 | * a group test. |
498 | * @package SimpleTest |
499 | * @subpackage UnitTester |
500 | */ |
501 | class GroupTest { |
502 | var $_label; |
503 | var $_test_cases; |
504 | |
505 | /** |
506 | * Sets the name of the test suite. |
507 | * @param string $label Name sent at the start and end |
508 | * of the test. |
509 | * @access public |
510 | */ |
511 | function GroupTest($label) { |
512 | $this->_label = $label; |
513 | $this->_test_cases = array(); |
514 | } |
515 | |
516 | /** |
517 | * Accessor for the test name for subclasses. |
518 | * @return string Name of the test. |
519 | * @access public |
520 | */ |
521 | function getLabel() { |
522 | return $this->_label; |
523 | } |
524 | |
525 | /** |
526 | * Adds a test into the suite. Can be either a group |
527 | * test or some other unit test. |
528 | * @param SimpleTestCase $test_case Suite or individual test |
529 | * case implementing the |
530 | * runnable test interface. |
531 | * @access public |
532 | */ |
533 | function addTestCase(&$test_case) { |
534 | $this->_test_cases[] = &$test_case; |
535 | } |
536 | |
537 | /** |
538 | * Adds a test into the suite by class name. The class will |
539 | * be instantiated as needed. |
540 | * @param SimpleTestCase $test_case Suite or individual test |
541 | * case implementing the |
542 | * runnable test interface. |
543 | * @access public |
544 | */ |
545 | function addTestClass($class) { |
546 | $this->_test_cases[] = $class; |
547 | } |
548 | |
549 | /** |
550 | * Builds a group test from a library of test cases. |
551 | * The new group is composed into this one. |
552 | * @param string $test_file File name of library with |
553 | * test case classes. |
554 | * @access public |
555 | */ |
556 | function addTestFile($test_file) { |
557 | $existing_classes = get_declared_classes(); |
558 | require($test_file); |
559 | $group = new GroupTest($test_file); |
560 | foreach (get_declared_classes() as $class) { |
561 | if (in_array($class, $existing_classes)) { |
562 | continue; |
563 | } |
564 | if (! $this->_isTestCase($class)) { |
565 | continue; |
566 | } |
567 | if (SimpleTestOptions::isIgnored($class)) { |
568 | continue; |
569 | } |
570 | $group->addTestClass($class); |
571 | } |
572 | $this->addTestCase($group); |
573 | } |
574 | |
575 | /** |
576 | * Test to see if a class is derived from the |
577 | * TestCase class. |
578 | * @param string $class Class name. |
579 | * @access private |
580 | */ |
581 | function _isTestCase($class) { |
582 | while ($class = get_parent_class($class)) { |
583 | if (strtolower($class) == "simpletestcase") { |
584 | return true; |
585 | } |
586 | } |
587 | return false; |
588 | } |
589 | |
590 | /** |
591 | * Invokes run() on all of the held test cases, instantiating |
592 | * them if necessary. |
593 | * @param SimpleReporter $reporter Current test reporter. |
594 | * @access public |
595 | */ |
596 | function run(&$reporter) { |
597 | $reporter->paintGroupStart($this->getLabel(), $this->getSize()); |
598 | for ($i = 0; $i < count($this->_test_cases); $i++) { |
599 | if (is_string($this->_test_cases[$i])) { |
600 | $class = $this->_test_cases[$i]; |
601 | $test = &new $class(); |
602 | $test->run($reporter); |
603 | } else { |
604 | $this->_test_cases[$i]->run($reporter); |
605 | } |
606 | } |
607 | $reporter->paintGroupEnd($this->getLabel()); |
608 | return $reporter->getStatus(); |
609 | } |
610 | |
611 | /** |
612 | * Number of contained test cases. |
613 | * @return integer Total count of cases in the group. |
614 | * @access public |
615 | */ |
616 | function getSize() { |
617 | $count = 0; |
618 | foreach ($this->_test_cases as $case) { |
619 | if (is_string($case)) { |
620 | $count++; |
621 | } else { |
622 | $count += $case->getSize(); |
623 | } |
624 | } |
625 | return $count; |
626 | } |
627 | } |
628 | ?> |