/lib/simpletest/xml.php

https://bitbucket.org/valmy/openx · PHP · 648 lines · 291 code · 51 blank · 306 comment · 18 complexity · f0b5d1829c90dd274016265fe3d02bff MD5 · raw file

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