<?php /** * base include file for SimpleTest * @package SimpleTest * @subpackage UnitTester * @version $Id$ */ /**#@+ * include other SimpleTest class files */ require_once(dirname(__FILE__) . '/scorer.php'); /**#@-*/ /** * Creates the XML needed for remote communication * by SimpleTest. * @package SimpleTest * @subpackage UnitTester */ class XmlReporter extends SimpleReporter { protected $_indent; protected $_namespace; /** * Does nothing yet. * @access public */ function XmlReporter($namespace = false, $indent = ' ') { $this->SimpleReporter(); $this->_namespace = ($namespace ? $namespace . ':' : ''); $this->_indent = $indent; } /** * Calculates the pretty printing indent level * from the current level of nesting. * @param integer $offset Extra indenting level. * @return string Leading space. * @access protected */ function _getIndent($offset = 0) { return str_repeat( $this->_indent, count($this->getTestList()) + $offset); } /** * Converts character string to parsed XML * entities string. * @param string text Unparsed character data. * @return string Parsed character data. * @access public */ function toParsedXml($text) { return str_replace( array('&', '<', '>', '"', '\''), array('&', '<', '>', '"', '''), $text); } /** * Paints the start of a group test. * @param string $test_name Name of test that is starting. * @param integer $size Number of test cases starting. * @access public */ function paintGroupStart($test_name, $size) { parent::paintGroupStart($test_name, $size); print $this->_getIndent(); print "<" . $this->_namespace . "group size=\"$size\">\n"; print $this->_getIndent(1); print "<" . $this->_namespace . "name>" . $this->toParsedXml($test_name) . "</" . $this->_namespace . "name>\n"; } /** * Paints the end of a group test. * @param string $test_name Name of test that is ending. * @access public */ function paintGroupEnd($test_name) { print $this->_getIndent(); print "</" . $this->_namespace . "group>\n"; parent::paintGroupEnd($test_name); } /** * Paints the start of a test case. * @param string $test_name Name of test that is starting. * @access public */ function paintCaseStart($test_name) { parent::paintCaseStart($test_name); print $this->_getIndent(); print "<" . $this->_namespace . "case>\n"; print $this->_getIndent(1); print "<" . $this->_namespace . "name>" . $this->toParsedXml($test_name) . "</" . $this->_namespace . "name>\n"; } /** * Paints the end of a test case. * @param string $test_name Name of test that is ending. * @access public */ function paintCaseEnd($test_name) { print $this->_getIndent(); print "</" . $this->_namespace . "case>\n"; parent::paintCaseEnd($test_name); } /** * Paints the start of a test method. * @param string $test_name Name of test that is starting. * @access public */ function paintMethodStart($test_name) { parent::paintMethodStart($test_name); print $this->_getIndent(); print "<" . $this->_namespace . "test>\n"; print $this->_getIndent(1); print "<" . $this->_namespace . "name>" . $this->toParsedXml($test_name) . "</" . $this->_namespace . "name>\n"; } /** * Paints the end of a test method. * @param string $test_name Name of test that is ending. * @param integer $progress Number of test cases ending. * @access public */ function paintMethodEnd($test_name) { print $this->_getIndent(); print "</" . $this->_namespace . "test>\n"; parent::paintMethodEnd($test_name); } /** * Increments the pass count. * @param string $message Message is ignored. * @access public */ function paintPass($message) { parent::paintPass($message); print $this->_getIndent(1); print "<" . $this->_namespace . "pass>"; print $this->toParsedXml($message); print "</" . $this->_namespace . "pass>\n"; } /** * Increments the fail count. * @param string $message Message is ignored. * @access public */ function paintFail($message) { parent::paintFail($message); print $this->_getIndent(1); print "<" . $this->_namespace . "fail>"; print $this->toParsedXml($message); print "</" . $this->_namespace . "fail>\n"; } /** * Paints a PHP error or exception. * @param string $message Message is ignored. * @access public * @abstract */ function paintError($message) { parent::paintError($message); print $this->_getIndent(1); print "<" . $this->_namespace . "exception>"; print $this->toParsedXml($message); print "</" . $this->_namespace . "exception>\n"; } /** * Paints a simple supplementary message. * @param string $message Text to display. * @access public */ function paintMessage($message) { parent::paintMessage($message); print $this->_getIndent(1); print "<" . $this->_namespace . "message>"; print $this->toParsedXml($message); print "</" . $this->_namespace . "message>\n"; } /** * Paints a formatted ASCII message such as a * variable dump. * @param string $message Text to display. * @access public */ function paintFormattedMessage($message) { parent::paintFormattedMessage($message); print $this->_getIndent(1); print "<" . $this->_namespace . "formatted>"; print "<![CDATA[$message]]>"; print "</" . $this->_namespace . "formatted>\n"; } /** * Serialises the event object. * @param string $type Event type as text. * @param mixed $payload Message or object. * @access public */ function paintSignal($type, $payload) { parent::paintSignal($type, $payload); print $this->_getIndent(1); print "<" . $this->_namespace . "signal type=\"$type\">"; print "<![CDATA[" . serialize($payload) . "]]>"; print "</" . $this->_namespace . "signal>\n"; } /** * Paints the test document header. * @param string $test_name First test top level * to start. * @access public * @abstract */ function paintHeader($test_name) { if (! SimpleReporter::inCli()) { header('Content-type: text/xml'); } print "<?xml version=\"1.0\""; if ($this->_namespace) { print " xmlns:" . $this->_namespace . "=\"www.lastcraft.com/SimpleTest/Beta3/Report\""; } print "?>\n"; print "<" . $this->_namespace . "run>\n"; } /** * Paints the test document footer. * @param string $test_name The top level test. * @access public * @abstract */ function paintFooter($test_name) { print "</" . $this->_namespace . "run>\n"; } } /** * Accumulator for incoming tag. Holds the * incoming test structure information for * later dispatch to the reporter. * @package SimpleTest * @subpackage UnitTester */ class NestingXmlTag { protected $_name; protected $_attributes; /** * Sets the basic test information except * the name. * @param hash $attributes Name value pairs. * @access public */ function NestingXmlTag($attributes) { $this->_name = false; $this->_attributes = $attributes; } /** * Sets the test case/method name. * @param string $name Name of test. * @access public */ function setName($name) { $this->_name = $name; } /** * Accessor for name. * @return string Name of test. * @access public */ function getName() { return $this->_name; } /** * Accessor for attributes. * @return hash All attributes. * @access protected */ function _getAttributes() { return $this->_attributes; } } /** * Accumulator for incoming method tag. Holds the * incoming test structure information for * later dispatch to the reporter. * @package SimpleTest * @subpackage UnitTester */ class NestingMethodTag extends NestingXmlTag { /** * Sets the basic test information except * the name. * @param hash $attributes Name value pairs. * @access public */ function NestingMethodTag($attributes) { $this->NestingXmlTag($attributes); } /** * Signals the appropriate start event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintStart($listener) { $listener->paintMethodStart($this->getName()); } /** * Signals the appropriate end event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintEnd($listener) { $listener->paintMethodEnd($this->getName()); } } /** * Accumulator for incoming case tag. Holds the * incoming test structure information for * later dispatch to the reporter. * @package SimpleTest * @subpackage UnitTester */ class NestingCaseTag extends NestingXmlTag { /** * Sets the basic test information except * the name. * @param hash $attributes Name value pairs. * @access public */ function NestingCaseTag($attributes) { $this->NestingXmlTag($attributes); } /** * Signals the appropriate start event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintStart($listener) { $listener->paintCaseStart($this->getName()); } /** * Signals the appropriate end event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintEnd($listener) { $listener->paintCaseEnd($this->getName()); } } /** * Accumulator for incoming group tag. Holds the * incoming test structure information for * later dispatch to the reporter. * @package SimpleTest * @subpackage UnitTester */ class NestingGroupTag extends NestingXmlTag { /** * Sets the basic test information except * the name. * @param hash $attributes Name value pairs. * @access public */ function NestingGroupTag($attributes) { $this->NestingXmlTag($attributes); } /** * Signals the appropriate start event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintStart($listener) { $listener->paintGroupStart($this->getName(), $this->getSize()); } /** * Signals the appropriate end event on the * listener. * @param SimpleReporter $listener Target for events. * @access public */ function paintEnd($listener) { $listener->paintGroupEnd($this->getName()); } /** * The size in the attributes. * @return integer Value of size attribute or zero. * @access public */ function getSize() { $attributes = $this->_getAttributes(); if (isset($attributes['SIZE'])) { return (integer)$attributes['SIZE']; } return 0; } } /** * Parser for importing the output of the XmlReporter. * Dispatches that output to another reporter. * @package SimpleTest * @subpackage UnitTester */ class SimpleTestXmlParser { protected $_listener; protected $_expat; protected $_tag_stack; protected $_in_content_tag; protected $_content; protected $_attributes; /** * Loads a listener with the SimpleReporter * interface. * @param SimpleReporter $listener Listener of tag events. * @access public */ function SimpleTestXmlParser($listener) { $this->_listener = $listener; $this->_expat = $this->_createParser(); $this->_tag_stack = array(); $this->_in_content_tag = false; $this->_content = ''; $this->_attributes = array(); } /** * Parses a block of XML sending the results to * the listener. * @param string $chunk Block of text to read. * @return boolean True if valid XML. * @access public */ function parse($chunk) { if (! xml_parse($this->_expat, $chunk)) { trigger_error('XML parse error with ' . xml_error_string(xml_get_error_code($this->_expat))); return false; } return true; } /** * Sets up expat as the XML parser. * @return resource Expat handle. * @access protected */ function &_createParser() { $expat = xml_parser_create(); xml_set_object($expat, $this); xml_set_element_handler($expat, '_startElement', '_endElement'); xml_set_character_data_handler($expat, '_addContent'); xml_set_default_handler($expat, '_default'); return $expat; } /** * Opens a new test nesting level. * @return NestedXmlTag The group, case or method tag * to start. * @access private */ function _pushNestingTag($nested) { array_unshift($this->_tag_stack, $nested); } /** * Accessor for current test structure tag. * @return NestedXmlTag The group, case or method tag * being parsed. * @access private */ function &_getCurrentNestingTag() { return $this->_tag_stack[0]; } /** * Ends a nesting tag. * @return NestedXmlTag The group, case or method tag * just finished. * @access private */ function _popNestingTag() { return array_shift($this->_tag_stack); } /** * Test if tag is a leaf node with only text content. * @param string $tag XML tag name. * @return @boolean True if leaf, false if nesting. * @private */ function _isLeaf($tag) { return in_array($tag, array( 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'MESSAGE', 'FORMATTED', 'SIGNAL')); } /** * Handler for start of event element. * @param resource $expat Parser handle. * @param string $tag Element name. * @param hash $attributes Name value pairs. * Attributes without content * are marked as true. * @access protected */ function _startElement($expat, $tag, $attributes) { $this->_attributes = $attributes; if ($tag == 'GROUP') { $this->_pushNestingTag(new NestingGroupTag($attributes)); } elseif ($tag == 'CASE') { $this->_pushNestingTag(new NestingCaseTag($attributes)); } elseif ($tag == 'TEST') { $this->_pushNestingTag(new NestingMethodTag($attributes)); } elseif ($this->_isLeaf($tag)) { $this->_in_content_tag = true; $this->_content = ''; } } /** * End of element event. * @param resource $expat Parser handle. * @param string $tag Element name. * @access protected */ function _endElement($expat, $tag) { $this->_in_content_tag = false; if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) { $nesting_tag = $this->_popNestingTag(); $nesting_tag->paintEnd($this->_listener); } elseif ($tag == 'NAME') { $nesting_tag = $this->_getCurrentNestingTag(); $nesting_tag->setName($this->_content); $nesting_tag->paintStart($this->_listener); } elseif ($tag == 'PASS') { $this->_listener->paintPass($this->_content); } elseif ($tag == 'FAIL') { $this->_listener->paintFail($this->_content); } elseif ($tag == 'EXCEPTION') { $this->_listener->paintError($this->_content); } elseif ($tag == 'SIGNAL') { $this->_listener->paintSignal( $this->_attributes['TYPE'], unserialize($this->_content)); } elseif ($tag == 'MESSAGE') { $this->_listener->paintMessage($this->_content); } elseif ($tag == 'FORMATTED') { $this->_listener->paintFormattedMessage($this->_content); } } /** * Content between start and end elements. * @param resource $expat Parser handle. * @param string $text Usually output messages. * @access protected */ function _addContent($expat, $text) { if ($this->_in_content_tag) { $this->_content .= $text; } return true; } /** * XML and Doctype handler. Discards all such content. * @param resource $expat Parser handle. * @param string $default Text of default content. * @access protected */ function _default($expat, $default) { } } ?>