0337d704 |
1 | <?php |
2 | /** |
3 | * base include file for SimpleTest |
4 | * @package SimpleTest |
5 | * @subpackage UnitTester |
6 | * @version $Id: xml.php,v 1.20 2004/08/04 22:09:39 lastcraft Exp $ |
7 | */ |
8 | |
9 | /**#@+ |
10 | * include other SimpleTest class files |
11 | */ |
12 | require_once(dirname(__FILE__) . '/scorer.php'); |
13 | /**#@-*/ |
14 | |
15 | /** |
16 | * Creates the XML needed for remote communication |
17 | * by SimpleTest. |
18 | * @package SimpleTest |
19 | * @subpackage UnitTester |
20 | */ |
21 | class XmlReporter extends SimpleReporter { |
22 | var $_indent; |
23 | var $_namespace; |
24 | |
25 | /** |
26 | * Does nothing yet. |
27 | * @access public |
28 | */ |
29 | function XmlReporter($namespace = false, $indent = ' ') { |
30 | $this->SimpleReporter(); |
31 | $this->_namespace = ($namespace ? $namespace . ':' : ''); |
32 | $this->_indent = $indent; |
33 | } |
34 | |
35 | /** |
36 | * Calculates the pretty printing indent level |
37 | * from the current level of nesting. |
38 | * @param integer $offset Extra indenting level. |
39 | * @return string Leading space. |
40 | * @access protected |
41 | */ |
42 | function _getIndent($offset = 0) { |
43 | return str_repeat( |
44 | $this->_indent, |
45 | count($this->getTestList()) + $offset); |
46 | } |
47 | |
48 | /** |
49 | * Converts character string to parsed XML |
50 | * entities string. |
51 | * @param string text Unparsed character data. |
52 | * @return string Parsed character data. |
53 | * @access public |
54 | */ |
55 | function toParsedXml($text) { |
56 | return str_replace( |
57 | array('&', '<', '>', '"', '\''), |
58 | array('&', '<', '>', '"', '''), |
59 | $text); |
60 | } |
61 | |
62 | /** |
63 | * Paints the start of a group test. |
64 | * @param string $test_name Name of test that is starting. |
65 | * @param integer $size Number of test cases starting. |
66 | * @access public |
67 | */ |
68 | function paintGroupStart($test_name, $size) { |
69 | parent::paintGroupStart($test_name, $size); |
70 | print $this->_getIndent(); |
71 | print "<" . $this->_namespace . "group size=\"$size\">\n"; |
72 | print $this->_getIndent(1); |
73 | print "<" . $this->_namespace . "name>" . |
74 | $this->toParsedXml($test_name) . |
75 | "</" . $this->_namespace . "name>\n"; |
76 | } |
77 | |
78 | /** |
79 | * Paints the end of a group test. |
80 | * @param string $test_name Name of test that is ending. |
81 | * @access public |
82 | */ |
83 | function paintGroupEnd($test_name) { |
84 | print $this->_getIndent(); |
85 | print "</" . $this->_namespace . "group>\n"; |
86 | parent::paintGroupEnd($test_name); |
87 | } |
88 | |
89 | /** |
90 | * Paints the start of a test case. |
91 | * @param string $test_name Name of test that is starting. |
92 | * @access public |
93 | */ |
94 | function paintCaseStart($test_name) { |
95 | parent::paintCaseStart($test_name); |
96 | print $this->_getIndent(); |
97 | print "<" . $this->_namespace . "case>\n"; |
98 | print $this->_getIndent(1); |
99 | print "<" . $this->_namespace . "name>" . |
100 | $this->toParsedXml($test_name) . |
101 | "</" . $this->_namespace . "name>\n"; |
102 | } |
103 | |
104 | /** |
105 | * Paints the end of a test case. |
106 | * @param string $test_name Name of test that is ending. |
107 | * @access public |
108 | */ |
109 | function paintCaseEnd($test_name) { |
110 | print $this->_getIndent(); |
111 | print "</" . $this->_namespace . "case>\n"; |
112 | parent::paintCaseEnd($test_name); |
113 | } |
114 | |
115 | /** |
116 | * Paints the start of a test method. |
117 | * @param string $test_name Name of test that is starting. |
118 | * @access public |
119 | */ |
120 | function paintMethodStart($test_name) { |
121 | parent::paintMethodStart($test_name); |
122 | print $this->_getIndent(); |
123 | print "<" . $this->_namespace . "test>\n"; |
124 | print $this->_getIndent(1); |
125 | print "<" . $this->_namespace . "name>" . |
126 | $this->toParsedXml($test_name) . |
127 | "</" . $this->_namespace . "name>\n"; |
128 | } |
129 | |
130 | /** |
131 | * Paints the end of a test method. |
132 | * @param string $test_name Name of test that is ending. |
133 | * @param integer $progress Number of test cases ending. |
134 | * @access public |
135 | */ |
136 | function paintMethodEnd($test_name) { |
137 | print $this->_getIndent(); |
138 | print "</" . $this->_namespace . "test>\n"; |
139 | parent::paintMethodEnd($test_name); |
140 | } |
141 | |
142 | /** |
143 | * Increments the pass count. |
144 | * @param string $message Message is ignored. |
145 | * @access public |
146 | */ |
147 | function paintPass($message) { |
148 | parent::paintPass($message); |
149 | print $this->_getIndent(1); |
150 | print "<" . $this->_namespace . "pass>"; |
151 | print $this->toParsedXml($message); |
152 | print "</" . $this->_namespace . "pass>\n"; |
153 | } |
154 | |
155 | /** |
156 | * Increments the fail count. |
157 | * @param string $message Message is ignored. |
158 | * @access public |
159 | */ |
160 | function paintFail($message) { |
161 | parent::paintFail($message); |
162 | print $this->_getIndent(1); |
163 | print "<" . $this->_namespace . "fail>"; |
164 | print $this->toParsedXml($message); |
165 | print "</" . $this->_namespace . "fail>\n"; |
166 | } |
167 | |
168 | /** |
169 | * Paints a PHP error or exception. |
170 | * @param string $message Message is ignored. |
171 | * @access public |
172 | * @abstract |
173 | */ |
174 | function paintException($message) { |
175 | parent::paintException($message); |
176 | print $this->_getIndent(1); |
177 | print "<" . $this->_namespace . "exception>"; |
178 | print $this->toParsedXml($message); |
179 | print "</" . $this->_namespace . "exception>\n"; |
180 | } |
181 | |
182 | /** |
183 | * Paints a simple supplementary message. |
184 | * @param string $message Text to display. |
185 | * @access public |
186 | */ |
187 | function paintMessage($message) { |
188 | parent::paintMessage($message); |
189 | print $this->_getIndent(1); |
190 | print "<" . $this->_namespace . "message>"; |
191 | print $this->toParsedXml($message); |
192 | print "</" . $this->_namespace . "message>\n"; |
193 | } |
194 | |
195 | /** |
196 | * Paints a formatted ASCII message such as a |
197 | * variable dump. |
198 | * @param string $message Text to display. |
199 | * @access public |
200 | */ |
201 | function paintFormattedMessage($message) { |
202 | parent::paintFormattedMessage($message); |
203 | print $this->_getIndent(1); |
204 | print "<" . $this->_namespace . "formatted>"; |
205 | print "<![CDATA[$message]]>"; |
206 | print "</" . $this->_namespace . "formatted>\n"; |
207 | } |
208 | |
209 | /** |
210 | * Serialises the event object. |
211 | * @param string $type Event type as text. |
212 | * @param mixed $payload Message or object. |
213 | * @access public |
214 | */ |
215 | function paintSignal($type, &$payload) { |
216 | parent::paintSignal($type, $payload); |
217 | print $this->_getIndent(1); |
218 | print "<" . $this->_namespace . "signal type=\"$type\">"; |
219 | print "<![CDATA[" . serialize($payload) . "]]>"; |
220 | print "</" . $this->_namespace . "signal>\n"; |
221 | } |
222 | |
223 | /** |
224 | * Paints the test document header. |
225 | * @param string $test_name First test top level |
226 | * to start. |
227 | * @access public |
228 | * @abstract |
229 | */ |
230 | function paintHeader($test_name) { |
231 | if (! SimpleReporter::inCli()) { |
232 | header('Content-type: text/xml'); |
233 | } |
234 | print "<?xml version=\"1.0\""; |
235 | if ($this->_namespace) { |
236 | print " xmlns:" . $this->_namespace . |
237 | "=\"www.lastcraft.com/SimpleTest/Beta3/Report\""; |
238 | } |
239 | print "?>\n"; |
240 | print "<" . $this->_namespace . "run>\n"; |
241 | } |
242 | |
243 | /** |
244 | * Paints the test document footer. |
245 | * @param string $test_name The top level test. |
246 | * @access public |
247 | * @abstract |
248 | */ |
249 | function paintFooter($test_name) { |
250 | print "</" . $this->_namespace . "run>\n"; |
251 | } |
252 | } |
253 | |
254 | /** |
255 | * Accumulator for incoming tag. Holds the |
256 | * incoming test structure information for |
257 | * later dispatch to the reporter. |
258 | * @package SimpleTest |
259 | * @subpackage UnitTester |
260 | */ |
261 | class NestingXmlTag { |
262 | var $_name; |
263 | var $_attributes; |
264 | |
265 | /** |
266 | * Sets the basic test information except |
267 | * the name. |
268 | * @param hash $attributes Name value pairs. |
269 | * @access public |
270 | */ |
271 | function NestingXmlTag($attributes) { |
272 | $this->_name = false; |
273 | $this->_attributes = $attributes; |
274 | } |
275 | |
276 | /** |
277 | * Sets the test case/method name. |
278 | * @param string $name Name of test. |
279 | * @access public |
280 | */ |
281 | function setName($name) { |
282 | $this->_name = $name; |
283 | } |
284 | |
285 | /** |
286 | * Accessor for name. |
287 | * @return string Name of test. |
288 | * @access public |
289 | */ |
290 | function getName() { |
291 | return $this->_name; |
292 | } |
293 | |
294 | /** |
295 | * Accessor for attributes. |
296 | * @return hash All attributes. |
297 | * @access protected |
298 | */ |
299 | function _getAttributes() { |
300 | return $this->_attributes; |
301 | } |
302 | } |
303 | |
304 | /** |
305 | * Accumulator for incoming method tag. Holds the |
306 | * incoming test structure information for |
307 | * later dispatch to the reporter. |
308 | * @package SimpleTest |
309 | * @subpackage UnitTester |
310 | */ |
311 | class NestingMethodTag extends NestingXmlTag { |
312 | |
313 | /** |
314 | * Sets the basic test information except |
315 | * the name. |
316 | * @param hash $attributes Name value pairs. |
317 | * @access public |
318 | */ |
319 | function NestingMethodTag($attributes) { |
320 | $this->NestingXmlTag($attributes); |
321 | } |
322 | |
323 | /** |
324 | * Signals the appropriate start event on the |
325 | * listener. |
326 | * @param SimpleReporter $listener Target for events. |
327 | * @access public |
328 | */ |
329 | function paintStart(&$listener) { |
330 | $listener->paintMethodStart($this->getName()); |
331 | } |
332 | |
333 | /** |
334 | * Signals the appropriate end event on the |
335 | * listener. |
336 | * @param SimpleReporter $listener Target for events. |
337 | * @access public |
338 | */ |
339 | function paintEnd(&$listener) { |
340 | $listener->paintMethodEnd($this->getName()); |
341 | } |
342 | } |
343 | |
344 | /** |
345 | * Accumulator for incoming case tag. Holds the |
346 | * incoming test structure information for |
347 | * later dispatch to the reporter. |
348 | * @package SimpleTest |
349 | * @subpackage UnitTester |
350 | */ |
351 | class NestingCaseTag extends NestingXmlTag { |
352 | |
353 | /** |
354 | * Sets the basic test information except |
355 | * the name. |
356 | * @param hash $attributes Name value pairs. |
357 | * @access public |
358 | */ |
359 | function NestingCaseTag($attributes) { |
360 | $this->NestingXmlTag($attributes); |
361 | } |
362 | |
363 | /** |
364 | * Signals the appropriate start event on the |
365 | * listener. |
366 | * @param SimpleReporter $listener Target for events. |
367 | * @access public |
368 | */ |
369 | function paintStart(&$listener) { |
370 | $listener->paintCaseStart($this->getName()); |
371 | } |
372 | |
373 | /** |
374 | * Signals the appropriate end event on the |
375 | * listener. |
376 | * @param SimpleReporter $listener Target for events. |
377 | * @access public |
378 | */ |
379 | function paintEnd(&$listener) { |
380 | $listener->paintCaseEnd($this->getName()); |
381 | } |
382 | } |
383 | |
384 | /** |
385 | * Accumulator for incoming group tag. Holds the |
386 | * incoming test structure information for |
387 | * later dispatch to the reporter. |
388 | * @package SimpleTest |
389 | * @subpackage UnitTester |
390 | */ |
391 | class NestingGroupTag extends NestingXmlTag { |
392 | |
393 | /** |
394 | * Sets the basic test information except |
395 | * the name. |
396 | * @param hash $attributes Name value pairs. |
397 | * @access public |
398 | */ |
399 | function NestingGroupTag($attributes) { |
400 | $this->NestingXmlTag($attributes); |
401 | } |
402 | |
403 | /** |
404 | * Signals the appropriate start event on the |
405 | * listener. |
406 | * @param SimpleReporter $listener Target for events. |
407 | * @access public |
408 | */ |
409 | function paintStart(&$listener) { |
410 | $listener->paintGroupStart($this->getName(), $this->getSize()); |
411 | } |
412 | |
413 | /** |
414 | * Signals the appropriate end event on the |
415 | * listener. |
416 | * @param SimpleReporter $listener Target for events. |
417 | * @access public |
418 | */ |
419 | function paintEnd(&$listener) { |
420 | $listener->paintGroupEnd($this->getName()); |
421 | } |
422 | |
423 | /** |
424 | * The size in the attributes. |
425 | * @return integer Value of size attribute or zero. |
426 | * @access public |
427 | */ |
428 | function getSize() { |
429 | $attributes = $this->_getAttributes(); |
430 | if (isset($attributes['SIZE'])) { |
431 | return (integer)$attributes['SIZE']; |
432 | } |
433 | return 0; |
434 | } |
435 | } |
436 | |
437 | /** |
438 | * Parser for importing the output of the XmlReporter. |
439 | * Dispatches that output to another reporter. |
440 | * @package SimpleTest |
441 | * @subpackage UnitTester |
442 | */ |
443 | class SimpleTestXmlParser { |
444 | var $_listener; |
445 | var $_expat; |
446 | var $_tag_stack; |
447 | var $_in_content_tag; |
448 | var $_content; |
449 | var $_attributes; |
450 | |
451 | /** |
452 | * Loads a listener with the SimpleReporter |
453 | * interface. |
454 | * @param SimpleReporter $listener Listener of tag events. |
455 | * @access public |
456 | */ |
457 | function SimpleTestXmlParser(&$listener) { |
458 | $this->_listener = &$listener; |
459 | $this->_expat = &$this->_createParser(); |
460 | $this->_tag_stack = array(); |
461 | $this->_in_content_tag = false; |
462 | $this->_content = ''; |
463 | $this->_attributes = array(); |
464 | } |
465 | |
466 | /** |
467 | * Parses a block of XML sending the results to |
468 | * the listener. |
469 | * @param string $chunk Block of text to read. |
470 | * @return boolean True if valid XML. |
471 | * @access public |
472 | */ |
473 | function parse($chunk) { |
474 | if (! xml_parse($this->_expat, $chunk)) { |
475 | trigger_error('XML parse error with ' . |
476 | xml_error_string(xml_get_error_code($this->_expat))); |
477 | return false; |
478 | } |
479 | return true; |
480 | } |
481 | |
482 | /** |
483 | * Sets up expat as the XML parser. |
484 | * @return resource Expat handle. |
485 | * @access protected |
486 | */ |
487 | function &_createParser() { |
488 | $expat = xml_parser_create(); |
489 | xml_set_object($expat, $this); |
490 | xml_set_element_handler($expat, '_startElement', '_endElement'); |
491 | xml_set_character_data_handler($expat, '_addContent'); |
492 | xml_set_default_handler($expat, '_default'); |
493 | return $expat; |
494 | } |
495 | |
496 | /** |
497 | * Opens a new test nesting level. |
498 | * @return NestedXmlTag The group, case or method tag |
499 | * to start. |
500 | * @access private |
501 | */ |
502 | function _pushNestingTag($nested) { |
503 | array_unshift($this->_tag_stack, $nested); |
504 | } |
505 | |
506 | /** |
507 | * Accessor for current test structure tag. |
508 | * @return NestedXmlTag The group, case or method tag |
509 | * being parsed. |
510 | * @access private |
511 | */ |
512 | function &_getCurrentNestingTag() { |
513 | return $this->_tag_stack[0]; |
514 | } |
515 | |
516 | /** |
517 | * Ends a nesting tag. |
518 | * @return NestedXmlTag The group, case or method tag |
519 | * just finished. |
520 | * @access private |
521 | */ |
522 | function _popNestingTag() { |
523 | return array_shift($this->_tag_stack); |
524 | } |
525 | |
526 | /** |
527 | * Test if tag is a leaf node with only text content. |
528 | * @param string $tag XML tag name. |
529 | * @return @boolean True if leaf, false if nesting. |
530 | * @private |
531 | */ |
532 | function _isLeaf($tag) { |
533 | return in_array( |
534 | $tag, |
535 | array('NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); |
536 | } |
537 | |
538 | /** |
539 | * Handler for start of event element. |
540 | * @param resource $expat Parser handle. |
541 | * @param string $tag Element name. |
542 | * @param hash $attributes Name value pairs. |
543 | * Attributes without content |
544 | * are marked as true. |
545 | * @access protected |
546 | */ |
547 | function _startElement($expat, $tag, $attributes) { |
548 | $this->_attributes = $attributes; |
549 | if ($tag == 'GROUP') { |
550 | $this->_pushNestingTag(new NestingGroupTag($attributes)); |
551 | } elseif ($tag == 'CASE') { |
552 | $this->_pushNestingTag(new NestingCaseTag($attributes)); |
553 | } elseif ($tag == 'TEST') { |
554 | $this->_pushNestingTag(new NestingMethodTag($attributes)); |
555 | } elseif ($this->_isLeaf($tag)) { |
556 | $this->_in_content_tag = true; |
557 | $this->_content = ''; |
558 | } |
559 | } |
560 | |
561 | /** |
562 | * End of element event. |
563 | * @param resource $expat Parser handle. |
564 | * @param string $tag Element name. |
565 | * @access protected |
566 | */ |
567 | function _endElement($expat, $tag) { |
568 | $this->_in_content_tag = false; |
569 | if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) { |
570 | $nesting_tag = $this->_popNestingTag(); |
571 | $nesting_tag->paintEnd($this->_listener); |
572 | } elseif ($tag == 'NAME') { |
573 | $nesting_tag = &$this->_getCurrentNestingTag(); |
574 | $nesting_tag->setName($this->_content); |
575 | $nesting_tag->paintStart($this->_listener); |
576 | } elseif ($tag == 'PASS') { |
577 | $this->_listener->paintPass($this->_content); |
578 | } elseif ($tag == 'FAIL') { |
579 | $this->_listener->paintFail($this->_content); |
580 | } elseif ($tag == 'EXCEPTION') { |
581 | $this->_listener->paintException($this->_content); |
582 | } elseif ($tag == 'SIGNAL') { |
583 | $this->_listener->paintSignal( |
584 | $this->_attributes['TYPE'], |
585 | unserialize($this->_content)); |
586 | } elseif ($tag == 'MESSAGE') { |
587 | $this->_listener->paintMessage($this->_content); |
588 | } elseif ($tag == 'FORMATTED') { |
589 | $this->_listener->paintFormattedMessage($this->_content); |
590 | } |
591 | } |
592 | |
593 | /** |
594 | * Content between start and end elements. |
595 | * @param resource $expat Parser handle. |
596 | * @param string $text Usually output messages. |
597 | * @access protected |
598 | */ |
599 | function _addContent($expat, $text) { |
600 | if ($this->_in_content_tag) { |
601 | $this->_content .= $text; |
602 | } |
603 | return true; |
604 | } |
605 | |
606 | /** |
607 | * XML and Doctype handler. Discards all such content. |
608 | * @param resource $expat Parser handle. |
609 | * @param string $default Text of default content. |
610 | * @access protected |
611 | */ |
612 | function _default($expat, $default) { |
613 | } |
614 | } |
615 | ?> |