AbstractTestRunner.php 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. <?php
  2. require_once 'Sweety/Runner.php';
  3. require_once 'Sweety/TestLocator.php';
  4. require_once 'Sweety/Reporter.php';
  5. /**
  6. * Base functionality for the Sweety_Runner.
  7. * @package Sweety
  8. * @author Chris Corbyn
  9. */
  10. abstract class Sweety_Runner_AbstractTestRunner implements Sweety_Runner
  11. {
  12. /**
  13. * The reporter used for showing progress.
  14. * @var Sweety_Reporter
  15. * @access private
  16. */
  17. private $_reporter;
  18. /**
  19. * TestLocator strategies.
  20. * @var Sweety_TestLocator[]
  21. * @access private
  22. */
  23. private $_testLocators = array();
  24. /**
  25. * Regular expression for classes which should be ignored.
  26. * @var string
  27. * @access private
  28. */
  29. private $_ignoredClassRegex = '/^$/D';
  30. /**
  31. * Set the reporter used for showing results.
  32. * @param Sweety_Reporter $reporter
  33. */
  34. public function setReporter(Sweety_Reporter $reporter)
  35. {
  36. $this->_reporter = $reporter;
  37. }
  38. /**
  39. * Get the reporter used for showing results.
  40. * @return Sweety_Reporter
  41. */
  42. public function getReporter()
  43. {
  44. return $this->_reporter;
  45. }
  46. /**
  47. * Register a test locator instance.
  48. * @param Sweety_TestLocator $locator
  49. */
  50. public function registerTestLocator(Sweety_TestLocator $locator)
  51. {
  52. $this->_testLocators[] = $locator;
  53. }
  54. /**
  55. * Set the regular expression used to filter out certain class names.
  56. * @param string $ignoredClassRegex
  57. */
  58. public function setIgnoredClassRegex($ignoredClassRegex)
  59. {
  60. $this->_ignoredClassRegex = $ignoredClassRegex;
  61. }
  62. /**
  63. * Get the filtering regular expression for ignoring certain classes.
  64. * @return string
  65. */
  66. public function getIgnoredClassRegex()
  67. {
  68. return $this->_ignoredClassRegex;
  69. }
  70. /**
  71. * Run a single test case with the given name, using the provided output format.
  72. * @param string $testName
  73. * @param string $format (xml, text or html)
  74. * @return int
  75. */
  76. public function runTestCase($testName, $format = Sweety_Runner::REPORT_TEXT)
  77. {
  78. foreach ($this->_testLocators as $locator)
  79. {
  80. if ($locator->includeTest($testName))
  81. {
  82. break;
  83. }
  84. }
  85. $testClass = new ReflectionClass($testName);
  86. if ($testClass->getConstructor())
  87. {
  88. //We don't want test output to be cached
  89. if (!SimpleReporter::inCli())
  90. {
  91. header("Cache-Control: no-cache, must-revalidate");
  92. header("Pragma: no-cache");
  93. header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
  94. }
  95. switch ($format)
  96. {
  97. case Sweety_Runner::REPORT_HTML:
  98. $reporter = new HtmlReporter();
  99. break;
  100. case Sweety_Runner::REPORT_XML:
  101. if (!SimpleReporter::inCli())
  102. {
  103. header("Content-Type: text/xml"); //Sigh! SimpleTest (skip() issues).
  104. }
  105. $reporter = new XmlReporter();
  106. break;
  107. case Sweety_Runner::REPORT_TEXT:
  108. default:
  109. $reporter = new TextReporter();
  110. break;
  111. }
  112. $test = $testClass->newInstance();
  113. return $test->run($reporter) ? 0 : 1;
  114. }
  115. return 1;
  116. }
  117. /**
  118. * Use strategies to find tests which are runnable.
  119. * @param string[] $dirs
  120. * @return string[]
  121. */
  122. protected function findTests($dirs = array())
  123. {
  124. $tests = array();
  125. foreach ($this->_testLocators as $locator)
  126. {
  127. $tests += $locator->getTests($dirs);
  128. }
  129. return $tests;
  130. }
  131. /**
  132. * Parse an XML response from a test case an report to the reporter.
  133. * @param string $xml
  134. * @param string $testCase
  135. */
  136. protected function parseXml($xml, $testCase)
  137. {
  138. $reporter = $this->_reporter->getReporterFor($testCase);
  139. if (!$reporter->isStarted())
  140. {
  141. $reporter->start();
  142. }
  143. $xml = str_replace("\0", '?', trim($xml));
  144. $xml = preg_replace('/[^\x01-\x7F]/e', 'sprintf("&#%d;", ord("$0"));', $xml); //Do something better?
  145. if (!empty($xml))
  146. {
  147. $document = @simplexml_load_string($xml);
  148. if ($document)
  149. {
  150. $this->_parseDocument($document, $testCase, $reporter);
  151. $reporter->finish();
  152. return;
  153. }
  154. }
  155. $reporter->reportException(
  156. 'Invalid XML response: ' .
  157. trim(strip_tags(
  158. preg_replace('/^\s*<\?xml.+<\/(?:name|pass|fail|exception)>/s', '', $xml)
  159. )),
  160. $testCase
  161. );
  162. }
  163. /**
  164. * Parse formatted test output.
  165. * @param SimpleXMLElement The node containing the output
  166. * @param string $path to this test method
  167. * @access private
  168. */
  169. private function _parseFormatted(SimpleXMLElement $formatted, $path = '',
  170. Sweety_Reporter $reporter)
  171. {
  172. $reporter->reportOutput((string)$formatted, $path);
  173. }
  174. /**
  175. * Parse test output.
  176. * @param SimpleXMLElement The node containing the output
  177. * @param string $path to this test method
  178. * @access private
  179. */
  180. private function _parseMessage(SimpleXMLElement $message, $path = '',
  181. Sweety_Reporter $reporter)
  182. {
  183. $reporter->reportOutput((string)$message, $path);
  184. }
  185. /**
  186. * Parse a test failure.
  187. * @param SimpleXMLElement The node containing the fail
  188. * @param string $path to this test method
  189. * @access private
  190. */
  191. private function _parseFailure(SimpleXMLElement $failure, $path = '',
  192. Sweety_Reporter $reporter)
  193. {
  194. $reporter->reportFail((string)$failure, $path);
  195. }
  196. /**
  197. * Parse an exception.
  198. * @param SimpleXMLElement The node containing the exception
  199. * @param string $path to this test method
  200. * @access private
  201. */
  202. private function _parseException(SimpleXMLElement $exception, $path = '',
  203. Sweety_Reporter $reporter)
  204. {
  205. $reporter->reportException((string)$exception, $path);
  206. }
  207. /**
  208. * Parse a pass.
  209. * @param SimpleXMLElement The node containing this pass.
  210. * @param string $path to this test method
  211. * @access private
  212. */
  213. private function _parsePass(SimpleXMLElement $pass, $path = '',
  214. Sweety_Reporter $reporter)
  215. {
  216. $reporter->reportPass((string)$pass, $path);
  217. }
  218. /**
  219. * Parse a single test case.
  220. * @param SimpleXMLElement The node containing the test case
  221. * @param string $path to this test case
  222. * @access private
  223. */
  224. private function _parseTestCase(SimpleXMLElement $testCase, $path = '',
  225. Sweety_Reporter $reporter)
  226. {
  227. foreach ($testCase->xpath('./test') as $testMethod)
  228. {
  229. $testMethodName = (string) $this->_firstNodeValue($testMethod->xpath('./name'));
  230. foreach ($testMethod->xpath('./formatted') as $formatted)
  231. {
  232. $this->_parseFormatted(
  233. $formatted, $path . ' -> ' . $testMethodName, $reporter);
  234. }
  235. foreach ($testMethod->xpath('./message') as $message)
  236. {
  237. $this->_parseMessage(
  238. $message, $path . ' -> ' . $testMethodName, $reporter);
  239. }
  240. foreach ($testMethod->xpath('./fail') as $failure)
  241. {
  242. $this->_parseFailure(
  243. $failure, $path . ' -> ' . $testMethodName, $reporter);
  244. }
  245. foreach ($testMethod->xpath('./exception') as $exception)
  246. {
  247. $this->_parseException(
  248. $exception, $path . ' -> ' . $testMethodName, $reporter);
  249. }
  250. foreach ($testMethod->xpath('./pass') as $pass)
  251. {
  252. $this->_parsePass($pass, $path . ' -> ' . $testMethodName, $reporter);
  253. }
  254. $stdout = trim((string) $testMethod);
  255. if ($stdout)
  256. {
  257. $reporter->reportOutput($stdout, $path . ' -> ' . $testMethodName);
  258. }
  259. }
  260. }
  261. /**
  262. * Parse the results of all tests.
  263. * @param SimpleXMLElement The node containing the tests
  264. * @param string $path to the tests
  265. * @access private
  266. */
  267. private function _parseResults(SimpleXMLElement $document, $path = '',
  268. Sweety_Reporter $reporter)
  269. {
  270. $groups = $document->xpath('./group');
  271. if (!empty($groups))
  272. {
  273. foreach ($groups as $group)
  274. {
  275. $groupName = (string) $this->_firstNodeValue($group->xpath('./name'));
  276. $this->_parseResults($group, $path . ' -> ' . $groupName, $reporter);
  277. }
  278. }
  279. else
  280. {
  281. foreach ($document->xpath('./case') as $testCase)
  282. {
  283. $this->_parseTestCase($testCase, $path, $reporter);
  284. }
  285. }
  286. }
  287. /**
  288. * Parse the entire SimpleTest XML document from a test case.
  289. * @param SimpleXMLElement $document to parse
  290. * @param string $path to the test
  291. * @access private
  292. */
  293. private function _parseDocument(SimpleXMLElement $document, $path = '',
  294. Sweety_Reporter $reporter)
  295. {
  296. if ($everything = $this->_firstNodeValue($document->xpath('/run')))
  297. {
  298. $this->_parseResults($everything, $path, $reporter);
  299. }
  300. elseif ($skip = $this->_firstNodeValue($document->xpath('/skip')))
  301. {
  302. $reporter->reportSkip((string) $skip, $path);
  303. }
  304. }
  305. protected function _sort($a, $b)
  306. {
  307. $apkg = preg_replace('/_[^_]+$/D', '', $a);
  308. $bpkg = preg_replace('/_[^_]+$/D', '', $b);
  309. if ($apkg == $bpkg)
  310. {
  311. if ($a == $b)
  312. {
  313. return 0;
  314. }
  315. else
  316. {
  317. return ($a > $b) ? 1 : -1;
  318. }
  319. }
  320. else
  321. {
  322. return ($apkg > $bpkg) ? 1 : -1;
  323. }
  324. }
  325. private function _firstNodeValue($nodeSet)
  326. {
  327. $first = array_shift($nodeSet);
  328. return $first;
  329. }
  330. }