1: <?php
2: /**
3: * CakeTestCase file
4: *
5: * PHP 5
6: *
7: * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
8: * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
9: *
10: * Licensed under The MIT License
11: * Redistributions of files must retain the above copyright notice
12: *
13: * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
14: * @link http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
15: * @package Cake.TestSuite
16: * @since CakePHP(tm) v 1.2.0.4667
17: * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
18: */
19: App::uses('CakeFixtureManager', 'TestSuite/Fixture');
20: App::uses('CakeTestFixture', 'TestSuite/Fixture');
21:
22: /**
23: * CakeTestCase class
24: *
25: * @package Cake.TestSuite
26: */
27: abstract class CakeTestCase extends PHPUnit_Framework_TestCase {
28:
29: /**
30: * The class responsible for managing the creation, loading and removing of fixtures
31: *
32: * @var CakeFixtureManager
33: */
34: public $fixtureManager = null;
35:
36: /**
37: * By default, all fixtures attached to this class will be truncated and reloaded after each test.
38: * Set this to false to handle manually
39: *
40: * @var array
41: */
42: public $autoFixtures = true;
43:
44: /**
45: * Set this to false to avoid tables to be dropped if they already exist
46: *
47: * @var boolean
48: */
49: public $dropTables = true;
50:
51: /**
52: * Configure values to restore at end of test.
53: *
54: * @var array
55: */
56: protected $_configure = array();
57:
58: /**
59: * Path settings to restore at the end of the test.
60: *
61: * @var array
62: */
63: protected $_pathRestore = array();
64:
65: /**
66: * Runs the test case and collects the results in a TestResult object.
67: * If no TestResult object is passed a new one will be created.
68: * This method is run for each test method in this class
69: *
70: * @param PHPUnit_Framework_TestResult $result
71: * @return PHPUnit_Framework_TestResult
72: * @throws InvalidArgumentException
73: */
74: public function run(PHPUnit_Framework_TestResult $result = NULL) {
75: if (!empty($this->fixtureManager)) {
76: $this->fixtureManager->load($this);
77: }
78: $result = parent::run($result);
79: if (!empty($this->fixtureManager)) {
80: $this->fixtureManager->unload($this);
81: }
82: return $result;
83: }
84:
85: /**
86: * Called when a test case method is about to start (to be overridden when needed.)
87: *
88: * @param string $method Test method about to get executed.
89: * @return void
90: */
91: public function startTest($method) {
92: }
93:
94: /**
95: * Called when a test case method has been executed (to be overridden when needed.)
96: *
97: * @param string $method Test method about that was executed.
98: * @return void
99: */
100: public function endTest($method) {
101: }
102:
103: /**
104: * Overrides SimpleTestCase::skipIf to provide a boolean return value
105: *
106: * @param boolean $shouldSkip
107: * @param string $message
108: * @return boolean
109: */
110: public function skipIf($shouldSkip, $message = '') {
111: if ($shouldSkip) {
112: $this->markTestSkipped($message);
113: }
114: return $shouldSkip;
115: }
116:
117: /**
118: * Setup the test case, backup the static object values so they can be restored.
119: * Specifically backs up the contents of Configure and paths in App if they have
120: * not already been backed up.
121: *
122: * @return void
123: */
124: public function setUp() {
125: parent::setUp();
126:
127: if (empty($this->_configure)) {
128: $this->_configure = Configure::read();
129: }
130: if (empty($this->_pathRestore)) {
131: $this->_pathRestore = App::paths();
132: }
133: if (class_exists('Router', false)) {
134: Router::reload();
135: }
136: }
137:
138: /**
139: * teardown any static object changes and restore them.
140: *
141: * @return void
142: */
143: public function tearDown() {
144: parent::tearDown();
145: App::build($this->_pathRestore, App::RESET);
146: if (class_exists('ClassRegistry', false)) {
147: ClassRegistry::flush();
148: }
149: Configure::write($this->_configure);
150: if (isset($_GET['debug']) && $_GET['debug']) {
151: ob_flush();
152: }
153: }
154:
155: /**
156: * Announces the start of a test.
157: *
158: * @param string $method Test method just started.
159: * @return void
160: */
161: protected function assertPreConditions() {
162: parent::assertPreConditions();
163: $this->startTest($this->getName());
164: }
165:
166: /**
167: * Announces the end of a test.
168: *
169: * @param string $method Test method just finished.
170: * @return void
171: */
172: protected function assertPostConditions() {
173: parent::assertPostConditions();
174: $this->endTest($this->getName());
175: }
176:
177: /**
178: * Chooses which fixtures to load for a given test
179: *
180: * @param string $fixture Each parameter is a model name that corresponds to a
181: * fixture, i.e. 'Post', 'Author', etc.
182: * @return void
183: * @see CakeTestCase::$autoFixtures
184: */
185: public function loadFixtures() {
186: if (empty($this->fixtureManager)) {
187: throw new Exception(__d('cake_dev', 'No fixture manager to load the test fixture'));
188: }
189: $args = func_get_args();
190: foreach ($args as $class) {
191: $this->fixtureManager->loadSingle($class);
192: }
193: }
194:
195: /**
196: * Takes an array $expected and generates a regex from it to match the provided $string.
197: * Samples for $expected:
198: *
199: * Checks for an input tag with a name attribute (contains any non-empty value) and an id
200: * attribute that contains 'my-input':
201: * array('input' => array('name', 'id' => 'my-input'))
202: *
203: * Checks for two p elements with some text in them:
204: * array(
205: * array('p' => true),
206: * 'textA',
207: * '/p',
208: * array('p' => true),
209: * 'textB',
210: * '/p'
211: * )
212: *
213: * You can also specify a pattern expression as part of the attribute values, or the tag
214: * being defined, if you prepend the value with preg: and enclose it with slashes, like so:
215: * array(
216: * array('input' => array('name', 'id' => 'preg:/FieldName\d+/')),
217: * 'preg:/My\s+field/'
218: * )
219: *
220: * Important: This function is very forgiving about whitespace and also accepts any
221: * permutation of attribute order. It will also allow whitespace between specified tags.
222: *
223: * @param string $string An HTML/XHTML/XML string
224: * @param array $expected An array, see above
225: * @param string $message SimpleTest failure output string
226: * @return boolean
227: */
228: public function assertTags($string, $expected, $fullDebug = false) {
229: $regex = array();
230: $normalized = array();
231: foreach ((array) $expected as $key => $val) {
232: if (!is_numeric($key)) {
233: $normalized[] = array($key => $val);
234: } else {
235: $normalized[] = $val;
236: }
237: }
238: $i = 0;
239: foreach ($normalized as $tags) {
240: if (!is_array($tags)) {
241: $tags = (string)$tags;
242: }
243: $i++;
244: if (is_string($tags) && $tags{0} == '<') {
245: $tags = array(substr($tags, 1) => array());
246: } elseif (is_string($tags)) {
247: $tagsTrimmed = preg_replace('/\s+/m', '', $tags);
248:
249: if (preg_match('/^\*?\//', $tags, $match) && $tagsTrimmed !== '//') {
250: $prefix = array(null, null);
251:
252: if ($match[0] == '*/') {
253: $prefix = array('Anything, ', '.*?');
254: }
255: $regex[] = array(
256: sprintf('%sClose %s tag', $prefix[0], substr($tags, strlen($match[0]))),
257: sprintf('%s<[\s]*\/[\s]*%s[\s]*>[\n\r]*', $prefix[1], substr($tags, strlen($match[0]))),
258: $i,
259: );
260: continue;
261: }
262: if (!empty($tags) && preg_match('/^preg\:\/(.+)\/$/i', $tags, $matches)) {
263: $tags = $matches[1];
264: $type = 'Regex matches';
265: } else {
266: $tags = preg_quote($tags, '/');
267: $type = 'Text equals';
268: }
269: $regex[] = array(
270: sprintf('%s "%s"', $type, $tags),
271: $tags,
272: $i,
273: );
274: continue;
275: }
276: foreach ($tags as $tag => $attributes) {
277: $regex[] = array(
278: sprintf('Open %s tag', $tag),
279: sprintf('[\s]*<%s', preg_quote($tag, '/')),
280: $i,
281: );
282: if ($attributes === true) {
283: $attributes = array();
284: }
285: $attrs = array();
286: $explanations = array();
287: $i = 1;
288: foreach ($attributes as $attr => $val) {
289: if (is_numeric($attr) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
290: $attrs[] = $matches[1];
291: $explanations[] = sprintf('Regex "%s" matches', $matches[1]);
292: continue;
293: } else {
294: $quotes = '["\']';
295: if (is_numeric($attr)) {
296: $attr = $val;
297: $val = '.+?';
298: $explanations[] = sprintf('Attribute "%s" present', $attr);
299: } elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
300: $quotes = '["\']?';
301: $val = $matches[1];
302: $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
303: } else {
304: $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
305: $val = preg_quote($val, '/');
306: }
307: $attrs[] = '[\s]+' . preg_quote($attr, '/') . '=' . $quotes . $val . $quotes;
308: }
309: $i++;
310: }
311: if ($attrs) {
312: $permutations = $this->_array_permute($attrs);
313:
314: $permutationTokens = array();
315: foreach ($permutations as $permutation) {
316: $permutationTokens[] = implode('', $permutation);
317: }
318: $regex[] = array(
319: sprintf('%s', implode(', ', $explanations)),
320: $permutationTokens,
321: $i,
322: );
323: }
324: $regex[] = array(
325: sprintf('End %s tag', $tag),
326: '[\s]*\/?[\s]*>[\n\r]*',
327: $i,
328: );
329: }
330: }
331: foreach ($regex as $i => $assertation) {
332: list($description, $expressions, $itemNum) = $assertation;
333: $matches = false;
334: foreach ((array)$expressions as $expression) {
335: if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
336: $matches = true;
337: $string = substr($string, strlen($match[0]));
338: break;
339: }
340: }
341: if (!$matches) {
342: $this->assertTrue(false, sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description));
343: if ($fullDebug) {
344: debug($string, true);
345: debug($regex, true);
346: }
347: return false;
348: }
349: }
350:
351: $this->assertTrue(true, '%s');
352: return true;
353: }
354:
355: /**
356: * Generates all permutation of an array $items and returns them in a new array.
357: *
358: * @param array $items An array of items
359: * @return array
360: */
361: protected function _array_permute($items, $perms = array()) {
362: static $permuted;
363: if (empty($perms)) {
364: $permuted = array();
365: }
366:
367: if (empty($items)) {
368: $permuted[] = $perms;
369: } else {
370: $numItems = count($items) - 1;
371: for ($i = $numItems; $i >= 0; --$i) {
372: $newItems = $items;
373: $newPerms = $perms;
374: list($tmp) = array_splice($newItems, $i, 1);
375: array_unshift($newPerms, $tmp);
376: $this->_array_permute($newItems, $newPerms);
377: }
378: return $permuted;
379: }
380: }
381:
382: /**
383: * Compatibility wrapper function for assertEquals
384: *
385: * @param mixed $result
386: * @param mixed $expected
387: * @param string $message the text to display if the assertion is not correct
388: * @return void
389: */
390: protected static function assertEqual($result, $expected, $message = '') {
391: return self::assertEquals($expected, $result, $message);
392: }
393:
394: /**
395: * Compatibility wrapper function for assertNotEquals
396: *
397: * @param mixed $result
398: * @param mixed $expected
399: * @param string $message the text to display if the assertion is not correct
400: * @return void
401: */
402: protected static function assertNotEqual($result, $expected, $message = '') {
403: return self::assertNotEquals($expected, $result, $message);
404: }
405:
406: /**
407: * Compatibility wrapper function for assertRegexp
408: *
409: * @param mixed $pattern a regular expression
410: * @param string $string the text to be matched
411: * @param string $message the text to display if the assertion is not correct
412: * @return void
413: */
414: protected static function assertPattern($pattern, $string, $message = '') {
415: return self::assertRegExp($pattern, $string, $message);
416: }
417:
418: /**
419: * Compatibility wrapper function for assertEquals
420: *
421: * @param mixed $actual
422: * @param mixed $expected
423: * @param string $message the text to display if the assertion is not correct
424: * @return void
425: */
426: protected static function assertIdentical($actual, $expected, $message = '') {
427: return self::assertSame($expected, $actual, $message);
428: }
429:
430: /**
431: * Compatibility wrapper function for assertNotEquals
432: *
433: * @param mixed $actual
434: * @param mixed $expected
435: * @param string $message the text to display if the assertion is not correct
436: * @return void
437: */
438: protected static function assertNotIdentical($actual, $expected, $message = '') {
439: return self::assertNotSame($expected, $actual, $message);
440: }
441:
442: /**
443: * Compatibility wrapper function for assertNotRegExp
444: *
445: * @param mixed $pattern a regular expression
446: * @param string $string the text to be matched
447: * @param string $message the text to display if the assertion is not correct
448: * @return void
449: */
450: protected static function assertNoPattern($pattern, $string, $message = '') {
451: return self::assertNotRegExp($pattern, $string, $message);
452: }
453:
454: protected function assertNoErrors() {
455: }
456:
457: /**
458: * Compatibility wrapper function for setExpectedException
459: *
460: * @param mixed $expected the name of the Exception or error
461: * @param string $message the text to display if the assertion is not correct
462: * @return void
463: */
464: protected function expectError($expected = false, $message = '') {
465: if (!$expected) {
466: $expected = 'Exception';
467: }
468: $this->setExpectedException($expected, $message);
469: }
470:
471: /**
472: * Compatibility wrapper function for setExpectedException
473: *
474: * @param mixed $expected the name of the Exception
475: * @param string $message the text to display if the assertion is not correct
476: * @return void
477: */
478: protected function expectException($name = 'Exception', $message = '') {
479: $this->setExpectedException($name, $message);
480: }
481:
482: /**
483: * Compatibility wrapper function for assertSame
484: *
485: * @param mixed $first
486: * @param mixed $second
487: * @param string $message the text to display if the assertion is not correct
488: * @return void
489: */
490: protected static function assertReference(&$first, &$second, $message = '') {
491: return self::assertSame($first, $second, $message);
492: }
493:
494: /**
495: * Compatibility wrapper for assertIsA
496: *
497: * @param string $object
498: * @param string $type
499: * @param string $message
500: * @return void
501: */
502: protected static function assertIsA($object, $type, $message = '') {
503: return self::assertInstanceOf($type, $object, $message);
504: }
505:
506: /**
507: * Compatibility function to test if value is between an acceptable range
508: *
509: * @param mixed $result
510: * @param mixed $expected
511: * @param mixed $margin the rage of acceptation
512: * @param string $message the text to display if the assertion is not correct
513: * @return void
514: */
515: protected static function assertWithinMargin($result, $expected, $margin, $message = '') {
516: $upper = $result + $margin;
517: $lower = $result - $margin;
518: return self::assertTrue((($expected <= $upper) && ($expected >= $lower)), $message);
519: }
520:
521: /**
522: * Compatibility function for skipping.
523: *
524: * @param boolean $condition Condition to trigger skipping
525: * @param string $message Message for skip
526: * @return boolean
527: */
528: protected function skipUnless($condition, $message = '') {
529: if (!$condition) {
530: $this->markTestSkipped($message);
531: }
532: return $condition;
533: }
534: }
535: