PageRenderTime 44ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/lithium-0.6/libraries/lithium/test/Unit.php

https://github.com/gwoo/framework-benchs
PHP | 805 lines | 476 code | 64 blank | 265 comment | 79 complexity | 1e809c3d3820d0f2fa655742442b85f9 MD5 | raw file
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2010, Union of RAD (http://union-of-rad.org)
  6. * @license http://opensource.org/licenses/bsd-license.php The BSD License
  7. */
  8. namespace lithium\test;
  9. use \Exception;
  10. use \lithium\util\String;
  11. use \lithium\util\Validator;
  12. use \lithium\analysis\Debugger;
  13. use \lithium\analysis\Inspector;
  14. use \RecursiveDirectoryIterator;
  15. use \RecursiveIteratorIterator;
  16. /**
  17. * This is the base class for all test cases. Test are performed using an assertion method. If the
  18. * assertion is correct, the test passes, otherwise it fails. Most assertions take an expected
  19. * result, a received result, and a message (to describe the failure) as parameters.
  20. *
  21. * Available assertions are (see `assert<assertion-name>` methods for details): Equal, False,
  22. * Identical, NoPattern, NotEqual, Null, Pattern, Tags, True.
  23. *
  24. * If an assertion is expected to produce an exception, the `expectException` method should be
  25. * called before it.
  26. *
  27. * Both _case_ (unit) and _integration_ tests extend this class. These two test types can loosely
  28. * be defined as follows:
  29. * - Case: These tests are used to check a small unit of functionality, such as if a method
  30. * returns an expected result for a known input, or whether an adapter can successfully open a
  31. * connection.
  32. * - Integration: These are tests for determining that different parts of the framework will work
  33. * together (integrate) as expected. For example, a model has CRUD functionality with its
  34. * underlying data source.
  35. *
  36. */
  37. class Unit extends \lithium\core\Object {
  38. /**
  39. * The Reference to a test reporter class.
  40. *
  41. * @var string
  42. */
  43. protected $_reporter = null;
  44. /**
  45. * The list of test results.
  46. *
  47. * @var string
  48. */
  49. protected $_results = array();
  50. /**
  51. * The list of expected exceptions.
  52. *
  53. * @var string
  54. */
  55. protected $_expected = array();
  56. /**
  57. * Runs the test methods in this test case, with the given options.
  58. *
  59. * @param array $options The options to use when running the test. Available options are:
  60. * - 'methods': An arbitrary array of method names to execute. If
  61. * unspecified, all methods starting with 'test' are run.
  62. * - 'reporter': A closure which gets called after each test result,
  63. * which may modify the results presented.
  64. * @return array
  65. */
  66. public function run($options = array()) {
  67. $defaults = array('methods' => array(), 'reporter' => null, 'handler' => null);
  68. $options += $defaults;
  69. $this->_results = array();
  70. $self = $this;
  71. $h = function($code, $message, $file, $line = 0, $context = array()) use ($self) {
  72. $trace = debug_backtrace();
  73. $trace = array_slice($trace, 1, count($trace));
  74. $self->invokeMethod('_handleException', array(
  75. compact('code', 'message', 'file', 'line', 'trace', 'context')
  76. ));
  77. };
  78. $options['handler'] = $options['handler'] ?: $h;
  79. $methods = $options['methods'] ?: $this->methods();
  80. $this->_reporter = $options['reporter'] ?: $this->_reporter;
  81. try {
  82. $this->skip();
  83. } catch (Exception $e) {
  84. if (preg_match('/^Skipped test/', $e->getMessage())) {
  85. $this->_result('skip', array());
  86. }
  87. $this->_handleException($e, __LINE__ - 5);
  88. return;
  89. }
  90. set_error_handler($options['handler']);
  91. foreach ($methods as $method) {
  92. $this->_runTestMethod($method, $options);
  93. }
  94. restore_error_handler();
  95. return $this->_results;
  96. }
  97. /**
  98. * Returns the class name that is the subject under test for this test case.
  99. *
  100. * @return string
  101. */
  102. public function subject() {
  103. return preg_replace('/Test$/', '', str_replace('tests\\cases\\', '', get_class($this)));
  104. }
  105. /**
  106. * Return test methods to run
  107. *
  108. * @return array
  109. */
  110. public function methods() {
  111. static $methods;
  112. return $methods ?: $methods = array_values(preg_grep('/^test/', get_class_methods($this)));
  113. }
  114. /**
  115. * Setup method run before every test method. override in subclasses
  116. *
  117. * @return void
  118. */
  119. public function setUp() {}
  120. /**
  121. * Teardown method run after every test method. override in subclasses
  122. *
  123. * @return void
  124. */
  125. public function tearDown() {}
  126. /**
  127. * Subclasses should use this method to set conditions that, if failed, terminate further
  128. * testing.
  129. *
  130. * For example:
  131. * {{{
  132. * public function skip() {
  133. * $this->_dbConfig = Connections::get('default', array('config' => true));
  134. * $hasDb = (isset($this->_dbConfig['adapter']) && $this->_dbConfig['adapter'] == 'MySql');
  135. * $message = 'Test database is either unavailable, or not using a MySQL adapter';
  136. * $this->skipIf(!$hasDb, $message);
  137. * }
  138. * }}}
  139. *
  140. * @return void
  141. */
  142. public function skip() {
  143. }
  144. /**
  145. * Skips test(s) if the condition is met.
  146. *
  147. * When used within a subclass' `skip` method, all tests are ignored if the condition is met,
  148. * otherwise processing continues as normal.
  149. * For other methods, only the remainder of the method is skipped, when the condition is met.
  150. *
  151. * @param boolean $condition
  152. * @param string $message Message to pass if the condition is met.
  153. * @return mixed
  154. */
  155. public function skipIf($condition, $message = 'Skipped test {:class}::{:function}()') {
  156. if (!$condition) {
  157. return;
  158. }
  159. $trace = Debugger::trace(array('start' => 2, 'depth' => 3, 'format' => 'array'));
  160. throw new Exception(String::insert($message, $trace));
  161. }
  162. /**
  163. * undocumented function
  164. *
  165. * @param string $expression
  166. * @param string $message
  167. * @param string $data
  168. * @return void
  169. */
  170. public function assert($expression, $message = '{:message}', $data = array()) {
  171. if (!is_string($message)) {
  172. $message = '{:message}';
  173. }
  174. $trace = Debugger::trace(array('start' => 1, 'format' => 'array'));
  175. $methods = $this->methods();
  176. $i = 1;
  177. while ($i < count($trace)) {
  178. if (in_array($trace[$i]['function'], $methods) && $trace[$i - 1]['object'] == $this) {
  179. break;
  180. }
  181. $i++;
  182. }
  183. if (strpos($message, "{:message}") !== false) {
  184. $data['message'] = $this->_message($data);
  185. }
  186. $result = array(
  187. 'file' => $trace[$i - 1]['file'],
  188. 'line' => $trace[$i - 1]['line'],
  189. 'method' => $trace[$i]['function'],
  190. 'assertion' => $trace[$i - 1]['function'],
  191. 'class' => get_class($trace[$i - 1]['object']),
  192. 'message' => String::insert($message, $data),
  193. 'data' => $data
  194. );
  195. $this->_result(($expression ? 'pass' : 'fail'), $result);
  196. return $expression;
  197. }
  198. /**
  199. * Checks that the actual result is equal, but not neccessarily identical, to the expected
  200. * result.
  201. *
  202. * @param mixed $expected
  203. * @param mixed $result
  204. * @param string $message
  205. */
  206. public function assertEqual($expected, $result, $message = '{:message}') {
  207. $data = null;
  208. if ($expected != $result) {
  209. $data = $this->_compare('equal', $expected, $result);
  210. }
  211. $this->assert($expected == $result, $message, $data);
  212. }
  213. /**
  214. * Checks that the actual result and the expected result are not equal to each other.
  215. *
  216. * @param mixed $expected
  217. * @param mixed $result
  218. * @param string $message
  219. */
  220. public function assertNotEqual($expected, $result, $message = '{:message}') {
  221. $this->assert($result != $expected, $message, compact('expected', 'result'));
  222. }
  223. /**
  224. * Checks that the actual result and the expected result are identical.
  225. *
  226. * @param mixed $expected
  227. * @param mixed $result
  228. * @param string $message
  229. */
  230. public function assertIdentical($expected, $result, $message = '{:message}') {
  231. $data = null;
  232. if ($expected !== $result) {
  233. $data = $this->_compare('identical', $expected, $result);
  234. }
  235. $this->assert($expected === $result, $message, $data);
  236. }
  237. /**
  238. * Checks that the result evalutes to true.
  239. *
  240. * For example:
  241. * {{{
  242. * $this->assertTrue('false', 'String has content');
  243. * }}}
  244. * {{{
  245. * $this->assertTrue(10, 'Non-Zero value');
  246. * }}}
  247. * {{{
  248. * $this->assertTrue(true, 'Boolean true');
  249. * }}}
  250. * all evaluate to true.
  251. *
  252. * @param mixed $result
  253. * @param string $message
  254. */
  255. public function assertTrue($result, $message = '{:message}') {
  256. $expected = true;
  257. $this->assert(!empty($result), $message, compact('expected', 'result'));
  258. }
  259. /**
  260. * Checks that the result evalutes to false.
  261. *
  262. * For example:
  263. * {{{
  264. * $this->assertFalse('', 'String is empty');
  265. * }}}
  266. *
  267. * {{{
  268. * $this->assertFalse(0, 'Zero value');
  269. * }}}
  270. *
  271. * {{{
  272. * $this->assertFalse(false, 'Boolean false');
  273. * }}}
  274. * all evaluate to false.
  275. *
  276. * @param mixed $result
  277. * @param string $message
  278. */
  279. public function assertFalse($result, $message = '{:message}') {
  280. $expected = false;
  281. $this->assert(empty($result), $message, compact('expected', 'result'));
  282. }
  283. /**
  284. * Checks if the result is null.
  285. *
  286. * @param mixed $result
  287. * @param string $message
  288. */
  289. public function assertNull($result, $message = '{:message}') {
  290. $expected = null;
  291. $this->assert($result === null, $message, compact('expected', 'result'));
  292. }
  293. /**
  294. * Checks that the regular expression `$expected` is not matched in the result.
  295. *
  296. * @param mixed $expected
  297. * @param mixed $result
  298. * @param string $message
  299. */
  300. public function assertNoPattern($expected, $result, $message = '{:message}') {
  301. $this->assert(!preg_match($expected, $result), $message, compact('expected', 'result'));
  302. }
  303. /**
  304. * Checks that the regular expression `$expected` is matched in the result.
  305. *
  306. * @param mixed $expected
  307. * @param mixed $result
  308. * @param string $message
  309. */
  310. public function assertPattern($expected, $result, $message = '{:message}') {
  311. $this->assert(!!preg_match($expected, $result), $message, compact('expected', 'result'));
  312. }
  313. /**
  314. * Takes an array $expected and generates a regex from it to match the provided $string.
  315. * Samples for $expected:
  316. *
  317. * Checks for an input tag with a name attribute (contains any non-empty value) and an id
  318. * attribute that contains 'my-input':
  319. * array('input' => array('name', 'id' => 'my-input'))
  320. *
  321. * Checks for two p elements with some text in them:
  322. * array(
  323. * array('p' => true),
  324. * 'textA',
  325. * '/p',
  326. * array('p' => true),
  327. * 'textB',
  328. * '/p'
  329. * )
  330. *
  331. * You can also specify a pattern expression as part of the attribute values, or the tag
  332. * being defined, if you prepend the value with preg: and enclose it with slashes, like so:
  333. * array(
  334. * array('input' => array('name', 'id' => 'preg:/FieldName\d+/')),
  335. * 'preg:/My\s+field/'
  336. * )
  337. *
  338. * Important: This function is very forgiving about whitespace and also accepts any
  339. * permutation of attribute order. It will also allow whitespaces between specified tags.
  340. *
  341. * @param string $string An HTML/XHTML/XML string
  342. * @param array $expected An array, see above
  343. * @param boolean $fullDebug
  344. * @access public
  345. */
  346. function assertTags($string, $expected, $fullDebug = false) {
  347. $regex = array();
  348. $normalized = array();
  349. foreach ((array) $expected as $key => $val) {
  350. if (!is_numeric($key)) {
  351. $normalized[] = array($key => $val);
  352. } else {
  353. $normalized[] = $val;
  354. }
  355. }
  356. $i = 0;
  357. foreach ($normalized as $tags) {
  358. $i++;
  359. if (is_string($tags) && $tags{0} == '<') {
  360. $tags = array(substr($tags, 1) => array());
  361. } elseif (is_string($tags)) {
  362. $tagsTrimmed = preg_replace('/\s+/m', '', $tags);
  363. if (preg_match('/^\*?\//', $tags, $match) && $tagsTrimmed !== '//') {
  364. $prefix = array(null, null);
  365. if ($match[0] == '*/') {
  366. $prefix = array('Anything, ', '.*?');
  367. }
  368. $regex[] = array(
  369. sprintf('%sClose %s tag', $prefix[0], substr($tags, strlen($match[0]))),
  370. sprintf('%s<[\s]*\/[\s]*%s[\s]*>[\n\r]*', $prefix[1], substr(
  371. $tags, strlen($match[0])
  372. )),
  373. $i
  374. );
  375. continue;
  376. }
  377. if (!empty($tags) && preg_match('/^regex\:\/(.+)\/$/i', $tags, $matches)) {
  378. $tags = $matches[1];
  379. $type = 'Regex matches';
  380. } else {
  381. $tags = preg_quote($tags, '/');
  382. $type = 'Text equals';
  383. }
  384. $regex[] = array(sprintf('%s "%s"', $type, $tags), $tags, $i);
  385. continue;
  386. }
  387. foreach ($tags as $tag => $attributes) {
  388. $regex[] = array(
  389. sprintf('Open %s tag', $tag),
  390. sprintf('[\s]*<%s', preg_quote($tag, '/')),
  391. $i
  392. );
  393. if ($attributes === true) {
  394. $attributes = array();
  395. }
  396. $attrs = array();
  397. $explanations = array();
  398. foreach ($attributes as $attr => $val) {
  399. if (is_numeric($attr) && preg_match('/^regex\:\/(.+)\/$/i', $val, $matches)) {
  400. $attrs[] = $matches[1];
  401. $explanations[] = sprintf('Regex "%s" matches', $matches[1]);
  402. continue;
  403. } else {
  404. $quotes = '"';
  405. if (is_numeric($attr)) {
  406. $attr = $val;
  407. $val = '.+?';
  408. $explanations[] = sprintf('Attribute "%s" present', $attr);
  409. } elseif (!empty($val) && preg_match('/^regex\:\/(.+)\/$/i', $val, $matches)) {
  410. $quotes = '"?';
  411. $val = $matches[1];
  412. $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
  413. } else {
  414. $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
  415. $val = preg_quote($val, '/');
  416. }
  417. $attrs[] = '[\s]+' . preg_quote($attr, '/') . "={$quotes}{$val}{$quotes}";
  418. }
  419. }
  420. if ($attrs) {
  421. $permutations = $this->_arrayPermute($attrs);
  422. $permutationTokens = array();
  423. foreach ($permutations as $permutation) {
  424. $permutationTokens[] = join('', $permutation);
  425. }
  426. $regex[] = array(
  427. sprintf('%s', join(', ', $explanations)),
  428. $permutationTokens,
  429. $i
  430. );
  431. }
  432. $regex[] = array(sprintf('End %s tag', $tag), '[\s]*\/?[\s]*>[\n\r]*', $i);
  433. }
  434. }
  435. foreach ($regex as $i => $assertation) {
  436. list($description, $expressions, $itemNum) = $assertation;
  437. $matches = false;
  438. foreach ((array) $expressions as $expression) {
  439. if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
  440. $matches = true;
  441. $string = substr($string, strlen($match[0]));
  442. break;
  443. }
  444. }
  445. if (!$matches) {
  446. $this->assert(false, sprintf(
  447. '{:message} - Item #%d / regex #%d failed: %s', $itemNum, $i, $description
  448. ));
  449. return false;
  450. }
  451. }
  452. return $this->assert(true, '%s');
  453. }
  454. /**
  455. * Used before a call to `assert*()` if you expect the test assertion to generate an exception
  456. * or PHP error. If no error or exception is thrown, a test failure will be reported. Can
  457. * be called multiple times per assertion, if more than one error is expected.
  458. *
  459. * @param mixed $message A string indicating what the error text is expected to be. This can
  460. * be an exact string, a /-delimited regular expression, or true, indicating that
  461. * any error text is acceptable.
  462. * @return void
  463. */
  464. public function expectException($message = true) {
  465. $this->_expected[] = $message;
  466. }
  467. /**
  468. * Reports test result messages.
  469. *
  470. * @param string $type The type of result being reported. Can be `'pass'`, `'fail'`, `'skip'`
  471. * or `'exception'`.
  472. * @param array $info An array of information about the test result. At a minimum, this should
  473. * contain a `'message'` key. Other possible keys are `'file'`, `'line'`,
  474. * `'class'`, `'method'`, `'assertion'` and `'data'`.
  475. * @param array $options Currently unimplemented.
  476. * @return void
  477. */
  478. protected function _result($type, $info, $options = array()) {
  479. $info = (array('result' => $type) + $info);
  480. $defaults = array();
  481. $options += $defaults;
  482. if ($this->_reporter) {
  483. $filtered = $this->_reporter->__invoke($info);
  484. $info = is_array($filtered) ? $filtered : $info;
  485. }
  486. $this->_results[] = $info;
  487. }
  488. /**
  489. * Runs an individual test method, collecting results and catching exceptions along the way.
  490. *
  491. * @param string $method The name of the test method to run.
  492. * @param array $options
  493. * @return void
  494. */
  495. protected function _runTestMethod($method, $options) {
  496. try {
  497. $this->setUp();
  498. } catch (Exception $e) {
  499. $this->_handleException($e, __LINE__ - 2);
  500. }
  501. $params = compact('options', 'method');
  502. $this->_filter(__CLASS__ . '::run', $params, function($self, $params, $chain) {
  503. try {
  504. $method = $params['method'];
  505. $lineFlag = __LINE__ + 1;
  506. $self->$method();
  507. } catch (Exception $e) {
  508. if (preg_match('/^Skipped test/', $e->getMessage())) {
  509. $self->invokeMethod('_result', array('skip', array(
  510. 'message' => $e->getMessage()
  511. )));
  512. } else {
  513. $self->invokeMethod('_handleException', array($e, $lineFlag));
  514. }
  515. }
  516. });
  517. $this->tearDown();
  518. }
  519. /**
  520. * Normalizes `Exception` objects and PHP error data into a single array format, and checks
  521. * each error against the list of expected errors (set using `expectException()`). If a match
  522. * is found, the expectation is removed from the stack and the error is ignored. If no match
  523. * is found, then the error data is logged to the test results.
  524. *
  525. * @param mixed $exception An `Exception` object instance, or an array containing the following
  526. * keys: `'message'`, `'file'`, `'line'`, `'trace'` (in `debug_backtrace()`
  527. * format) and optionally `'code'` (error code number) and `'context'` (an array
  528. * of variables relevant to the scope of where the error occurred).
  529. * @param integer $lineFlag A flag used for determining the relevant scope of the call stack.
  530. * Set to the line number where test methods are called.
  531. * @return void
  532. * @see lithium\test\Unit::expectException()
  533. * @see lithium\test\Unit::_reportException()
  534. */
  535. protected function _handleException($exception, $lineFlag = null) {
  536. if (is_object($exception)) {
  537. $data = array();
  538. foreach (array('message', 'file', 'line', 'trace') as $key) {
  539. $method = 'get' . ucfirst($key);
  540. $data[$key] = $exception->{$method}();
  541. }
  542. $ref = $exception->getTrace();
  543. $ref = $ref[0] + array('class' => null);
  544. if ($ref['class'] == __CLASS__ && $ref['function'] == 'skipIf') {
  545. return $this->_result('skip', $data);
  546. }
  547. $exception = $data;
  548. }
  549. $message = $exception['message'];
  550. $isExpected = (($exp = end($this->_expected)) && ($exp === true || $exp == $message || (
  551. Validator::isRegex($exp) && preg_match($exp, $message)
  552. )));
  553. if ($isExpected) {
  554. return array_pop($this->_expected);
  555. }
  556. $this->_reportException($exception, $lineFlag);
  557. }
  558. /**
  559. * Convert an exception object to an exception result array for test reporting.
  560. *
  561. * @param object $exception The exception object to report on. Statistics are gathered and
  562. * added to the reporting stack contained in `Unit::$_results`.
  563. * @param string $lineFlag
  564. * @return void
  565. * @todo Refactor so that reporters handle trace formatting.
  566. */
  567. protected function _reportException($exception, $lineFlag = null) {
  568. $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-');
  569. foreach ($exception['trace'] as $frame) {
  570. if (isset($scopedFrame)) {
  571. break;
  572. }
  573. if (!class_exists('lithium\analysis\Inspector')) {
  574. continue;
  575. }
  576. if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) {
  577. $scopedFrame = $frame;
  578. }
  579. }
  580. $trace = $exception['trace'];
  581. unset($exception['trace']);
  582. $this->_result('exception', $exception + array(
  583. 'class' => $initFrame['class'],
  584. 'method' => $initFrame['function'],
  585. 'trace' => Debugger::trace(array(
  586. 'trace' => $trace,
  587. 'format' => '{:functionRef}, line {:line}',
  588. 'includeScope' => false,
  589. 'scope' => array_filter(array(
  590. 'functionRef' => __NAMESPACE__ . '\{closure}',
  591. 'line' => $lineFlag
  592. )),
  593. ))
  594. ));
  595. }
  596. /**
  597. * Compare the expected with the result. If `$result` is null `$expected` equals `$type`
  598. * and `$result` equals `$expected`.
  599. *
  600. * @param string $type The type of comparison either `'identical'` or `'equal'` (default).
  601. * @param mixed $expected The expected value.
  602. * @param mixed $result An optional result value, defaults to `null`
  603. * @param string $trace An optional trace used internally to track arrays and objects,
  604. * defaults to `null`.
  605. * @return array Data with the keys `trace'`, `'expected'` and `'result'`.
  606. */
  607. protected function _compare($type, $expected, $result = null, $trace = null) {
  608. $types = array(
  609. 'trace' => $trace, 'expected' => gettype($expected), 'result' => gettype($result)
  610. );
  611. if ($types['expected'] !== $types['result']) {
  612. return $types;
  613. }
  614. $data = array();
  615. $isObject = false;
  616. if (is_object($expected)) {
  617. $isObject = true;
  618. $expected = (array) $expected;
  619. $result = (array) $result;
  620. }
  621. if (is_array($expected)) {
  622. foreach ($expected as $key => $value) {
  623. $check = array_key_exists($key, $result) ? $result[$key] : false;
  624. $newTrace = (($isObject == true) ? "{$trace}->{$key}" : "{$trace}[{$key}]");
  625. if ($type === 'identical') {
  626. if ($value === $check) {
  627. continue;
  628. }
  629. if ($check === false) {
  630. $trace = $newTrace;
  631. return compact('trace', 'expected', 'result');
  632. }
  633. } else {
  634. if ($value == $check) {
  635. continue;
  636. }
  637. if (!is_array($value)) {
  638. $trace = $newTrace;
  639. return compact('trace', 'expected', 'result');
  640. }
  641. }
  642. $compare = $this->_compare($type, $value, $check, $newTrace);
  643. if ($compare !== true) {
  644. $data[] = $compare;
  645. }
  646. }
  647. if (empty($data)) {
  648. return compact('trace', 'expected', 'result');
  649. }
  650. return $data;
  651. }
  652. if ($type === 'identical') {
  653. if ($expected === $result) {
  654. return true;
  655. }
  656. } else {
  657. if ($expected == $result) {
  658. return true;
  659. }
  660. }
  661. $data = compact('trace', 'expected', 'result');
  662. return $data;
  663. }
  664. /**
  665. * Returns a basic message for the data returned from `_result()`.
  666. *
  667. * @param array $data The data to use for creating the message.
  668. * @return string
  669. * @see lithium\test\Unit::assert()
  670. * @see lithium\test\Unit::_result()
  671. */
  672. protected function _message($data = array()) {
  673. $messages = null;
  674. if (!empty($data[0])) {
  675. foreach ($data as $message) {
  676. $messages .= $this->_message($message);
  677. }
  678. return $messages;
  679. }
  680. $defaults = array('trace' => null, 'expected' => null, 'result' => null);
  681. $data = (array) $data + $defaults;
  682. return sprintf("trace: %s\nexpected: %s\nresult: %s\n",
  683. $data['trace'],
  684. var_export($data['expected'], true),
  685. var_export($data['result'], true)
  686. );
  687. }
  688. /**
  689. * Generates all permutation of an array $items and returns them in a new array.
  690. *
  691. * @param array $items An array of items
  692. * @param array $perms
  693. * @return array
  694. */
  695. protected function _arrayPermute($items, $perms = array()) {
  696. static $permuted;
  697. if (empty($perms)) {
  698. $permuted = array();
  699. }
  700. if (empty($items)) {
  701. $permuted[] = $perms;
  702. return;
  703. }
  704. $numItems = count($items) - 1;
  705. for ($i = $numItems; $i >= 0; --$i) {
  706. $newItems = $items;
  707. $newPerms = $perms;
  708. list($tmp) = array_splice($newItems, $i, 1);
  709. array_unshift($newPerms, $tmp);
  710. $this->_arrayPermute($newItems, $newPerms);
  711. }
  712. return $permuted;
  713. }
  714. /**
  715. * Removes everything from `resources/tmp/tests` directory.
  716. * Call from inside of your test method or `tearDown()`.
  717. *
  718. * @param string $path path to directory of contents to remove
  719. * if first character is NOT `/` prepend `LITHIUM_APP_PATH/resources/tmp/`
  720. * @return void
  721. */
  722. protected function _cleanUp($path = null) {
  723. $path = $path ?: LITHIUM_APP_PATH . '/resources/tmp/tests';
  724. $path = $path[0] !== '/' ? LITHIUM_APP_PATH . '/resources/tmp/' . $path : $path;
  725. if (!is_dir($path)) {
  726. return;
  727. }
  728. $dirs = new RecursiveDirectoryIterator($path);
  729. $iterator = new RecursiveIteratorIterator($dirs, RecursiveIteratorIterator::CHILD_FIRST);
  730. foreach ($iterator as $item) {
  731. if ($item->getPathname() === "{$path}/empty") continue;
  732. ($item->isDir()) ? rmdir($item->getPathname()) : unlink($item->getPathname());
  733. }
  734. }
  735. }
  736. ?>