Unit.php 59 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058
  1. <?php
  2. /**
  3. * Lithium: the most rad php framework
  4. *
  5. * @copyright Copyright 2013, 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 ReflectionClass;
  11. use InvalidArgumentException;
  12. use lithium\util\String;
  13. use lithium\core\Libraries;
  14. use lithium\util\Validator;
  15. use lithium\analysis\Debugger;
  16. use lithium\analysis\Inspector;
  17. use RecursiveDirectoryIterator;
  18. use RecursiveIteratorIterator;
  19. /**
  20. * This is the base class for all test cases. Test are performed using an assertion method. If the
  21. * assertion is correct, the test passes, otherwise it fails. Most assertions take an expected
  22. * result, a received result, and a message (to describe the failure) as parameters.
  23. *
  24. * Unit tests are used to check a small unit of functionality, such as if a
  25. * method returns an expected result for a known input, or whether an adapter
  26. * can successfully open a connection.
  27. *
  28. * Available assertions are (see `assert<assertion-name>` methods for details): Equal, False,
  29. * Identical, NoPattern, NotEqual, Null, Pattern, Tags, True.
  30. *
  31. * If an assertion is expected to produce an exception, the `expectException` method should be
  32. * called before it.
  33. */
  34. class Unit extends \lithium\core\Object {
  35. /**
  36. * The Reference to a test reporter class.
  37. *
  38. * @var string
  39. */
  40. protected $_reporter = null;
  41. /**
  42. * The list of test results.
  43. *
  44. * @var string
  45. */
  46. protected $_results = array();
  47. /**
  48. * The list of expected exceptions.
  49. *
  50. * @var string
  51. */
  52. protected $_expected = array();
  53. /**
  54. * Internal types and how to test for them
  55. *
  56. * @var array
  57. */
  58. protected static $_internalTypes = array(
  59. 'array' => 'is_array',
  60. 'bool' => 'is_bool',
  61. 'callable' => 'is_callable',
  62. 'double' => 'is_double',
  63. 'float' => 'is_float',
  64. 'int' => 'is_int',
  65. 'integer' => 'is_integer',
  66. 'long' => 'is_long',
  67. 'null' => 'is_null',
  68. 'numeric' => 'is_numeric',
  69. 'object' => 'is_object',
  70. 'real' => 'is_real',
  71. 'resource' => 'is_resource',
  72. 'scalar' => 'is_scalar',
  73. 'string' => 'is_string'
  74. );
  75. /**
  76. * Finds the test case for the corresponding class name.
  77. *
  78. * @param string $class A fully-namespaced class reference for which to find a test case.
  79. * @return string Returns the class name of a test case for `$class`, or `null` if none exists.
  80. */
  81. public static function get($class) {
  82. $parts = explode('\\', $class);
  83. $library = array_shift($parts);
  84. $name = array_pop($parts);
  85. $type = 'tests.cases.' . implode('.', $parts);
  86. return Libraries::locate($type, $name, compact('library'));
  87. }
  88. /**
  89. * Setup method run before every test method. override in subclasses
  90. *
  91. * @return void
  92. */
  93. public function setUp() {}
  94. /**
  95. * Teardown method run after every test method. override in subclasses
  96. *
  97. * @return void
  98. */
  99. public function tearDown() {}
  100. /**
  101. * Subclasses should use this method to set conditions that, if failed, terminate further
  102. * testing.
  103. *
  104. * For example:
  105. * {{{
  106. * public function skip() {
  107. * $this->_dbConfig = Connections::get('default', array('config' => true));
  108. * $hasDb = (isset($this->_dbConfig['adapter']) && $this->_dbConfig['adapter'] == 'MySql');
  109. * $message = 'Test database is either unavailable, or not using a MySQL adapter';
  110. * $this->skipIf(!$hasDb, $message);
  111. * }
  112. * }}}
  113. *
  114. * @return void
  115. */
  116. public function skip() {}
  117. /**
  118. * Skips test(s) if the condition is met.
  119. *
  120. * When used within a subclass' `skip` method, all tests are ignored if the condition is met,
  121. * otherwise processing continues as normal.
  122. * For other methods, only the remainder of the method is skipped, when the condition is met.
  123. *
  124. * @throws Exception
  125. * @param boolean $condition
  126. * @param string|boolean $message Message to pass if the condition is met.
  127. * @return mixed
  128. */
  129. public function skipIf($condition, $message = false) {
  130. if ($condition) {
  131. throw new Exception(is_string($message) ? $message : null);
  132. }
  133. }
  134. /**
  135. * Returns the class name that is the subject under test for this test case.
  136. *
  137. * @return string
  138. */
  139. public function subject() {
  140. return preg_replace('/Test$/', '', str_replace('tests\\cases\\', '', get_class($this)));
  141. }
  142. /**
  143. * Return test methods to run
  144. *
  145. * @return array
  146. */
  147. public function methods() {
  148. static $methods;
  149. return $methods ?: $methods = array_values(preg_grep('/^test/', get_class_methods($this)));
  150. }
  151. /**
  152. * Runs the test methods in this test case, with the given options.
  153. *
  154. * @param array $options The options to use when running the test. Available options are:
  155. * - 'methods': An arbitrary array of method names to execute. If
  156. * unspecified, all methods starting with 'test' are run.
  157. * - 'reporter': A closure which gets called after each test result,
  158. * which may modify the results presented.
  159. * @return array
  160. */
  161. public function run(array $options = array()) {
  162. $defaults = array('methods' => array(), 'reporter' => null, 'handler' => null);
  163. $options += $defaults;
  164. $this->_results = array();
  165. $self = $this;
  166. try {
  167. $this->skip();
  168. } catch (Exception $e) {
  169. $this->_handleException($e);
  170. return $this->_results;
  171. }
  172. $h = function($code, $message, $file, $line = 0, $context = array()) use ($self) {
  173. $trace = debug_backtrace();
  174. $trace = array_slice($trace, 1, count($trace));
  175. $self->invokeMethod('_reportException', array(
  176. compact('code', 'message', 'file', 'line', 'trace', 'context')
  177. ));
  178. };
  179. $options['handler'] = $options['handler'] ?: $h;
  180. set_error_handler($options['handler']);
  181. $methods = $options['methods'] ?: $this->methods();
  182. $this->_reporter = $options['reporter'] ?: $this->_reporter;
  183. foreach ($methods as $method) {
  184. if ($this->_runTestMethod($method, $options) === false) {
  185. break;
  186. }
  187. }
  188. restore_error_handler();
  189. return $this->_results;
  190. }
  191. /**
  192. * General assert method used by others for common output.
  193. *
  194. * @param boolean $expression
  195. * @param string|boolean $message The message to output. If the message is not a string,
  196. * then it will be converted to '{:message}'. Use '{:message}' in the string and it
  197. * will use the `$data` to format the message with `String::insert()`.
  198. * @param array $data
  199. * @return void
  200. */
  201. public function assert($expression, $message = false, $data = array()) {
  202. if (!is_string($message)) {
  203. $message = '{:message}';
  204. }
  205. if (strpos($message, "{:message}") !== false) {
  206. $params = $data;
  207. $params['message'] = $this->_message($params);
  208. $message = String::insert($message, $params);
  209. }
  210. $trace = Debugger::trace(array(
  211. 'start' => 1, 'depth' => 4, 'format' => 'array', 'closures' => !$expression
  212. ));
  213. $methods = $this->methods();
  214. $i = 1;
  215. while ($i < count($trace)) {
  216. if (in_array($trace[$i]['function'], $methods) && $trace[$i - 1]['object'] == $this) {
  217. break;
  218. }
  219. $i++;
  220. }
  221. $class = isset($trace[$i - 1]['object']) ? get_class($trace[$i - 1]['object']) : null;
  222. $method = isset($trace[$i]) ? $trace[$i]['function'] : $trace[$i - 1]['function'];
  223. $result = compact('class', 'method', 'message', 'data') + array(
  224. 'file' => $trace[$i - 1]['file'],
  225. 'line' => $trace[$i - 1]['line'],
  226. 'assertion' => $trace[$i - 1]['function']
  227. );
  228. $this->_result($expression ? 'pass' : 'fail', $result);
  229. return $expression;
  230. }
  231. /**
  232. * Generates a failed test with the passed message.
  233. *
  234. * @param string $message
  235. */
  236. public function fail($message = false) {
  237. $this->assert(false, $message);
  238. }
  239. /**
  240. * Fixes some issues regarding the used EOL character(s).
  241. *
  242. * On linux EOL is LF, on Windows it is normally CRLF, but the latter may depend also
  243. * on the git config core.autocrlf setting. As some tests use heredoc style (<<<) to
  244. * specify multiline expectations, this EOL issue may cause tests to fail only because
  245. * of a difference in EOL's used.
  246. *
  247. * in assertEqual, assertNotEqual, assertPattern and assertNotPattern this function is
  248. * called to get rid of any EOL differences.
  249. *
  250. * @param mixed $expected
  251. * @param mixed $result
  252. */
  253. protected function _normalizeLineEndings($expected, $result) {
  254. if (is_string($expected) && is_string($result)) {
  255. $expected = preg_replace('/\r\n/', "\n", $expected);
  256. $result = preg_replace('/\r\n/', "\n", $result);
  257. }
  258. return array($expected, $result);
  259. }
  260. /**
  261. * Checks that the actual result is equal, but not neccessarily identical, to the expected
  262. * result.
  263. *
  264. * @param mixed $expected
  265. * @param mixed $result
  266. * @param string|boolean $message
  267. */
  268. public function assertEqual($expected, $result, $message = false) {
  269. list($expected, $result) = $this->_normalizeLineEndings($expected, $result);
  270. $data = ($expected != $result) ? $this->_compare('equal', $expected, $result) : null;
  271. return $this->assert($expected == $result, $message, $data);
  272. }
  273. /**
  274. * Checks that the actual result and the expected result are not equal to each other.
  275. *
  276. * @param mixed $expected
  277. * @param mixed $result
  278. * @param string|boolean $message
  279. */
  280. public function assertNotEqual($expected, $result, $message = false) {
  281. list($expected, $result) = $this->_normalizeLineEndings($expected, $result);
  282. return $this->assert($result != $expected, $message, compact('expected', 'result'));
  283. }
  284. /**
  285. * Checks that the actual result and the expected result are identical.
  286. *
  287. * @param mixed $expected
  288. * @param mixed $result
  289. * @param string|boolean $message
  290. */
  291. public function assertIdentical($expected, $result, $message = false) {
  292. $data = ($expected !== $result) ? $this->_compare('identical', $expected, $result) : null;
  293. return $this->assert($expected === $result, $message, $data);
  294. }
  295. /**
  296. * Checks that the result evaluates to true.
  297. *
  298. * For example:
  299. * {{{
  300. * $this->assertTrue('false', 'String has content');
  301. * }}}
  302. * {{{
  303. * $this->assertTrue(10, 'Non-Zero value');
  304. * }}}
  305. * {{{
  306. * $this->assertTrue(true, 'Boolean true');
  307. * }}}
  308. * all evaluate to true.
  309. *
  310. * @param mixed $result
  311. * @param string $message
  312. */
  313. public function assertTrue($result, $message = '{:message}') {
  314. $expected = true;
  315. return $this->assert(!empty($result), $message, compact('expected', 'result'));
  316. }
  317. /**
  318. * Checks that the result evaluates to false.
  319. *
  320. * For example:
  321. * {{{
  322. * $this->assertFalse('', 'String is empty');
  323. * }}}
  324. *
  325. * {{{
  326. * $this->assertFalse(0, 'Zero value');
  327. * }}}
  328. *
  329. * {{{
  330. * $this->assertFalse(false, 'Boolean false');
  331. * }}}
  332. * all evaluate to false.
  333. *
  334. * @param mixed $result
  335. * @param string $message
  336. */
  337. public function assertFalse($result, $message = '{:message}') {
  338. $expected = false;
  339. return $this->assert(empty($result), $message, compact('expected', 'result'));
  340. }
  341. /**
  342. * Checks if the result is null.
  343. *
  344. * @param mixed $result
  345. * @param string $message
  346. */
  347. public function assertNull($result, $message = '{:message}') {
  348. $expected = null;
  349. return $this->assert($result === null, $message, compact('expected', 'result'));
  350. }
  351. /**
  352. * Checks that the regular expression `$expected` is not matched in the result.
  353. *
  354. * @param mixed $expected
  355. * @param mixed $result
  356. * @param string $message
  357. */
  358. public function assertNoPattern($expected, $result, $message = '{:message}') {
  359. list($expected, $result) = $this->_normalizeLineEndings($expected, $result);
  360. $params = compact('expected', 'result');
  361. return $this->assert(!preg_match($expected, $result), $message, $params);
  362. }
  363. /**
  364. * Checks that the regular expression `$expected` is matched in the result.
  365. *
  366. * @param mixed $expected
  367. * @param mixed $result
  368. * @param string $message
  369. */
  370. public function assertPattern($expected, $result, $message = '{:message}') {
  371. list($expected, $result) = $this->_normalizeLineEndings($expected, $result);
  372. $params = compact('expected', 'result');
  373. return $this->assert(!!preg_match($expected, $result), $message, $params);
  374. }
  375. /**
  376. * Takes an array $expected and generates a regex from it to match the provided $string.
  377. * Samples for $expected:
  378. *
  379. * Checks for an input tag with a name attribute (contains any non-empty value) and an id
  380. * attribute that contains 'my-input':
  381. * {{{
  382. * array('input' => array('name', 'id' => 'my-input'))
  383. * }}}
  384. *
  385. * Checks for two p elements with some text in them:
  386. * {{{
  387. * array(
  388. * array('p' => true),
  389. * 'textA',
  390. * '/p',
  391. * array('p' => true),
  392. * 'textB',
  393. * '/p'
  394. * )
  395. * }}}
  396. *
  397. * You can also specify a pattern expression as part of the attribute values, or the tag
  398. * being defined, if you prepend the value with preg: and enclose it with slashes, like so:
  399. * {{{
  400. * array(
  401. * array('input' => array('name', 'id' => 'preg:/FieldName\d+/')),
  402. * 'preg:/My\s+field/'
  403. * )
  404. * }}}
  405. *
  406. * Important: This function is very forgiving about whitespace and also accepts any
  407. * permutation of attribute order. It will also allow whitespaces between specified tags.
  408. *
  409. * @param string $string An HTML/XHTML/XML string
  410. * @param array $expected An array, see above
  411. * @return boolean
  412. */
  413. public function assertTags($string, $expected) {
  414. $regex = array();
  415. $normalized = array();
  416. foreach ((array) $expected as $key => $val) {
  417. if (!is_numeric($key)) {
  418. $normalized[] = array($key => $val);
  419. } else {
  420. $normalized[] = $val;
  421. }
  422. }
  423. $i = 0;
  424. foreach ($normalized as $tags) {
  425. $i++;
  426. if (is_string($tags) && $tags{0} === '<') {
  427. $tags = array(substr($tags, 1) => array());
  428. } elseif (is_string($tags)) {
  429. $tagsTrimmed = preg_replace('/\s+/m', '', $tags);
  430. if (preg_match('/^\*?\//', $tags, $match) && $tagsTrimmed !== '//') {
  431. $prefix = array(null, null);
  432. if ($match[0] === '*/') {
  433. $prefix = array('Anything, ', '.*?');
  434. }
  435. $regex[] = array(
  436. sprintf('%sClose %s tag', $prefix[0], substr($tags, strlen($match[0]))),
  437. sprintf('%s<[\s]*\/[\s]*%s[\s]*>[\n\r]*', $prefix[1], substr(
  438. $tags, strlen($match[0])
  439. )),
  440. $i
  441. );
  442. continue;
  443. }
  444. if (!empty($tags) && preg_match('/^regex\:\/(.+)\/$/i', $tags, $matches)) {
  445. $tags = $matches[1];
  446. $type = 'Regex matches';
  447. } else {
  448. $tags = preg_quote($tags, '/');
  449. $type = 'Text equals';
  450. }
  451. $regex[] = array(sprintf('%s "%s"', $type, $tags), $tags, $i);
  452. continue;
  453. }
  454. foreach ($tags as $tag => $attributes) {
  455. $regex[] = array(
  456. sprintf('Open %s tag', $tag),
  457. sprintf('[\s]*<%s', preg_quote($tag, '/')),
  458. $i
  459. );
  460. if ($attributes === true) {
  461. $attributes = array();
  462. }
  463. $attrs = array();
  464. $explanations = array();
  465. foreach ($attributes as $attr => $val) {
  466. if (is_numeric($attr) && preg_match('/^regex\:\/(.+)\/$/i', $val, $matches)) {
  467. $attrs[] = $matches[1];
  468. $explanations[] = sprintf('Regex "%s" matches', $matches[1]);
  469. continue;
  470. } else {
  471. $quotes = '"';
  472. if (is_numeric($attr)) {
  473. $attr = $val;
  474. $val = '.+?';
  475. $explanations[] = sprintf('Attribute "%s" present', $attr);
  476. } elseif (
  477. !empty($val) && preg_match('/^regex\:\/(.+)\/$/i', $val, $matches)
  478. ) {
  479. $quotes = '"?';
  480. $val = $matches[1];
  481. $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
  482. } else {
  483. $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
  484. $val = preg_quote($val, '/');
  485. }
  486. $attrs[] = '[\s]+' . preg_quote($attr, '/') . "={$quotes}{$val}{$quotes}";
  487. }
  488. }
  489. if ($attrs) {
  490. $permutations = $this->_arrayPermute($attrs);
  491. $permutationTokens = array();
  492. foreach ($permutations as $permutation) {
  493. $permutationTokens[] = join('', $permutation);
  494. }
  495. $regex[] = array(
  496. sprintf('%s', join(', ', $explanations)),
  497. $permutationTokens,
  498. $i
  499. );
  500. }
  501. $regex[] = array(sprintf('End %s tag', $tag), '[\s]*\/?[\s]*>[\n\r]*', $i);
  502. }
  503. }
  504. foreach ($regex as $i => $assertation) {
  505. list($description, $expressions, $itemNum) = $assertation;
  506. $matches = false;
  507. foreach ((array) $expressions as $expression) {
  508. if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
  509. $matches = true;
  510. $string = substr($string, strlen($match[0]));
  511. break;
  512. }
  513. }
  514. if (!$matches) {
  515. $this->assert(false, sprintf(
  516. '- Item #%d / regex #%d failed: %s', $itemNum, $i, $description
  517. ));
  518. return false;
  519. }
  520. }
  521. return $this->assert(true);
  522. }
  523. /**
  524. * Assert that the code passed in a closure throws an exception matching the passed expected
  525. * exception.
  526. *
  527. * The value passed to `exepected` is either an exception class name or the expected message.
  528. *
  529. * @param mixed $expected A string indicating what the error text is expected to be. This can
  530. * be an exact string, a /-delimited regular expression, or true, indicating that
  531. * any error text is acceptable.
  532. * @param closure $closure A closure containing the code that should throw the exception.
  533. * @param string $message
  534. * @return boolean
  535. */
  536. public function assertException($expected, $closure, $message = '{:message}') {
  537. try {
  538. $closure();
  539. $message = sprintf('An exception "%s" was expected but not thrown.', $expected);
  540. return $this->assert(false, $message, compact('expected', 'result'));
  541. } catch (Exception $e) {
  542. $class = get_class($e);
  543. $eMessage = $e->getMessage();
  544. if (get_class($e) === $expected) {
  545. $result = $class;
  546. return $this->assert(true, $message, compact('expected', 'result'));
  547. }
  548. if ($eMessage === $expected) {
  549. $result = $eMessage;
  550. return $this->assert(true, $message, compact('expected', 'result'));
  551. }
  552. if (Validator::isRegex($expected) && preg_match($expected, $eMessage)) {
  553. $result = $eMessage;
  554. return $this->assert(true, $message, compact('expected', 'result'));
  555. }
  556. $message = sprintf(
  557. 'Exception "%s" was expected. Exception "%s" with message "%s" was thrown instead.',
  558. $expected, get_class($e), $eMessage);
  559. return $this->assert(false, $message);
  560. }
  561. }
  562. /**
  563. * Assert Cookie data is properly set in headers.
  564. *
  565. * The value passed to `exepected` is an array of the cookie data, with at least the key and
  566. * value expected, but can support any of the following keys:
  567. * - `key`: the expected key
  568. * - `value`: the expected value
  569. * - `path`: optionally specifiy a path
  570. * - `name`: optionally specify the cookie name
  571. * - `expires`: optionally assert a specific expire time
  572. *
  573. * @param array $expected
  574. * @param array $headers When empty, value of `headers_list()` is used.
  575. * @return boolean
  576. */
  577. public function assertCookie($expected, $headers = null) {
  578. $matched = $this->_cookieMatch($expected, $headers);
  579. if (!$matched['match']) {
  580. $message = sprintf('%s - Cookie not found in headers.', $matched['pattern']);
  581. return $this->assert(false, $message, compact('expected', 'result'));
  582. }
  583. return $this->assert(true, '%s');
  584. }
  585. /**
  586. * Assert Cookie data is *not* set in headers.
  587. *
  588. * The value passed to `expected` is an array of the cookie data, with at least the key and
  589. * value expected, but can support any of the following keys:
  590. * - `key`: the expected key
  591. * - `value`: the expected value
  592. * - `path`: optionally specify a path
  593. * - `name`: optionally specify the cookie name
  594. * - `expires`: optionally assert a specific expire time
  595. *
  596. * @param array $expected
  597. * @param array $headers When empty, value of `headers_list()` is used.
  598. * @return boolean
  599. */
  600. public function assertNoCookie($expected, $headers = null) {
  601. $matched = $this->_cookieMatch($expected, $headers);
  602. if ($matched['match']) {
  603. $message = sprintf('%s - Cookie found in headers.', $matched['pattern']);
  604. return $this->assert(false, $message, compact('expected', 'result'));
  605. }
  606. return $this->assert(true, '%s');
  607. }
  608. /**
  609. * Match an `$expected` cookie with the given headers. If no headers are provided, then
  610. * the value of `headers_list()` will be used.
  611. *
  612. * @param array $expected
  613. * @param array $headers When empty, value of `headers_list()` will be used.
  614. * @return boolean True if cookie is found, false otherwise.
  615. */
  616. protected function _cookieMatch($expected, $headers) {
  617. $defaults = array('path' => '/', 'name' => '[\w.-]+');
  618. $expected += $defaults;
  619. $headers = ($headers) ?: headers_list();
  620. $value = preg_quote(urlencode($expected['value']), '/');
  621. $key = explode('.', $expected['key']);
  622. $key = (count($key) === 1) ? '[' . current($key) . ']' : ('[' . join('][', $key) . ']');
  623. $key = preg_quote($key, '/');
  624. if (isset($expected['expires'])) {
  625. $date = gmdate('D, d-M-Y H:i:s \G\M\T', strtotime($expected['expires']));
  626. $expires = preg_quote($date, '/');
  627. } else {
  628. $expires = '(?:.+?)';
  629. }
  630. $path = preg_quote($expected['path'], '/');
  631. $pattern = "/^Set\-Cookie:\s{$expected['name']}$key=$value;";
  632. $pattern .= "\sexpires=$expires;\spath=$path/";
  633. $match = false;
  634. foreach ($headers as $header) {
  635. if (preg_match($pattern, $header)) {
  636. $match = true;
  637. continue;
  638. }
  639. }
  640. return compact('match', 'pattern');
  641. }
  642. /**
  643. * Used before a call to `assert*()` if you expect the test assertion to generate an exception
  644. * or PHP error. If no error or exception is thrown, a test failure will be reported. Can
  645. * be called multiple times per assertion, if more than one error is expected.
  646. *
  647. * @param mixed $message A string indicating what the error text is expected to be. This can
  648. * be an exact string, a /-delimited regular expression, or true, indicating that
  649. * any error text is acceptable.
  650. * @return void
  651. */
  652. public function expectException($message = true) {
  653. $this->_expected[] = $message;
  654. }
  655. /**
  656. * Reports test result messages.
  657. *
  658. * @param string $type The type of result being reported. Can be `'pass'`, `'fail'`, `'skip'`
  659. * or `'exception'`.
  660. * @param array $info An array of information about the test result. At a minimum, this should
  661. * contain a `'message'` key. Other possible keys are `'file'`, `'line'`,
  662. * `'class'`, `'method'`, `'assertion'` and `'data'`.
  663. * @param array $options Currently unimplemented.
  664. * @return void
  665. */
  666. protected function _result($type, $info, array $options = array()) {
  667. $info = (array('result' => $type) + $info);
  668. $defaults = array();
  669. $options += $defaults;
  670. if ($this->_reporter) {
  671. $filtered = $this->_reporter->__invoke($info);
  672. $info = is_array($filtered) ? $filtered : $info;
  673. }
  674. $this->_results[] = $info;
  675. }
  676. /**
  677. * Runs an individual test method, collecting results and catching exceptions along the way.
  678. *
  679. * @param string $method The name of the test method to run.
  680. * @param array $options
  681. * @return mixed
  682. * @filter
  683. */
  684. protected function _runTestMethod($method, $options) {
  685. try {
  686. $this->setUp();
  687. } catch (Exception $e) {
  688. $this->_handleException($e, __LINE__ - 2);
  689. return $this->_results;
  690. }
  691. $params = compact('options', 'method');
  692. $passed = $this->_filter(__CLASS__ . '::run', $params, function($self, $params, $chain) {
  693. try {
  694. $method = $params['method'];
  695. $lineFlag = __LINE__ + 1;
  696. $self->{$method}();
  697. } catch (Exception $e) {
  698. $self->invokeMethod('_handleException', array($e));
  699. }
  700. });
  701. foreach ($this->_expected as $expected) {
  702. $this->_result('fail', compact('method') + array(
  703. 'class' => get_class($this),
  704. 'message' => "Expected exception matching `{$expected}` uncaught.",
  705. 'data' => array(),
  706. 'file' => null,
  707. 'line' => null,
  708. 'assertion' => 'expectException'
  709. ));
  710. }
  711. $this->_expected = array();
  712. try {
  713. $this->tearDown();
  714. } catch (Exception $e) {
  715. $this->_handleException($e, __LINE__ - 2);
  716. }
  717. return $passed;
  718. }
  719. /**
  720. * Normalizes `Exception` objects and PHP error data into a single array format, and checks
  721. * each error against the list of expected errors (set using `expectException()`). If a match
  722. * is found, the expectation is removed from the stack and the error is ignored. If no match
  723. * is found, then the error data is logged to the test results.
  724. *
  725. * @see lithium\test\Unit::expectException()
  726. * @see lithium\test\Unit::_reportException()
  727. * @param mixed $exception An `Exception` object instance, or an array containing the following
  728. * keys: `'message'`, `'file'`, `'line'`, `'trace'` (in `debug_backtrace()`
  729. * format) and optionally `'code'` (error code number) and `'context'` (an array
  730. * of variables relevant to the scope of where the error occurred).
  731. * @param integer $lineFlag A flag used for determining the relevant scope of the call stack.
  732. * Set to the line number where test methods are called.
  733. * @return void
  734. */
  735. protected function _handleException($exception, $lineFlag = null) {
  736. $data = $exception;
  737. if (is_object($exception)) {
  738. $data = array();
  739. foreach (array('message', 'file', 'line', 'trace') as $key) {
  740. $method = 'get' . ucfirst($key);
  741. $data[$key] = $exception->{$method}();
  742. }
  743. $ref = $exception->getTrace();
  744. $ref = $ref[0] + array('class' => null);
  745. if ($ref['class'] == __CLASS__ && $ref['function'] == 'skipIf') {
  746. return $this->_result('skip', $data);
  747. }
  748. }
  749. return $this->_reportException($data, $lineFlag);
  750. }
  751. /**
  752. * Convert an exception object to an exception result array for test reporting.
  753. *
  754. * @param array $exception The exception data to report on. Statistics are gathered and
  755. * added to the reporting stack contained in `Unit::$_results`.
  756. * @param string $lineFlag
  757. * @return void
  758. * @todo Refactor so that reporters handle trace formatting.
  759. */
  760. protected function _reportException($exception, $lineFlag = null) {
  761. $message = $exception['message'];
  762. $isExpected = (($exp = end($this->_expected)) && ($exp === true || $exp === $message || (
  763. Validator::isRegex($exp) && preg_match($exp, $message)
  764. )));
  765. if ($isExpected) {
  766. return array_pop($this->_expected);
  767. }
  768. $initFrame = current($exception['trace']) + array('class' => '-', 'function' => '-');
  769. foreach ($exception['trace'] as $frame) {
  770. if (isset($scopedFrame)) {
  771. break;
  772. }
  773. if (!class_exists('lithium\analysis\Inspector')) {
  774. continue;
  775. }
  776. if (isset($frame['class']) && in_array($frame['class'], Inspector::parents($this))) {
  777. $scopedFrame = $frame;
  778. }
  779. }
  780. if (class_exists('lithium\analysis\Debugger')) {
  781. $exception['trace'] = Debugger::trace(array(
  782. 'trace' => $exception['trace'],
  783. 'format' => '{:functionRef}, line {:line}',
  784. 'includeScope' => false,
  785. 'scope' => array_filter(array(
  786. 'functionRef' => __NAMESPACE__ . '\{closure}',
  787. 'line' => $lineFlag
  788. ))
  789. ));
  790. }
  791. $this->_result('exception', $exception + array(
  792. 'class' => $initFrame['class'],
  793. 'method' => $initFrame['function']
  794. ));
  795. }
  796. /**
  797. * Compare the expected with the result. If `$result` is null `$expected` equals `$type`
  798. * and `$result` equals `$expected`.
  799. *
  800. * @param string $type The type of comparison either `'identical'` or `'equal'` (default).
  801. * @param mixed $expected The expected value.
  802. * @param mixed $result An optional result value, defaults to `null`
  803. * @param string $trace An optional trace used internally to track arrays and objects,
  804. * defaults to `null`.
  805. * @return array Data with the keys `trace'`, `'expected'` and `'result'`.
  806. */
  807. protected function _compare($type, $expected, $result = null, $trace = null) {
  808. $compareTypes = function($expected, $result, $trace) {
  809. $types = array('expected' => gettype($expected), 'result' => gettype($result));
  810. if ($types['expected'] !== $types['result']) {
  811. $expected = trim("({$types['expected']}) " . print_r($expected, true));
  812. $result = trim("({$types['result']}) " . print_r($result, true));
  813. return compact('trace', 'expected', 'result');
  814. }
  815. };
  816. if ($types = $compareTypes($expected, $result, $trace)) {
  817. return $types;
  818. }
  819. $data = array();
  820. if (!is_scalar($expected)) {
  821. foreach ($expected as $key => $value) {
  822. $newTrace = "{$trace}[{$key}]";
  823. $isObject = false;
  824. if (is_object($expected)) {
  825. $isObject = true;
  826. $expected = (array) $expected;
  827. $result = (array) $result;
  828. }
  829. if (!array_key_exists($key, $result)) {
  830. $trace = (!$key) ? null : $newTrace;
  831. $expected = (!$key) ? $expected : $value;
  832. $result = ($key) ? null : $result;
  833. return compact('trace', 'expected', 'result');
  834. }
  835. $check = $result[$key];
  836. if ($isObject) {
  837. $newTrace = ($trace) ? "{$trace}->{$key}" : $key;
  838. $expected = (object) $expected;
  839. $result = (object) $result;
  840. }
  841. if ($type === 'identical') {
  842. if ($value === $check) {
  843. if ($types = $compareTypes($value, $check, $trace)) {
  844. return $types;
  845. }
  846. continue;
  847. }
  848. if ($check === array()) {
  849. $trace = $newTrace;
  850. return compact('trace', 'expected', 'result');
  851. }
  852. if (is_string($check)) {
  853. $trace = $newTrace;
  854. $expected = $value;
  855. $result = $check;
  856. return compact('trace', 'expected', 'result');
  857. }
  858. } else {
  859. if ($value == $check) {
  860. if ($types = $compareTypes($value, $check, $trace)) {
  861. return $types;
  862. }
  863. continue;
  864. }
  865. if (!is_array($value)) {
  866. $trace = $newTrace;
  867. return compact('trace', 'expected', 'result');
  868. }
  869. }
  870. $compare = $this->_compare($type, $value, $check, $newTrace);
  871. if ($compare !== true) {
  872. $data[] = $compare;
  873. }
  874. }
  875. if (!empty($data)) {
  876. return $data;
  877. }
  878. } elseif (!is_scalar($result)) {
  879. $data = $this->_compare($type, $result, $expected);
  880. if (!empty($data)) {
  881. return array(
  882. 'trace' => $data['trace'],
  883. 'expected' => $data['result'],
  884. 'result' => $data['expected']
  885. );
  886. }
  887. }
  888. if ((($type === 'identical') ? $expected === $result : $expected == $result)) {
  889. if ($types = $compareTypes($expected, $result, $trace)) {
  890. return $types;
  891. }
  892. return true;
  893. }
  894. return compact('trace', 'expected', 'result');
  895. }
  896. /**
  897. * Returns a basic message for the data returned from `_result()`.
  898. *
  899. * @see lithium\test\Unit::assert()
  900. * @see lithium\test\Unit::_result()
  901. * @param array $data The data to use for creating the message.
  902. * @param string $message The string prepended to the generate message in the current scope.
  903. * @return string
  904. */
  905. protected function _message(&$data = array(), $message = null) {
  906. if (!empty($data[0])) {
  907. foreach ($data as $key => $value) {
  908. $message = (!empty($data[$key][0])) ? $message : null;
  909. $message .= $this->_message($value, $message);
  910. unset($data[$key]);
  911. }
  912. return $message;
  913. }
  914. $defaults = array('trace' => null, 'expected' => null, 'result' => null);
  915. $result = (array) $data + $defaults;
  916. $message = null;
  917. if (!empty($result['trace'])) {
  918. $message = sprintf("trace: %s\n", $result['trace']);
  919. }
  920. if (is_object($result['expected'])) {
  921. $result['expected'] = get_object_vars($result['expected']);
  922. }
  923. if (is_object($result['result'])) {
  924. $result['result'] = get_object_vars($result['result']);
  925. }
  926. return $message . sprintf("expected: %s\nresult: %s\n",
  927. var_export($result['expected'], true),
  928. var_export($result['result'], true)
  929. );
  930. }
  931. /**
  932. * Generates all permutation of an array $items and returns them in a new array.
  933. *
  934. * @param array $items An array of items
  935. * @param array $perms
  936. * @return array
  937. */
  938. protected function _arrayPermute($items, $perms = array()) {
  939. static $permuted;
  940. if (empty($perms)) {
  941. $permuted = array();
  942. }
  943. if (empty($items)) {
  944. $permuted[] = $perms;
  945. return;
  946. }
  947. $numItems = count($items) - 1;
  948. for ($i = $numItems; $i >= 0; --$i) {
  949. $newItems = $items;
  950. $newPerms = $perms;
  951. list($tmp) = array_splice($newItems, $i, 1);
  952. array_unshift($newPerms, $tmp);
  953. $this->_arrayPermute($newItems, $newPerms);
  954. }
  955. return $permuted;
  956. }
  957. /**
  958. * Removes everything from `resources/tmp/tests` directory. Call from inside of your test
  959. * method or `tearDown()`.
  960. *
  961. * Uses `DIRECTORY_SEPARATOR` as `getPathname()` is used in a a direct string comparison.
  962. * The method may contain slashes and backslashes.
  963. *
  964. * If the file to unlink is readonly, it throws a exception (Permission denied) on Windows.
  965. * So, the file is checked before an unlink is tried. (this will make the tests run slower
  966. * but is prefered over a if (!unlink { chmod; unlink }.
  967. * http://stringoftheseus.com/blog/2010/12/22/php-unlink-permisssion-denied-error-on-windows/
  968. *
  969. * @param string $path Path to directory with contents to remove. If first
  970. * character is NOT a slash (`/`) or a Windows drive letter (`C:`)
  971. * prepends `LITHIUM_APP_PATH/resources/tmp/`.
  972. * @return void
  973. */
  974. protected function _cleanUp($path = null) {
  975. $resources = Libraries::get(true, 'resources');
  976. $path = $path ?: $resources . '/tmp/tests';
  977. $path = preg_match('/^\w:|^\//', $path) ? $path : $resources . '/tmp/' . $path;
  978. if (!is_dir($path)) {
  979. return;
  980. }
  981. $dirs = new RecursiveDirectoryIterator($path);
  982. $iterator = new RecursiveIteratorIterator($dirs, RecursiveIteratorIterator::CHILD_FIRST);
  983. foreach ($iterator as $item) {
  984. $empty = $item->getPathname() === $path . DIRECTORY_SEPARATOR . 'empty';
  985. if ($empty || $iterator->isDot()) {
  986. continue;
  987. }
  988. if ($item->isDir()) {
  989. rmdir($item->getPathname());
  990. continue;
  991. }
  992. if (!$item->isWritable()) {
  993. chmod($item->getPathname(), 0777);
  994. }
  995. unlink($item->getPathname());
  996. }
  997. }
  998. /**
  999. * Returns the current results
  1000. *
  1001. * @return array The Results, currently
  1002. */
  1003. public function results() {
  1004. return $this->_results;
  1005. }
  1006. /**
  1007. * Checks for a working internet connection.
  1008. *
  1009. * This method is used to check for a working connection to google.com, both
  1010. * testing for proper DNS resolution and reading the actual URL.
  1011. *
  1012. * @param array $config Override the default URL to check.
  1013. * @return boolean True if a network connection is established, false otherwise.
  1014. */
  1015. protected function _hasNetwork($config = array()) {
  1016. $defaults = array(
  1017. 'scheme' => 'http',
  1018. 'host' => 'google.com'
  1019. );
  1020. $config += $defaults;
  1021. $url = "{$config['scheme']}://{$config['host']}";
  1022. $failed = false;
  1023. set_error_handler(function($errno, $errstr) use (&$failed) {
  1024. $failed = true;
  1025. });
  1026. dns_check_record($config['host'], 'A');
  1027. if ($handle = fopen($url, 'r')) {
  1028. fclose($handle);
  1029. }
  1030. restore_error_handler();
  1031. return !$failed;
  1032. }
  1033. /**
  1034. * Will mark the test `true` if `$count` and `count($arr)` are equal.
  1035. *
  1036. * {{{
  1037. * $this->assertCount(1, array('foo'));
  1038. * }}}
  1039. *
  1040. * {{{
  1041. * $this->assertCount(2, array('foo', 'bar', 'bar'));
  1042. * }}}
  1043. *
  1044. * @param int $expected Expected count
  1045. * @param array $array Result
  1046. * @param string $message optional
  1047. * @return bool
  1048. */
  1049. public function assertCount($expected, $array, $message = '{:message}') {
  1050. return $this->assert($expected === ($result = count($array)), $message, array(
  1051. 'expected' => $expected,
  1052. 'result' => $result,
  1053. ));
  1054. }
  1055. /**
  1056. * Will mark the test `true` if `$count` and `count($arr)` are not equal.
  1057. *
  1058. * {{{
  1059. * $this->assertNotCount(2, array('foo', 'bar', 'bar'));
  1060. * }}}
  1061. *
  1062. * {{{
  1063. * $this->assertNotCount(1, array('foo'));
  1064. * }}}
  1065. *
  1066. * @param int $expected Expected count
  1067. * @param array $array Result
  1068. * @param string $message optional
  1069. * @return bool
  1070. */
  1071. public function assertNotCount($expected, $array, $message = '{:message}') {
  1072. return $this->assert($expected !== ($result = count($array)), $message, array(
  1073. 'expected' => $expected,
  1074. 'result' => $result,
  1075. ));
  1076. }
  1077. /**
  1078. * Will mark the test `true` if `$array` has key `$expected`.
  1079. *
  1080. * {{{
  1081. * $this->assertArrayHasKey('foo', array('bar' => 'baz'));
  1082. * }}}
  1083. *
  1084. * {{{
  1085. * $this->assertArrayHasKey('bar', array('bar' => 'baz'));
  1086. * }}}
  1087. *
  1088. * @param string $key Key you are looking for
  1089. * @param array $array Array to search through
  1090. * @param string $message optional
  1091. * @return bool
  1092. */
  1093. public function assertArrayHasKey($key, $array, $message = '{:message}') {
  1094. return $this->assert(isset($array[$key]), $message, array(
  1095. 'expected' => $key,
  1096. 'result' => $array
  1097. ));
  1098. }
  1099. /**
  1100. * Will mark the test `true` if `$array` does not have key `$expected`.
  1101. *
  1102. * {{{
  1103. * $this->assertArrayNotHasKey('foo', array('bar' => 'baz'));
  1104. * }}}
  1105. *
  1106. * {{{
  1107. * $this->assertArrayNotHasKey('bar', array('bar' => 'baz'));
  1108. * }}}
  1109. *
  1110. * @param int $key Expected count
  1111. * @param array $array Array to search through
  1112. * @param string $message optional
  1113. * @return bool
  1114. */
  1115. public function assertArrayNotHasKey($key, $array, $message = '{:message}') {
  1116. return $this->assert(!isset($array[$key]), $message, array(
  1117. 'expected' => $key,
  1118. 'result' => $array
  1119. ));
  1120. }
  1121. /**
  1122. * Will mark the test `true` if `$class` has an attribute `$attributeName`.
  1123. *
  1124. * {{{
  1125. * $this->assertClassHasAttribute('name', '\ReflectionClass');
  1126. * }}}
  1127. *
  1128. * {{{
  1129. * $this->assertClassHasAttribute('__construct', '\ReflectionClass');
  1130. * }}}
  1131. *
  1132. * @see lithium\test\Unit::assertObjectHasAttribute()
  1133. * @throws InvalidArgumentException When $object is not an object
  1134. * @throws ReflectionException If the given class does not exist
  1135. * @param string $attributeName Attribute you wish to look for
  1136. * @param string $class Class name
  1137. * @param string $message optional
  1138. * @return bool
  1139. */
  1140. public function assertClassHasAttribute($attributeName, $class, $message = '{:message}') {
  1141. if (!is_string($class)) {
  1142. throw new InvalidArgumentException('Argument $class must be a string');
  1143. }
  1144. $object = new ReflectionClass($class);
  1145. return $this->assert($object->hasProperty($attributeName), $message, array(
  1146. 'expected' => $attributeName,
  1147. 'result' => $object->getProperties()
  1148. ));
  1149. }
  1150. /**
  1151. * Will mark the test `true` if `$class` has an attribute `$attributeName`.
  1152. *
  1153. * {{{
  1154. * $this->assertClassNotHasAttribute('__construct', '\ReflectionClass');
  1155. * }}}
  1156. *
  1157. * {{{
  1158. * $this->assertClassNotHasAttribute('name', '\ReflectionClass');
  1159. * }}}
  1160. *
  1161. * @see lithium\test\Unit::assertObjectNotHasAttribute()
  1162. * @throws InvalidArgumentException When $object is not an object
  1163. * @throws ReflectionException If the given class does not exist
  1164. * @param string $attributeName Attribute you wish to look for
  1165. * @param string $class Class name
  1166. * @param string $message optional
  1167. * @return bool
  1168. */
  1169. public function assertClassNotHasAttribute($attributeName, $class, $message = '{:message}') {
  1170. if (!is_string($class)) {
  1171. throw new InvalidArgumentException('Argument $class must be a string.');
  1172. }
  1173. $object = new ReflectionClass($class);
  1174. return $this->assert(!$object->hasProperty($attributeName), $message, array(
  1175. 'expected' => $attributeName,
  1176. 'result' => $object->getProperties()
  1177. ));
  1178. }
  1179. /**
  1180. * Will mark the test `true` if `$class` has a static property `$attributeName`.
  1181. *
  1182. * {{{
  1183. * $this->assertClassHasStaticAttribute('foobar', '\lithium\core\StaticObject');
  1184. * }}}
  1185. *
  1186. * {{{
  1187. * $this->assertClassHasStaticAttribute('_methodFilters', '\lithium\core\StaticObject');
  1188. * }}}
  1189. *
  1190. * @throws ReflectionException If the given class does not exist
  1191. * @param string $attributeName Attribute you wish to look for
  1192. * @param string|object $class Class name or object
  1193. * @param string $message optional
  1194. * @return bool
  1195. */
  1196. public function assertClassHasStaticAttribute($attributeName, $class, $message = '{:message}') {
  1197. $object = new ReflectionClass($class);
  1198. if ($object->hasProperty($attributeName)) {
  1199. $attribute = $object->getProperty($attributeName);
  1200. return $this->assert($attribute->isStatic(), $message, array(
  1201. 'expected' => $attributeName,
  1202. 'result' => $object->getProperties()
  1203. ));
  1204. }
  1205. return $this->assert(false, $message, array(
  1206. 'expected' => $attributeName,
  1207. 'result' => $object->getProperties()
  1208. ));
  1209. }
  1210. /**
  1211. * Will mark the test `true` if `$class` does not have a static property `$attrName`.
  1212. *
  1213. * {{{
  1214. * $this->assertClassNotHasStaticAttribute('_methodFilters', '\lithium\core\StaticObject');
  1215. * }}}
  1216. *
  1217. * {{{
  1218. * $this->assertClassNotHasStaticAttribute('foobar', '\lithium\core\StaticObject')
  1219. * }}}
  1220. *
  1221. * @throws ReflectionException If the given class does not exist
  1222. * @param string $attrName Attribute you wish to look for
  1223. * @param string|object $class Class name or object
  1224. * @param string $message optional
  1225. * @return bool
  1226. */
  1227. public function assertClassNotHasStaticAttribute($attrName, $class, $message = '{:message}') {
  1228. $object = new ReflectionClass($class);
  1229. if ($object->hasProperty($attrName)) {
  1230. $attribute = $object->getProperty($attrName);
  1231. return $this->assert(!$attribute->isStatic(), $message, array(
  1232. 'expected' => $attrName,
  1233. 'result' => $object->getProperties()
  1234. ));
  1235. }
  1236. return $this->assert(true, $message, array(
  1237. 'expected' => $attrName,
  1238. 'result' => $object->getProperties()
  1239. ));
  1240. }
  1241. /**
  1242. * Will mark the test `true` if `$haystack` contains `$needle` as a value.
  1243. *
  1244. * {{{
  1245. * $this->assertContains('foo', array('foo', 'bar', 'baz'));
  1246. * }}}
  1247. *
  1248. * {{{
  1249. * $this->assertContains(4, array(1,2,3));
  1250. * }}}
  1251. *
  1252. * @param string $needle The needle you are looking for
  1253. * @param mixed $haystack An array, iterable object, or string
  1254. * @param string $message optional
  1255. * @return bool
  1256. */
  1257. public function assertContains($needle, $haystack, $message = '{:message}') {
  1258. if (is_string($haystack)) {
  1259. return $this->assert(strpos($haystack, $needle) !== false, $message, array(
  1260. 'expected' => $needle,
  1261. 'result' => $haystack
  1262. ));
  1263. }
  1264. foreach ($haystack as $key => $value) {
  1265. if ($value === $needle) {
  1266. return $this->assert(true, $message, array(
  1267. 'expected' => $needle,
  1268. 'result' => $haystack
  1269. ));
  1270. }
  1271. }
  1272. return $this->assert(false, $message, array(
  1273. 'expected' => $needle,
  1274. 'result' => $haystack
  1275. ));
  1276. }
  1277. /**
  1278. * Will mark the test `true` if `$haystack` does not contain `$needle` as a value.
  1279. *
  1280. * {{{
  1281. * $this->assertNotContains(4, array(1,2,3));
  1282. * }}}
  1283. *
  1284. * {{{
  1285. * $this->assertNotContains('foo', array('foo', 'bar', 'baz'));
  1286. * }}}
  1287. *
  1288. * @param string $needle Needle you are looking for
  1289. * @param mixed $haystack Array, iterable object, or string
  1290. * @param string $message optional
  1291. * @return bool
  1292. */
  1293. public function assertNotContains($needle, $haystack, $message = '{:message}') {
  1294. if (is_string($haystack)) {
  1295. return $this->assert(strpos($haystack, $needle) === false, $message, array(
  1296. 'expected' => $needle,
  1297. 'result' => $haystack
  1298. ));
  1299. }
  1300. foreach ($haystack as $key => $value) {
  1301. if ($value === $needle) {
  1302. return $this->assert(false, $message, array(
  1303. 'expected' => $needle,
  1304. 'result' => $haystack
  1305. ));
  1306. }
  1307. }
  1308. return $this->assert(true, $message, array(
  1309. 'expected' => $needle,
  1310. 'result' => $haystack
  1311. ));
  1312. }
  1313. /**
  1314. * Will mark the test `true` if `$haystack` contains only items of `$type`.
  1315. *
  1316. * {{{
  1317. * $this->assertContainsOnly('int', array(1,2,3));
  1318. * }}}
  1319. *
  1320. * {{{
  1321. * $this->assertContainsOnly('int', array('foo', 'bar', 'baz'));
  1322. * }}}
  1323. *
  1324. * @param string $type Data type to check for
  1325. * @param mixed $haystack Array or iterable object
  1326. * @param string $message optional
  1327. * @return bool
  1328. */
  1329. public function assertContainsOnly($type, $haystack, $message = '{:message}') {
  1330. $method = self::$_internalTypes[$type];
  1331. foreach ($haystack as $key => $value) {
  1332. if (!$method($value)) {
  1333. return $this->assert(false, $message, array(
  1334. 'expected' => $type,
  1335. 'result' => $haystack
  1336. ));
  1337. }
  1338. }
  1339. return $this->assert(true, $message, array(
  1340. 'expected' => $type,
  1341. 'result' => $haystack
  1342. ));
  1343. }
  1344. /**
  1345. * Will mark the test `true` if `$haystack` does not have any of `$type`.
  1346. *
  1347. * {{{
  1348. * $this->assertNotContainsOnly('int', array('foo', 'bar', 'baz'));
  1349. * }}}
  1350. *
  1351. * {{{
  1352. * $this->assertNotContainsOnly('int', array(1,2,3));
  1353. * }}}
  1354. *
  1355. * @param string $type Data type to check for
  1356. * @param mixed $haystack Array or iterable object
  1357. * @param string $message optional
  1358. * @return bool
  1359. */
  1360. public function assertNotContainsOnly($type, $haystack, $message = '{:message}') {
  1361. $method = self::$_internalTypes[$type];
  1362. foreach ($haystack as $key => $value) {
  1363. if (!$method($value)) {
  1364. return $this->assert(true, $message, array(
  1365. 'expected' => $type,
  1366. 'result' => $haystack
  1367. ));
  1368. }
  1369. }
  1370. return $this->assert(false, $message, array(
  1371. 'expected' => $type,
  1372. 'result' => $haystack
  1373. ));
  1374. }
  1375. /**
  1376. * Will mark the test `true` if `$haystack` contains only items of `$type`.
  1377. *
  1378. * {{{
  1379. * $this->assertContainsOnlyInstancesOf('stdClass', array(new \stdClass));
  1380. * }}}
  1381. *
  1382. * {{{
  1383. * $this->assertContainsOnlyInstancesOf('stdClass', array(new \lithium\test\Unit));
  1384. * }}}
  1385. *
  1386. * @param string $class Fully namespaced class name
  1387. * @param mixed $haystack Array or iterable object
  1388. * @param string $message optional
  1389. * @return bool
  1390. */
  1391. public function assertContainsOnlyInstancesOf($class, $haystack, $message = '{:message}') {
  1392. $result = array();
  1393. foreach ($haystack as $key => &$value) {
  1394. if (!is_a($value, $class)) {
  1395. $result[$key] =& $value;
  1396. break;
  1397. }
  1398. }
  1399. return $this->assert(empty($result), $message, array(
  1400. 'expected' => $class,
  1401. 'result' => $result
  1402. ));
  1403. }
  1404. /**
  1405. * Will mark the test `true` if `$actual` is empty.
  1406. *
  1407. * {{{
  1408. * $this->assertEmpty(1);
  1409. * }}}
  1410. *
  1411. * {{{
  1412. * $this->assertEmpty(array());
  1413. * }}}
  1414. *
  1415. * @param string $actual Variable to check
  1416. * @param string $message optional
  1417. * @return bool
  1418. */
  1419. public function assertEmpty($actual, $message = '{:message}') {
  1420. return $this->assert(empty($actual), $message, array(
  1421. 'expected' => $actual,
  1422. 'result' => empty($actual)
  1423. ));
  1424. }
  1425. /**
  1426. * Will mark the test `true` if `$actual` is not empty.
  1427. *
  1428. * {{{
  1429. * $this->assertNotEmpty(array());
  1430. * }}}
  1431. *
  1432. * {{{
  1433. * $this->assertNotEmpty(1);
  1434. * }}}
  1435. *
  1436. * @param string $actual Variable to check
  1437. * @param string $message optional
  1438. * @return bool
  1439. */
  1440. public function assertNotEmpty($actual, $message = '{:message}') {
  1441. return $this->assert(!empty($actual), $message, array(
  1442. 'expected' => $actual,
  1443. 'result' => !empty($actual)
  1444. ));
  1445. }
  1446. /**
  1447. * Will mark the test `true` if the contents of `$expected` are equal to the
  1448. * contents of `$actual`.
  1449. *
  1450. * {{{
  1451. * $file1 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md';
  1452. * $file2 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md.copy';
  1453. * $this->assertFileEquals($file1, $file2);
  1454. * }}}
  1455. *
  1456. * {{{
  1457. * $file1 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md';
  1458. * $file2 = LITHIUM_APP_PATH . '/tests/mocks/md/file_2.md';
  1459. * $this->assertFileEquals($file1, $file2);
  1460. * }}}
  1461. *
  1462. * @param string $expected Path to the expected file
  1463. * @param string $actual Path to the actual file
  1464. * @param string $message optional
  1465. * @return bool
  1466. */
  1467. public function assertFileEquals($expected, $actual, $message = '{:message}') {
  1468. $expected = md5_file($expected);
  1469. $result = md5_file($actual);
  1470. return $this->assert($expected === $result, $message, compact('expected', 'result'));
  1471. }
  1472. /**
  1473. * Will mark the test `true` if the contents of `$expected` are not equal to
  1474. * the contents of `$actual`.
  1475. *
  1476. * {{{
  1477. * $file1 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md';
  1478. * $file2 = LITHIUM_APP_PATH . '/tests/mocks/md/file_2.md';
  1479. * $this->assertFileNotEquals($file1, $file2);
  1480. * }}}
  1481. *
  1482. * {{{
  1483. * $file1 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md';
  1484. * $file2 = LITHIUM_APP_PATH . '/tests/mocks/md/file_1.md.copy';
  1485. * $this->assertFileNotEquals($file1, $file2);
  1486. * }}}
  1487. *
  1488. * @param string $expected Path to the expected file
  1489. * @param string $actual Path to the actual file
  1490. * @param string $message optional
  1491. * @return bool
  1492. */
  1493. public function assertFileNotEquals($expected, $actual, $message = '{:message}') {
  1494. $expected = md5_file($expected);
  1495. $result = md5_file($actual);
  1496. return $this->assert($expected !== $result, $message, compact('expected', 'result'));
  1497. }
  1498. /**
  1499. * Will mark the test `true` if the file `$actual` exists.
  1500. *
  1501. * {{{
  1502. * $this->assertFileExists(LITHIUM_APP_PATH . '/readme.md');
  1503. * }}}
  1504. *
  1505. * {{{
  1506. * $this->assertFileExists(LITHIUM_APP_PATH . '/does/not/exist.txt');
  1507. * }}}
  1508. *
  1509. * @param string $actual Path to the file you are asserting
  1510. * @param string $message optional
  1511. * @return bool
  1512. */
  1513. public function assertFileExists($actual, $message = '{:message}') {
  1514. return $this->assert(file_exists($actual), $message, array(
  1515. 'expected' => $actual,
  1516. 'result' => file_exists($actual)
  1517. ));
  1518. }
  1519. /**
  1520. * Will mark the test `true` if the file `$actual` does not exist.
  1521. *
  1522. * {{{
  1523. * $this->assertFileExists(LITHIUM_APP_PATH . '/does/not/exist.txt');
  1524. * }}}
  1525. *
  1526. * {{{
  1527. * $this->assertFileExists(LITHIUM_APP_PATH . '/readme.md');
  1528. * }}}
  1529. *
  1530. * @param string $actual Path to the file you are asserting
  1531. * @param string $message optional
  1532. * @return bool
  1533. */
  1534. public function assertFileNotExists($actual, $message = '{:message}') {
  1535. return $this->assert(!file_exists($actual), $message, array(
  1536. 'expected' => $actual,
  1537. 'result' => !file_exists($actual)
  1538. ));
  1539. }
  1540. /**
  1541. * Will mark the test `true` if `$expected` greater than `$actual`.
  1542. *
  1543. * {{{
  1544. * $this->assertGreaterThan(5, 3);
  1545. * }}}
  1546. *
  1547. * {{{
  1548. * $this->assertGreaterThan(3, 5);
  1549. * }}}
  1550. *
  1551. * @param float|int $expected
  1552. * @param float|int $actual
  1553. * @param string $message optional
  1554. * @return bool
  1555. */
  1556. public function assertGreaterThan($expected, $actual, $message = '{:message}') {
  1557. return $this->assert($expected > $actual, $message, array(
  1558. 'expected' => $expected,
  1559. 'result' => $actual
  1560. ));
  1561. }
  1562. /**
  1563. * Will mark the test `true` if `$expected` great than or equal to `$actual`.
  1564. *
  1565. * {{{
  1566. * $this->assertGreaterThanOrEqual(5, 5);
  1567. * }}}
  1568. *
  1569. * {{{
  1570. * $this->assertGreaterThanOrEqual(3, 5);
  1571. * }}}
  1572. *
  1573. * @param float|int $expected
  1574. * @param float|int $actual
  1575. * @param string $message optional
  1576. * @return bool
  1577. */
  1578. public function assertGreaterThanOrEqual($expected, $actual, $message = '{:message}') {
  1579. return $this->assert($expected >= $actual, $message, array(
  1580. 'expected' => $expected,
  1581. 'result' => $actual
  1582. ));
  1583. }
  1584. /**
  1585. * Will mark the test `true` if `$expected` less than `$actual`.
  1586. *
  1587. * {{{
  1588. * $this->assertLessThan(3, 5);
  1589. * }}}
  1590. *
  1591. * {{{
  1592. * $this->assertLessThan(5, 3);
  1593. * }}}
  1594. *
  1595. * @param float|int $expected
  1596. * @param float|int $actual
  1597. * @param string $message optional
  1598. * @return bool
  1599. */
  1600. public function assertLessThan($expected, $actual, $message = '{:message}') {
  1601. return $this->assert($expected < $actual, $message, array(
  1602. 'expected' => $expected,
  1603. 'result' => $actual
  1604. ));
  1605. }
  1606. /**
  1607. * Will mark the test `true` if `$expected` is less than or equal to `$actual`.
  1608. *
  1609. * {{{
  1610. * $this->assertLessThanOrEqual(5, 5);
  1611. * }}}
  1612. *
  1613. * {{{
  1614. * $this->assertLessThanOrEqual(5, 3);
  1615. * }}}
  1616. *
  1617. * @param float|int $expected
  1618. * @param float|int $actual
  1619. * @param string $message optional
  1620. * @return bool
  1621. */
  1622. public function assertLessThanOrEqual($expected, $actual, $message = '{:message}') {
  1623. return $this->assert($expected <= $actual, $message, array(
  1624. 'expected' => $expected,
  1625. 'result' => $actual
  1626. ));
  1627. }
  1628. /**
  1629. * Will mark the test `true` if `$actual` is a `$expected`.
  1630. *
  1631. * {{{
  1632. * $this->assertInstanceOf('stdClass', new stdClass);
  1633. * }}}
  1634. *
  1635. * {{{
  1636. * $this->assertInstanceOf('ReflectionClass', new stdClass);
  1637. * }}}
  1638. *
  1639. * @param string $expected Fully namespaced expected class
  1640. * @param object $actual Object you are testing
  1641. * @param string $message optional
  1642. * @return bool
  1643. */
  1644. public function assertInstanceOf($expected, $actual, $message = '{:message}') {
  1645. return $this->assert(is_a($actual, $expected), $message, array(
  1646. 'expected' => $expected,
  1647. 'result' => get_class($actual)
  1648. ));
  1649. }
  1650. /**
  1651. * Will mark the test `true` if `$actual` is not a `$expected`.
  1652. *
  1653. * {{{
  1654. * $this->assertNotInstanceOf('ReflectionClass', new stdClass);
  1655. * }}}
  1656. *
  1657. * {{{
  1658. * $this->assertNotInstanceOf('stdClass', new stdClass);
  1659. * }}}
  1660. *
  1661. * @param string $expected Fully namespaced expected class
  1662. * @param object $actual Object you are testing
  1663. * @param string $message optional
  1664. * @return bool
  1665. */
  1666. public function assertNotInstanceOf($expected, $actual, $message = '{:message}') {
  1667. return $this->assert(!is_a($actual, $expected), $message, array(
  1668. 'expected' => $expected,
  1669. 'result' => get_class($actual)
  1670. ));
  1671. }
  1672. /**
  1673. * Will mark the test `true` if `$actual` if of type $expected.
  1674. *
  1675. * {{{
  1676. * $this->assertInternalType('string', 'foobar');
  1677. * }}}
  1678. *
  1679. * {{{
  1680. * $this->assertInternalType('int', 'foobar');
  1681. * }}}
  1682. *
  1683. * @param string $expected Internal data type
  1684. * @param object $actual Object you are testing
  1685. * @param string $message optional
  1686. * @return bool
  1687. */
  1688. public function assertInternalType($expected, $actual, $message = '{:message}') {
  1689. $method = self::$_internalTypes[$expected];
  1690. return $this->assert($method($actual), $message, array(
  1691. 'expected' => $expected,
  1692. 'result' => gettype($actual)
  1693. ));
  1694. }
  1695. /**
  1696. * Will mark the test `true` if `$actual` if not of type $expected.
  1697. *
  1698. * {{{
  1699. * $this->assertNotInternalType('int', 'foobar');
  1700. * }}}
  1701. *
  1702. * {{{
  1703. * $this->assertNotInternalType('string', 'foobar');
  1704. * }}}
  1705. *
  1706. * @param string $expected Internal data type
  1707. * @param object $actual Object you are testing
  1708. * @param string $message optional
  1709. * @return bool
  1710. */
  1711. public function assertNotInternalType($expected, $actual, $message = '{:message}') {
  1712. $method = self::$_internalTypes[$expected];
  1713. return $this->assert(!$method($actual), $message, array(
  1714. 'expected' => $expected,
  1715. 'result' => gettype($actual)
  1716. ));
  1717. }
  1718. /**
  1719. * Will mark the test as true if `$actual` is not null.
  1720. *
  1721. * {{{
  1722. * $this->assertNotNull(1);
  1723. * }}}
  1724. *
  1725. * {{{
  1726. * $this->assertNotNull(null);
  1727. * }}}
  1728. *
  1729. * @param object $actual Variable you are testing
  1730. * @param string $message optional
  1731. * @return bool
  1732. */
  1733. public function assertNotNull($actual, $message = '{:message}') {
  1734. return $this->assert(!is_null($actual), $message, array(
  1735. 'expected' => null,
  1736. 'actual' => gettype($actual)
  1737. ));
  1738. }
  1739. /**
  1740. * Will mark the test `true` if `$object` has an attribute `$attributeName`.
  1741. *
  1742. * {{{
  1743. * $this->assertObjectHasAttribute('name', '\ReflectionClass');
  1744. * }}}
  1745. *
  1746. * {{{
  1747. * $this->assertObjectHasAttribute('__construct', '\ReflectionClass');
  1748. * }}}
  1749. *
  1750. * @see lithium\test\Unit::assertClassHasAttribute()
  1751. * @throws InvalidArgumentException When $object is not an object
  1752. * @param string $attributeName Attribute you wish to look for
  1753. * @param string $object Object to assert
  1754. * @param string $message optional
  1755. * @return bool
  1756. */
  1757. public function assertObjectHasAttribute($attributeName, $object, $message = '{:message}') {
  1758. if (!is_object($object)) {
  1759. throw new InvalidArgumentException('Second argument $object must be an object.');
  1760. }
  1761. $object = new ReflectionClass($object);
  1762. return $this->assert($object->hasProperty($attributeName), $message, array(
  1763. 'expected' => $attributeName,
  1764. 'result' => $object->getProperties()
  1765. ));
  1766. }
  1767. /**
  1768. * Will mark the test `true` if `$object` has an attribute `$attributeName`.
  1769. *
  1770. * {{{
  1771. * $this->assertObjectNotHasAttribute('__construct', '\ReflectionClass');
  1772. * }}}
  1773. *
  1774. * {{{
  1775. * $this->assertObjectNotHasAttribute('name', '\ReflectionClass');
  1776. * }}}
  1777. *
  1778. * @see lithium\test\Unit::assertClassHasNotAttribute()
  1779. * @throws InvalidArgumentException When $object is not an object
  1780. * @param string $attributeName Attribute you wish to look for
  1781. * @param string $object Object to assert
  1782. * @param string $message optional
  1783. * @return bool
  1784. */
  1785. public function assertObjectNotHasAttribute($attributeName, $object, $message = '{:message}') {
  1786. if (!is_object($object)) {
  1787. throw new InvalidArgumentException('Second argument $object must be an object');
  1788. }
  1789. $object = new ReflectionClass($object);
  1790. return $this->assert(!$object->hasProperty($attributeName), $message, array(
  1791. 'expected' => $attributeName,
  1792. 'result' => $object->getProperties()
  1793. ));
  1794. }
  1795. /**
  1796. * Will mark the test `true` if `$actual` matches $expected using `preg_match`.
  1797. *
  1798. * {{{
  1799. * $this->assertRegExp('/^foo/', 'foobar');
  1800. * }}}
  1801. *
  1802. * {{{
  1803. * $this->assertRegExp('/^foobar/', 'bar');
  1804. * }}}
  1805. *
  1806. * @param string $expected Regex to match against $actual
  1807. * @param string $actual String to be matched upon
  1808. * @param string $message optional
  1809. * @return bool
  1810. */
  1811. public function assertRegExp($expected, $actual, $message = '{:message}') {
  1812. return $this->assert(preg_match($expected, $actual, $matches) === 1, $message, array(
  1813. 'expected' => $expected,
  1814. 'result' => $matches
  1815. ));
  1816. }
  1817. /**
  1818. * Will mark the test `true` if `$actual` does not match $expected using `preg_match`.
  1819. *
  1820. * {{{
  1821. * $this->assertNotRegExp('/^foobar/', 'bar');
  1822. * }}}
  1823. *
  1824. * {{{
  1825. * $this->assertNotRegExp('/^foo/', 'foobar');
  1826. * }}}
  1827. *
  1828. * @param string $expected Regex to match against $actual
  1829. * @param string $actual String to be matched upon
  1830. * @param string $message optional
  1831. * @return bool
  1832. */
  1833. public function assertNotRegExp($expected, $actual, $message = '{:message}') {
  1834. return $this->assert(preg_match($expected, $actual, $matches) === 0, $message, array(
  1835. 'expected' => $expected,
  1836. 'result' => $matches
  1837. ));
  1838. }
  1839. /**
  1840. * Will mark the test `true` if $actual matches $expected using `sprintf` format.
  1841. *
  1842. * {{{
  1843. * $this->assertStringMatchesFormat('%d', '10')
  1844. * }}}
  1845. *
  1846. * {{{
  1847. * $this->assertStringMatchesFormat('%d', '10.555')
  1848. * }}}
  1849. *
  1850. * @link http://php.net/sprintf
  1851. * @link http://php.net/sscanf
  1852. * @param string $expected Expected format using sscanf's format
  1853. * @param string $actual Value to compare against
  1854. * @param string $message optional
  1855. * @return bool
  1856. */
  1857. public function assertStringMatchesFormat($expected, $actual, $message = '{:message}') {
  1858. $result = sscanf($actual, $expected);
  1859. return $this->assert($result[0] == $actual, $message, compact('expected', 'result'));
  1860. }
  1861. /**
  1862. * Will mark the test `true` if $actual doesn't match $expected using `sprintf` format.
  1863. *
  1864. * {{{
  1865. * $this->assertStringNotMatchesFormat('%d', '10.555')
  1866. * }}}
  1867. *
  1868. * {{{
  1869. * $this->assertStringNotMatchesFormat('%d', '10')
  1870. * }}}
  1871. *
  1872. * @link http://php.net/sprintf
  1873. * @link http://php.net/sscanf
  1874. * @param string $expected Expected format using sscanf's format
  1875. * @param string $actual Value to test against
  1876. * @param string $message optional
  1877. * @return bool
  1878. */
  1879. public function assertStringNotMatchesFormat($expected, $actual, $message = '{:message}') {
  1880. $result = sscanf($actual, $expected);
  1881. return $this->assert($result[0] != $actual, $message, compact('expected', 'result'));
  1882. }
  1883. /**
  1884. * Will mark the test `true` if $actual ends with `$expected`.
  1885. *
  1886. * {{{
  1887. * $this->assertStringEndsWith('bar', 'foobar');
  1888. * }}}
  1889. *
  1890. * {{{
  1891. * $this->assertStringEndsWith('foo', 'foobar');
  1892. * }}}
  1893. *
  1894. * @param string $expected The suffix to check for
  1895. * @param string $actual Value to test against
  1896. * @param string $message optional
  1897. * @return bool
  1898. */
  1899. public function assertStringEndsWith($expected, $actual, $message = '{:message}') {
  1900. return $this->assert(preg_match("/$expected$/", $actual, $matches) === 1, $message, array(
  1901. 'expected' => $expected,
  1902. 'result' => $actual
  1903. ));
  1904. }
  1905. /**
  1906. * Will mark the test `true` if $actual starts with `$expected`.
  1907. *
  1908. * {{{
  1909. * $this->assertStringStartsWith('foo', 'foobar');
  1910. * }}}
  1911. *
  1912. * {{{
  1913. * $this->assertStringStartsWith('bar', 'foobar');
  1914. * }}}
  1915. *
  1916. * @param string $expected Prefix to check for
  1917. * @param string $actual Value to test against
  1918. * @param string $message optional
  1919. * @return bool
  1920. */
  1921. public function assertStringStartsWith($expected, $actual, $message = '{:message}') {
  1922. return $this->assert(preg_match("/^$expected/", $actual, $matches) === 1, $message, array(
  1923. 'expected' => $expected,
  1924. 'result' => $actual
  1925. ));
  1926. }
  1927. }
  1928. ?>