test_case.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658
  1. <?php
  2. /**
  3. * Base include file for SimpleTest
  4. * @package SimpleTest
  5. * @subpackage UnitTester
  6. * @version $Id$
  7. */
  8. /**#@+
  9. * Includes SimpleTest files and defined the root constant
  10. * for dependent libraries.
  11. */
  12. require_once(dirname(__FILE__) . '/invoker.php');
  13. require_once(dirname(__FILE__) . '/errors.php');
  14. require_once(dirname(__FILE__) . '/compatibility.php');
  15. require_once(dirname(__FILE__) . '/scorer.php');
  16. require_once(dirname(__FILE__) . '/expectation.php');
  17. require_once(dirname(__FILE__) . '/dumper.php');
  18. require_once(dirname(__FILE__) . '/simpletest.php');
  19. require_once(dirname(__FILE__) . '/exceptions.php');
  20. require_once(dirname(__FILE__) . '/reflection_php5.php');
  21. /**#@-*/
  22. if (! defined('SIMPLE_TEST')) {
  23. /**
  24. * @ignore
  25. */
  26. define('SIMPLE_TEST', dirname(__FILE__) . DIRECTORY_SEPARATOR);
  27. }
  28. /**
  29. * Basic test case. This is the smallest unit of a test
  30. * suite. It searches for
  31. * all methods that start with the the string "test" and
  32. * runs them. Working test cases extend this class.
  33. * @package SimpleTest
  34. * @subpackage UnitTester
  35. */
  36. class SimpleTestCase {
  37. private $label = false;
  38. protected $reporter;
  39. private $observers;
  40. private $should_skip = false;
  41. /**
  42. * Sets up the test with no display.
  43. * @param string $label If no test name is given then
  44. * the class name is used.
  45. * @access public
  46. */
  47. function __construct($label = false) {
  48. if ($label) {
  49. $this->label = $label;
  50. }
  51. }
  52. /**
  53. * Accessor for the test name for subclasses.
  54. * @return string Name of the test.
  55. * @access public
  56. */
  57. function getLabel() {
  58. return $this->label ? $this->label : get_class($this);
  59. }
  60. /**
  61. * This is a placeholder for skipping tests. In this
  62. * method you place skipIf() and skipUnless() calls to
  63. * set the skipping state.
  64. * @access public
  65. */
  66. function skip() {
  67. }
  68. /**
  69. * Will issue a message to the reporter and tell the test
  70. * case to skip if the incoming flag is true.
  71. * @param string $should_skip Condition causing the tests to be skipped.
  72. * @param string $message Text of skip condition.
  73. * @access public
  74. */
  75. function skipIf($should_skip, $message = '%s') {
  76. if ($should_skip && ! $this->should_skip) {
  77. $this->should_skip = true;
  78. $message = sprintf($message, 'Skipping [' . get_class($this) . ']');
  79. $this->reporter->paintSkip($message . $this->getAssertionLine());
  80. }
  81. }
  82. /**
  83. * Accessor for the private variable $_shoud_skip
  84. * @access public
  85. */
  86. function shouldSkip() {
  87. return $this->should_skip;
  88. }
  89. /**
  90. * Will issue a message to the reporter and tell the test
  91. * case to skip if the incoming flag is false.
  92. * @param string $shouldnt_skip Condition causing the tests to be run.
  93. * @param string $message Text of skip condition.
  94. * @access public
  95. */
  96. function skipUnless($shouldnt_skip, $message = false) {
  97. $this->skipIf(! $shouldnt_skip, $message);
  98. }
  99. /**
  100. * Used to invoke the single tests.
  101. * @return SimpleInvoker Individual test runner.
  102. * @access public
  103. */
  104. function createInvoker() {
  105. return new SimpleErrorTrappingInvoker(
  106. new SimpleExceptionTrappingInvoker(new SimpleInvoker($this)));
  107. }
  108. /**
  109. * Uses reflection to run every method within itself
  110. * starting with the string "test" unless a method
  111. * is specified.
  112. * @param SimpleReporter $reporter Current test reporter.
  113. * @return boolean True if all tests passed.
  114. * @access public
  115. */
  116. function run($reporter) {
  117. $context = SimpleTest::getContext();
  118. $context->setTest($this);
  119. $context->setReporter($reporter);
  120. $this->reporter = $reporter;
  121. $started = false;
  122. foreach ($this->getTests() as $method) {
  123. if ($reporter->shouldInvoke($this->getLabel(), $method)) {
  124. $this->skip();
  125. if ($this->should_skip) {
  126. break;
  127. }
  128. if (! $started) {
  129. $reporter->paintCaseStart($this->getLabel());
  130. $started = true;
  131. }
  132. $invoker = $this->reporter->createInvoker($this->createInvoker());
  133. $invoker->before($method);
  134. $invoker->invoke($method);
  135. $invoker->after($method);
  136. }
  137. }
  138. if ($started) {
  139. $reporter->paintCaseEnd($this->getLabel());
  140. }
  141. unset($this->reporter);
  142. $context->setTest(null);
  143. return $reporter->getStatus();
  144. }
  145. /**
  146. * Gets a list of test names. Normally that will
  147. * be all internal methods that start with the
  148. * name "test". This method should be overridden
  149. * if you want a different rule.
  150. * @return array List of test names.
  151. * @access public
  152. */
  153. function getTests() {
  154. $methods = array();
  155. foreach (get_class_methods(get_class($this)) as $method) {
  156. if ($this->isTest($method)) {
  157. $methods[] = $method;
  158. }
  159. }
  160. return $methods;
  161. }
  162. /**
  163. * Tests to see if the method is a test that should
  164. * be run. Currently any method that starts with 'test'
  165. * is a candidate unless it is the constructor.
  166. * @param string $method Method name to try.
  167. * @return boolean True if test method.
  168. * @access protected
  169. */
  170. protected function isTest($method) {
  171. if (strtolower(substr($method, 0, 4)) == 'test') {
  172. return ! SimpleTestCompatibility::isA($this, strtolower($method));
  173. }
  174. return false;
  175. }
  176. /**
  177. * Announces the start of the test.
  178. * @param string $method Test method just started.
  179. * @access public
  180. */
  181. function before($method) {
  182. $this->reporter->paintMethodStart($method);
  183. $this->observers = array();
  184. }
  185. /**
  186. * Sets up unit test wide variables at the start
  187. * of each test method. To be overridden in
  188. * actual user test cases.
  189. * @access public
  190. */
  191. function setUp() {
  192. }
  193. /**
  194. * Clears the data set in the setUp() method call.
  195. * To be overridden by the user in actual user test cases.
  196. * @access public
  197. */
  198. function tearDown() {
  199. }
  200. /**
  201. * Announces the end of the test. Includes private clean up.
  202. * @param string $method Test method just finished.
  203. * @access public
  204. */
  205. function after($method) {
  206. for ($i = 0; $i < count($this->observers); $i++) {
  207. $this->observers[$i]->atTestEnd($method, $this);
  208. }
  209. $this->reporter->paintMethodEnd($method);
  210. }
  211. /**
  212. * Sets up an observer for the test end.
  213. * @param object $observer Must have atTestEnd()
  214. * method.
  215. * @access public
  216. */
  217. function tell($observer) {
  218. $this->observers[] = &$observer;
  219. }
  220. /**
  221. * @deprecated
  222. */
  223. function pass($message = "Pass") {
  224. if (! isset($this->reporter)) {
  225. trigger_error('Can only make assertions within test methods');
  226. }
  227. $this->reporter->paintPass(
  228. $message . $this->getAssertionLine());
  229. return true;
  230. }
  231. /**
  232. * Sends a fail event with a message.
  233. * @param string $message Message to send.
  234. * @access public
  235. */
  236. function fail($message = "Fail") {
  237. if (! isset($this->reporter)) {
  238. trigger_error('Can only make assertions within test methods');
  239. }
  240. $this->reporter->paintFail(
  241. $message . $this->getAssertionLine());
  242. return false;
  243. }
  244. /**
  245. * Formats a PHP error and dispatches it to the
  246. * reporter.
  247. * @param integer $severity PHP error code.
  248. * @param string $message Text of error.
  249. * @param string $file File error occoured in.
  250. * @param integer $line Line number of error.
  251. * @access public
  252. */
  253. function error($severity, $message, $file, $line) {
  254. if (! isset($this->reporter)) {
  255. trigger_error('Can only make assertions within test methods');
  256. }
  257. $this->reporter->paintError(
  258. "Unexpected PHP error [$message] severity [$severity] in [$file line $line]");
  259. }
  260. /**
  261. * Formats an exception and dispatches it to the
  262. * reporter.
  263. * @param Exception $exception Object thrown.
  264. * @access public
  265. */
  266. function exception($exception) {
  267. $this->reporter->paintException($exception);
  268. }
  269. /**
  270. * For user defined expansion of the available messages.
  271. * @param string $type Tag for sorting the signals.
  272. * @param mixed $payload Extra user specific information.
  273. */
  274. function signal($type, $payload) {
  275. if (! isset($this->reporter)) {
  276. trigger_error('Can only make assertions within test methods');
  277. }
  278. $this->reporter->paintSignal($type, $payload);
  279. }
  280. /**
  281. * Runs an expectation directly, for extending the
  282. * tests with new expectation classes.
  283. * @param SimpleExpectation $expectation Expectation subclass.
  284. * @param mixed $compare Value to compare.
  285. * @param string $message Message to display.
  286. * @return boolean True on pass
  287. * @access public
  288. */
  289. function assert($expectation, $compare, $message = '%s') {
  290. if ($expectation->test($compare)) {
  291. return $this->pass(sprintf(
  292. $message,
  293. $expectation->overlayMessage($compare, $this->reporter->getDumper())));
  294. } else {
  295. return $this->fail(sprintf(
  296. $message,
  297. $expectation->overlayMessage($compare, $this->reporter->getDumper())));
  298. }
  299. }
  300. /**
  301. * Uses a stack trace to find the line of an assertion.
  302. * @return string Line number of first assert*
  303. * method embedded in format string.
  304. * @access public
  305. */
  306. function getAssertionLine() {
  307. $trace = new SimpleStackTrace(array('assert', 'expect', 'pass', 'fail', 'skip'));
  308. return $trace->traceMethod();
  309. }
  310. /**
  311. * Sends a formatted dump of a variable to the
  312. * test suite for those emergency debugging
  313. * situations.
  314. * @param mixed $variable Variable to display.
  315. * @param string $message Message to display.
  316. * @return mixed The original variable.
  317. * @access public
  318. */
  319. function dump($variable, $message = false) {
  320. $dumper = $this->reporter->getDumper();
  321. $formatted = $dumper->dump($variable);
  322. if ($message) {
  323. $formatted = $message . "\n" . $formatted;
  324. }
  325. $this->reporter->paintFormattedMessage($formatted);
  326. return $variable;
  327. }
  328. /**
  329. * Accessor for the number of subtests including myelf.
  330. * @return integer Number of test cases.
  331. * @access public
  332. */
  333. function getSize() {
  334. return 1;
  335. }
  336. }
  337. /**
  338. * Helps to extract test cases automatically from a file.
  339. * @package SimpleTest
  340. * @subpackage UnitTester
  341. */
  342. class SimpleFileLoader {
  343. /**
  344. * Builds a test suite from a library of test cases.
  345. * The new suite is composed into this one.
  346. * @param string $test_file File name of library with
  347. * test case classes.
  348. * @return TestSuite The new test suite.
  349. * @access public
  350. */
  351. function load($test_file) {
  352. $existing_classes = get_declared_classes();
  353. $existing_globals = get_defined_vars();
  354. include_once($test_file);
  355. $new_globals = get_defined_vars();
  356. $this->makeFileVariablesGlobal($existing_globals, $new_globals);
  357. $new_classes = array_diff(get_declared_classes(), $existing_classes);
  358. if (empty($new_classes)) {
  359. $new_classes = $this->scrapeClassesFromFile($test_file);
  360. }
  361. $classes = $this->selectRunnableTests($new_classes);
  362. return $this->createSuiteFromClasses($test_file, $classes);
  363. }
  364. /**
  365. * Imports new variables into the global namespace.
  366. * @param hash $existing Variables before the file was loaded.
  367. * @param hash $new Variables after the file was loaded.
  368. * @access private
  369. */
  370. protected function makeFileVariablesGlobal($existing, $new) {
  371. $globals = array_diff(array_keys($new), array_keys($existing));
  372. foreach ($globals as $global) {
  373. $GLOBALS[$global] = $new[$global];
  374. }
  375. }
  376. /**
  377. * Lookup classnames from file contents, in case the
  378. * file may have been included before.
  379. * Note: This is probably too clever by half. Figuring this
  380. * out after a failed test case is going to be tricky for us,
  381. * never mind the user. A test case should not be included
  382. * twice anyway.
  383. * @param string $test_file File name with classes.
  384. * @access private
  385. */
  386. protected function scrapeClassesFromFile($test_file) {
  387. preg_match_all('~^\s*class\s+(\w+)(\s+(extends|implements)\s+\w+)*\s*\{~mi',
  388. file_get_contents($test_file),
  389. $matches );
  390. return $matches[1];
  391. }
  392. /**
  393. * Calculates the incoming test cases. Skips abstract
  394. * and ignored classes.
  395. * @param array $candidates Candidate classes.
  396. * @return array New classes which are test
  397. * cases that shouldn't be ignored.
  398. * @access public
  399. */
  400. function selectRunnableTests($candidates) {
  401. $classes = array();
  402. foreach ($candidates as $class) {
  403. if (TestSuite::getBaseTestCase($class)) {
  404. $reflection = new SimpleReflection($class);
  405. if ($reflection->isAbstract()) {
  406. SimpleTest::ignore($class);
  407. } else {
  408. $classes[] = $class;
  409. }
  410. }
  411. }
  412. return $classes;
  413. }
  414. /**
  415. * Builds a test suite from a class list.
  416. * @param string $title Title of new group.
  417. * @param array $classes Test classes.
  418. * @return TestSuite Group loaded with the new
  419. * test cases.
  420. * @access public
  421. */
  422. function createSuiteFromClasses($title, $classes) {
  423. if (count($classes) == 0) {
  424. $suite = new BadTestSuite($title, "No runnable test cases in [$title]");
  425. return $suite;
  426. }
  427. SimpleTest::ignoreParentsIfIgnored($classes);
  428. $suite = new TestSuite($title);
  429. foreach ($classes as $class) {
  430. if (! SimpleTest::isIgnored($class)) {
  431. $suite->add($class);
  432. }
  433. }
  434. return $suite;
  435. }
  436. }
  437. /**
  438. * This is a composite test class for combining
  439. * test cases and other RunnableTest classes into
  440. * a group test.
  441. * @package SimpleTest
  442. * @subpackage UnitTester
  443. */
  444. class TestSuite {
  445. private $label;
  446. private $test_cases;
  447. /**
  448. * Sets the name of the test suite.
  449. * @param string $label Name sent at the start and end
  450. * of the test.
  451. * @access public
  452. */
  453. function TestSuite($label = false) {
  454. $this->label = $label;
  455. $this->test_cases = array();
  456. }
  457. /**
  458. * Accessor for the test name for subclasses. If the suite
  459. * wraps a single test case the label defaults to the name of that test.
  460. * @return string Name of the test.
  461. * @access public
  462. */
  463. function getLabel() {
  464. if (! $this->label) {
  465. return ($this->getSize() == 1) ?
  466. @get_class($this->test_cases[0]) : get_class($this);
  467. } else {
  468. return $this->label;
  469. }
  470. }
  471. /**
  472. * Adds a test into the suite by instance or class. The class will
  473. * be instantiated if it's a test suite.
  474. * @param SimpleTestCase $test_case Suite or individual test
  475. * case implementing the
  476. * runnable test interface.
  477. * @access public
  478. */
  479. function add($test_case) {
  480. if (! is_string($test_case)) {
  481. $this->test_cases[] = $test_case;
  482. } elseif (TestSuite::getBaseTestCase($test_case) == 'testsuite') {
  483. $this->test_cases[] = new $test_case();
  484. } else {
  485. $this->test_cases[] = $test_case;
  486. }
  487. }
  488. /**
  489. * Builds a test suite from a library of test cases.
  490. * The new suite is composed into this one.
  491. * @param string $test_file File name of library with
  492. * test case classes.
  493. * @access public
  494. */
  495. function addFile($test_file) {
  496. $extractor = new SimpleFileLoader();
  497. $this->add($extractor->load($test_file));
  498. }
  499. /**
  500. * Delegates to a visiting collector to add test
  501. * files.
  502. * @param string $path Path to scan from.
  503. * @param SimpleCollector $collector Directory scanner.
  504. * @access public
  505. */
  506. function collect($path, $collector) {
  507. $collector->collect($this, $path);
  508. }
  509. /**
  510. * Invokes run() on all of the held test cases, instantiating
  511. * them if necessary.
  512. * @param SimpleReporter $reporter Current test reporter.
  513. * @access public
  514. */
  515. function run($reporter) {
  516. $reporter->paintGroupStart($this->getLabel(), $this->getSize());
  517. for ($i = 0, $count = count($this->test_cases); $i < $count; $i++) {
  518. if (is_string($this->test_cases[$i])) {
  519. $class = $this->test_cases[$i];
  520. $test = new $class();
  521. $test->run($reporter);
  522. unset($test);
  523. } else {
  524. $this->test_cases[$i]->run($reporter);
  525. }
  526. }
  527. $reporter->paintGroupEnd($this->getLabel());
  528. return $reporter->getStatus();
  529. }
  530. /**
  531. * Number of contained test cases.
  532. * @return integer Total count of cases in the group.
  533. * @access public
  534. */
  535. function getSize() {
  536. $count = 0;
  537. foreach ($this->test_cases as $case) {
  538. if (is_string($case)) {
  539. if (! SimpleTest::isIgnored($case)) {
  540. $count++;
  541. }
  542. } else {
  543. $count += $case->getSize();
  544. }
  545. }
  546. return $count;
  547. }
  548. /**
  549. * Test to see if a class is derived from the
  550. * SimpleTestCase class.
  551. * @param string $class Class name.
  552. * @access public
  553. */
  554. static function getBaseTestCase($class) {
  555. while ($class = get_parent_class($class)) {
  556. $class = strtolower($class);
  557. if ($class == 'simpletestcase' || $class == 'testsuite') {
  558. return $class;
  559. }
  560. }
  561. return false;
  562. }
  563. }
  564. /**
  565. * This is a failing group test for when a test suite hasn't
  566. * loaded properly.
  567. * @package SimpleTest
  568. * @subpackage UnitTester
  569. */
  570. class BadTestSuite {
  571. private $label;
  572. private $error;
  573. /**
  574. * Sets the name of the test suite and error message.
  575. * @param string $label Name sent at the start and end
  576. * of the test.
  577. * @access public
  578. */
  579. function BadTestSuite($label, $error) {
  580. $this->label = $label;
  581. $this->error = $error;
  582. }
  583. /**
  584. * Accessor for the test name for subclasses.
  585. * @return string Name of the test.
  586. * @access public
  587. */
  588. function getLabel() {
  589. return $this->label;
  590. }
  591. /**
  592. * Sends a single error to the reporter.
  593. * @param SimpleReporter $reporter Current test reporter.
  594. * @access public
  595. */
  596. function run($reporter) {
  597. $reporter->paintGroupStart($this->getLabel(), $this->getSize());
  598. $reporter->paintFail('Bad TestSuite [' . $this->getLabel() .
  599. '] with error [' . $this->error . ']');
  600. $reporter->paintGroupEnd($this->getLabel());
  601. return $reporter->getStatus();
  602. }
  603. /**
  604. * Number of contained test cases. Always zero.
  605. * @return integer Total count of cases in the group.
  606. * @access public
  607. */
  608. function getSize() {
  609. return 0;
  610. }
  611. }
  612. ?>