~tsep-dev/tsep/0.9-beta

« back to all changes in this revision

Viewing changes to branches/symfony/cake/tests/lib/cake_test_case.php

  • Committer: geoffreyfishing
  • Date: 2011-01-11 23:46:12 UTC
  • Revision ID: svn-v4:ae0de26e-ed09-4cbe-9a20-e40b4c60ac6c::125
Created a symfony branch for future migration to symfony

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
<?php
 
2
/**
 
3
 * CakeTestCase file
 
4
 *
 
5
 * PHP versions 4 and 5
 
6
 *
 
7
 * CakePHP(tm) Tests <http://book.cakephp.org/view/1196/Testing>
 
8
 * Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
 
9
 *
 
10
 *  Licensed under The Open Group Test Suite License
 
11
 *  Redistributions of files must retain the above copyright notice.
 
12
 *
 
13
 * @copyright     Copyright 2005-2010, Cake Software Foundation, Inc. (http://cakefoundation.org)
 
14
 * @link          http://book.cakephp.org/view/1196/Testing CakePHP(tm) Tests
 
15
 * @package       cake
 
16
 * @subpackage    cake.cake.tests.libs
 
17
 * @since         CakePHP(tm) v 1.2.0.4667
 
18
 * @license       http://www.opensource.org/licenses/opengroup.php The Open Group Test Suite License
 
19
 */
 
20
if (!class_exists('dispatcher')) {
 
21
        require CAKE . 'dispatcher.php';
 
22
}
 
23
require_once CAKE_TESTS_LIB . 'cake_test_model.php';
 
24
require_once CAKE_TESTS_LIB . 'cake_test_fixture.php';
 
25
App::import('Vendor', 'simpletest' . DS . 'unit_tester');
 
26
 
 
27
/**
 
28
 * CakeTestDispatcher
 
29
 *
 
30
 * @package       cake
 
31
 * @subpackage    cake.cake.tests.lib
 
32
 */
 
33
class CakeTestDispatcher extends Dispatcher {
 
34
 
 
35
/**
 
36
 * controller property
 
37
 *
 
38
 * @var Controller
 
39
 * @access public
 
40
 */
 
41
        var $controller;
 
42
        var $testCase;
 
43
 
 
44
/**
 
45
 * testCase method
 
46
 *
 
47
 * @param CakeTestCase $testCase
 
48
 * @return void
 
49
 * @access public
 
50
 */
 
51
        function testCase(&$testCase) {
 
52
                $this->testCase =& $testCase;
 
53
        }
 
54
 
 
55
/**
 
56
 * invoke method
 
57
 *
 
58
 * @param Controller $controller
 
59
 * @param array $params
 
60
 * @param boolean $missingAction
 
61
 * @return Controller
 
62
 * @access protected
 
63
 */
 
64
        function _invoke(&$controller, $params, $missingAction = false) {
 
65
                $this->controller =& $controller;
 
66
 
 
67
                if (isset($this->testCase) && method_exists($this->testCase, 'startController')) {
 
68
                        $this->testCase->startController($this->controller, $params);
 
69
                }
 
70
 
 
71
                $result = parent::_invoke($this->controller, $params, $missingAction);
 
72
 
 
73
                if (isset($this->testCase) && method_exists($this->testCase, 'endController')) {
 
74
                        $this->testCase->endController($this->controller, $params);
 
75
                }
 
76
 
 
77
                return $result;
 
78
        }
 
79
}
 
80
 
 
81
/**
 
82
 * CakeTestCase class
 
83
 *
 
84
 * @package       cake
 
85
 * @subpackage    cake.cake.tests.lib
 
86
 */
 
87
class CakeTestCase extends UnitTestCase {
 
88
 
 
89
/**
 
90
 * Methods used internally.
 
91
 *
 
92
 * @var array
 
93
 * @access public
 
94
 */
 
95
        var $methods = array('start', 'end', 'startcase', 'endcase', 'starttest', 'endtest');
 
96
 
 
97
/**
 
98
 * By default, all fixtures attached to this class will be truncated and reloaded after each test.
 
99
 * Set this to false to handle manually
 
100
 *
 
101
 * @var array
 
102
 * @access public
 
103
 */
 
104
        var $autoFixtures = true;
 
105
 
 
106
/**
 
107
 * Set this to false to avoid tables to be dropped if they already exist
 
108
 *
 
109
 * @var boolean
 
110
 * @access public
 
111
 */
 
112
        var $dropTables = true;
 
113
 
 
114
/**
 
115
 * Maps fixture class names to fixture identifiers as included in CakeTestCase::$fixtures
 
116
 *
 
117
 * @var array
 
118
 * @access protected
 
119
 */
 
120
        var $_fixtureClassMap = array();
 
121
 
 
122
/**
 
123
 * truncated property
 
124
 *
 
125
 * @var boolean
 
126
 * @access private
 
127
 */
 
128
        var $__truncated = true;
 
129
 
 
130
/**
 
131
 * savedGetData property
 
132
 *
 
133
 * @var array
 
134
 * @access private
 
135
 */
 
136
        var $__savedGetData = array();
 
137
 
 
138
/**
 
139
 * Called when a test case (group of methods) is about to start (to be overriden when needed.)
 
140
 *
 
141
 * @param string $method Test method about to get executed.
 
142
 * @return void
 
143
 * @access public
 
144
 */
 
145
        function startCase() {
 
146
        }
 
147
 
 
148
/**
 
149
 * Called when a test case (group of methods) has been executed (to be overriden when needed.)
 
150
 *
 
151
 * @param string $method Test method about that was executed.
 
152
 * @return void
 
153
 * @access public
 
154
 */
 
155
        function endCase() {
 
156
        }
 
157
 
 
158
/**
 
159
 * Called when a test case method is about to start (to be overriden when needed.)
 
160
 *
 
161
 * @param string $method Test method about to get executed.
 
162
 * @return void
 
163
 * @access public
 
164
 */
 
165
        function startTest($method) {
 
166
        }
 
167
 
 
168
/**
 
169
 * Called when a test case method has been executed (to be overriden when needed.)
 
170
 *
 
171
 * @param string $method Test method about that was executed.
 
172
 * @return void
 
173
 * @access public
 
174
 */
 
175
        function endTest($method) {
 
176
        }
 
177
 
 
178
/**
 
179
 * Overrides SimpleTestCase::assert to enable calling of skipIf() from within tests
 
180
 *
 
181
 * @param Expectation $expectation
 
182
 * @param mixed $compare
 
183
 * @param string $message
 
184
 * @return boolean|null
 
185
 * @access public
 
186
 */
 
187
        function assert(&$expectation, $compare, $message = '%s') {
 
188
                if ($this->_should_skip) {
 
189
                        return;
 
190
                }
 
191
                return parent::assert($expectation, $compare, $message);
 
192
        }
 
193
 
 
194
/**
 
195
 * Overrides SimpleTestCase::skipIf to provide a boolean return value
 
196
 *
 
197
 * @param boolean $shouldSkip
 
198
 * @param string $message
 
199
 * @return boolean
 
200
 * @access public
 
201
 */
 
202
        function skipIf($shouldSkip, $message = '%s') {
 
203
                parent::skipIf($shouldSkip, $message);
 
204
                return $shouldSkip;
 
205
        }
 
206
 
 
207
/**
 
208
 * Callback issued when a controller's action is about to be invoked through testAction().
 
209
 *
 
210
 * @param Controller $controller        Controller that's about to be invoked.
 
211
 * @param array $params Additional parameters as sent by testAction().
 
212
 * @return void
 
213
 * @access public
 
214
 */
 
215
        function startController(&$controller, $params = array()) {
 
216
                if (isset($params['fixturize']) && ((is_array($params['fixturize']) && !empty($params['fixturize'])) || $params['fixturize'] === true)) {
 
217
                        if (!isset($this->db)) {
 
218
                                $this->_initDb();
 
219
                        }
 
220
 
 
221
                        if ($controller->uses === false) {
 
222
                                $list = array($controller->modelClass);
 
223
                        } else {
 
224
                                $list = is_array($controller->uses) ? $controller->uses : array($controller->uses);
 
225
                        }
 
226
 
 
227
                        $models = array();
 
228
                        ClassRegistry::config(array('ds' => $params['connection']));
 
229
 
 
230
                        foreach ($list as $name) {
 
231
                                if ((is_array($params['fixturize']) && in_array($name, $params['fixturize'])) || $params['fixturize'] === true) {
 
232
                                        if (class_exists($name) || App::import('Model', $name)) {
 
233
                                                $object =& ClassRegistry::init($name);
 
234
                                                //switch back to specified datasource.
 
235
                                                $object->setDataSource($params['connection']);
 
236
                                                $db =& ConnectionManager::getDataSource($object->useDbConfig);
 
237
                                                $db->cacheSources = false;
 
238
 
 
239
                                                $models[$object->alias] = array(
 
240
                                                        'table' => $object->table,
 
241
                                                        'model' => $object->alias,
 
242
                                                        'key' => strtolower($name),
 
243
                                                );
 
244
                                        }
 
245
                                }
 
246
                        }
 
247
                        ClassRegistry::config(array('ds' => 'test_suite'));
 
248
 
 
249
                        if (!empty($models) && isset($this->db)) {
 
250
                                $this->_actionFixtures = array();
 
251
 
 
252
                                foreach ($models as $model) {
 
253
                                        $fixture =& new CakeTestFixture($this->db);
 
254
 
 
255
                                        $fixture->name = $model['model'] . 'Test';
 
256
                                        $fixture->table = $model['table'];
 
257
                                        $fixture->import = array('model' => $model['model'], 'records' => true);
 
258
                                        $fixture->init();
 
259
 
 
260
                                        $fixture->create($this->db);
 
261
                                        $fixture->insert($this->db);
 
262
                                        $this->_actionFixtures[] =& $fixture;
 
263
                                }
 
264
 
 
265
                                foreach ($models as $model) {
 
266
                                        $object =& ClassRegistry::getObject($model['key']);
 
267
                                        if ($object !== false) {
 
268
                                                $object->setDataSource('test_suite');
 
269
                                                $object->cacheSources = false;
 
270
                                        }
 
271
                                }
 
272
                        }
 
273
                }
 
274
        }
 
275
 
 
276
/**
 
277
 * Callback issued when a controller's action has been invoked through testAction().
 
278
 *
 
279
 * @param Controller $controller Controller that has been invoked.
 
280
 * @param array $params Additional parameters as sent by testAction().
 
281
 * @return void
 
282
 * @access public
 
283
 */
 
284
        function endController(&$controller, $params = array()) {
 
285
                if (isset($this->db) && isset($this->_actionFixtures) && !empty($this->_actionFixtures) && $this->dropTables) {
 
286
                        foreach ($this->_actionFixtures as $fixture) {
 
287
                                $fixture->drop($this->db);
 
288
                        }
 
289
                }
 
290
        }
 
291
 
 
292
/**
 
293
 * Executes a Cake URL, and can get (depending on the $params['return'] value):
 
294
 *
 
295
 * Params:
 
296
 * - 'return' has several possible values:
 
297
 *   1. 'result': Whatever the action returns (and also specifies $this->params['requested'] for controller)
 
298
 *   2. 'view': The rendered view, without the layout
 
299
 *   3. 'contents': The rendered view, within the layout.
 
300
 *   4. 'vars': the view vars
 
301
 *
 
302
 * - 'fixturize' - Set to true if you want to copy model data from 'connection' to the test_suite connection
 
303
 * - 'data' - The data you want to insert into $this->data in the controller.
 
304
 * - 'connection' - Which connection to use in conjunction with fixturize (defaults to 'default')
 
305
 * - 'method' - What type of HTTP method to simulate (defaults to post)
 
306
 *
 
307
 * @param string $url Cake URL to execute (e.g: /articles/view/455)
 
308
 * @param mixed $params Parameters (see above), or simply a string of what to return
 
309
 * @return mixed Whatever is returned depending of requested result
 
310
 * @access public
 
311
 */
 
312
        function testAction($url, $params = array()) {
 
313
                $default = array(
 
314
                        'return' => 'result',
 
315
                        'fixturize' => false,
 
316
                        'data' => array(),
 
317
                        'method' => 'post',
 
318
                        'connection' => 'default'
 
319
                );
 
320
 
 
321
                if (is_string($params)) {
 
322
                        $params = array('return' => $params);
 
323
                }
 
324
                $params = array_merge($default, $params);
 
325
 
 
326
                $toSave = array(
 
327
                        'case' => null,
 
328
                        'group' => null,
 
329
                        'app' => null,
 
330
                        'output' => null,
 
331
                        'show' => null,
 
332
                        'plugin' => null
 
333
                );
 
334
                $this->__savedGetData = (empty($this->__savedGetData))
 
335
                                ? array_intersect_key($_GET, $toSave)
 
336
                                : $this->__savedGetData;
 
337
 
 
338
                $data = (!empty($params['data'])) ? $params['data'] : array();
 
339
 
 
340
                if (strtolower($params['method']) == 'get') {
 
341
                        $_GET = array_merge($this->__savedGetData, $data);
 
342
                        $_POST = array();
 
343
                } else {
 
344
                        $_POST = array('data' => $data);
 
345
                        $_GET = $this->__savedGetData;
 
346
                }
 
347
 
 
348
                $return = $params['return'];
 
349
                $params = array_diff_key($params, array('data' => null, 'method' => null, 'return' => null));
 
350
 
 
351
                $dispatcher =& new CakeTestDispatcher();
 
352
                $dispatcher->testCase($this);
 
353
 
 
354
                if ($return != 'result') {
 
355
                        if ($return != 'contents') {
 
356
                                $params['layout'] = false;
 
357
                        }
 
358
 
 
359
                        ob_start();
 
360
                        @$dispatcher->dispatch($url, $params);
 
361
                        $result = ob_get_clean();
 
362
 
 
363
                        if ($return == 'vars') {
 
364
                                $view =& ClassRegistry::getObject('view');
 
365
                                $viewVars = $view->getVars();
 
366
 
 
367
                                $result = array();
 
368
 
 
369
                                foreach ($viewVars as $var) {
 
370
                                        $result[$var] = $view->getVar($var);
 
371
                                }
 
372
 
 
373
                                if (!empty($view->pageTitle)) {
 
374
                                        $result = array_merge($result, array('title' => $view->pageTitle));
 
375
                                }
 
376
                        }
 
377
                } else {
 
378
                        $params['return'] = 1;
 
379
                        $params['bare'] = 1;
 
380
                        $params['requested'] = 1;
 
381
 
 
382
                        $result = @$dispatcher->dispatch($url, $params);
 
383
                }
 
384
 
 
385
                if (isset($this->_actionFixtures)) {
 
386
                        unset($this->_actionFixtures);
 
387
                }
 
388
                ClassRegistry::flush();
 
389
 
 
390
                return $result;
 
391
        }
 
392
 
 
393
/**
 
394
 * Announces the start of a test.
 
395
 *
 
396
 * @param string $method Test method just started.
 
397
 * @return void
 
398
 * @access public
 
399
 */
 
400
        function before($method) {
 
401
                parent::before($method);
 
402
 
 
403
                if (isset($this->fixtures) && (!is_array($this->fixtures) || empty($this->fixtures))) {
 
404
                        unset($this->fixtures);
 
405
                }
 
406
 
 
407
                // Set up DB connection
 
408
                if (isset($this->fixtures) && strtolower($method) == 'start') {
 
409
                        $this->_initDb();
 
410
                        $this->_loadFixtures();
 
411
                }
 
412
 
 
413
                // Create records
 
414
                if (isset($this->_fixtures) && isset($this->db) && !in_array(strtolower($method), array('start', 'end')) && $this->__truncated && $this->autoFixtures == true) {
 
415
                        foreach ($this->_fixtures as $fixture) {
 
416
                                $inserts = $fixture->insert($this->db);
 
417
                        }
 
418
                }
 
419
 
 
420
                if (!in_array(strtolower($method), $this->methods)) {
 
421
                        $this->startTest($method);
 
422
                }
 
423
        }
 
424
 
 
425
/**
 
426
 * Runs as first test to create tables.
 
427
 *
 
428
 * @return void
 
429
 * @access public
 
430
 */
 
431
        function start() {
 
432
                if (isset($this->_fixtures) && isset($this->db)) {
 
433
                        Configure::write('Cache.disable', true);
 
434
                        $cacheSources = $this->db->cacheSources;
 
435
                        $this->db->cacheSources = false;
 
436
                        $sources = $this->db->listSources();
 
437
                        $this->db->cacheSources = $cacheSources;
 
438
 
 
439
                        if (!$this->dropTables) {
 
440
                                return;
 
441
                        }
 
442
                        foreach ($this->_fixtures as $fixture) {
 
443
                                $table = $this->db->config['prefix'] . $fixture->table;
 
444
                                if (in_array($table, $sources)) {
 
445
                                        $fixture->drop($this->db);
 
446
                                        $fixture->create($this->db);
 
447
                                } elseif (!in_array($table, $sources)) {
 
448
                                        $fixture->create($this->db);
 
449
                                }
 
450
                        }
 
451
                }
 
452
        }
 
453
 
 
454
/**
 
455
 * Runs as last test to drop tables.
 
456
 *
 
457
 * @return void
 
458
 * @access public
 
459
 */
 
460
        function end() {
 
461
                if (isset($this->_fixtures) && isset($this->db)) {
 
462
                        if ($this->dropTables) {
 
463
                                foreach (array_reverse($this->_fixtures) as $fixture) {
 
464
                                        $fixture->drop($this->db);
 
465
                                }
 
466
                        }
 
467
                        $this->db->sources(true);
 
468
                        Configure::write('Cache.disable', false);
 
469
                }
 
470
 
 
471
                if (class_exists('ClassRegistry')) {
 
472
                        ClassRegistry::flush();
 
473
                }
 
474
        }
 
475
 
 
476
/**
 
477
 * Announces the end of a test.
 
478
 *
 
479
 * @param string $method Test method just finished.
 
480
 * @return void
 
481
 * @access public
 
482
 */
 
483
        function after($method) {
 
484
                $isTestMethod = !in_array(strtolower($method), array('start', 'end'));
 
485
 
 
486
                if (isset($this->_fixtures) && isset($this->db) && $isTestMethod) {
 
487
                        foreach ($this->_fixtures as $fixture) {
 
488
                                $fixture->truncate($this->db);
 
489
                        }
 
490
                        $this->__truncated = true;
 
491
                } else {
 
492
                        $this->__truncated = false;
 
493
                }
 
494
 
 
495
                if (!in_array(strtolower($method), $this->methods)) {
 
496
                        $this->endTest($method);
 
497
                }
 
498
                $this->_should_skip = false;
 
499
 
 
500
                parent::after($method);
 
501
        }
 
502
 
 
503
/**
 
504
 * Gets a list of test names. Normally that will be all internal methods that start with the
 
505
 * name "test". This method should be overridden if you want a different rule.
 
506
 *
 
507
 * @return array List of test names.
 
508
 * @access public
 
509
 */
 
510
        function getTests() {
 
511
                return array_merge(
 
512
                        array('start', 'startCase'),
 
513
                        array_diff(parent::getTests(), array('testAction', 'testaction')),
 
514
                        array('endCase', 'end')
 
515
                );
 
516
        }
 
517
 
 
518
/**
 
519
 * Chooses which fixtures to load for a given test
 
520
 *
 
521
 * @param string $fixture Each parameter is a model name that corresponds to a
 
522
 *                        fixture, i.e. 'Post', 'Author', etc.
 
523
 * @return void
 
524
 * @access public
 
525
 * @see CakeTestCase::$autoFixtures
 
526
 */
 
527
        function loadFixtures() {
 
528
                $args = func_get_args();
 
529
                foreach ($args as $class) {
 
530
                        if (isset($this->_fixtureClassMap[$class])) {
 
531
                                $fixture = $this->_fixtures[$this->_fixtureClassMap[$class]];
 
532
 
 
533
                                $fixture->truncate($this->db);
 
534
                                $fixture->insert($this->db);
 
535
                        } else {
 
536
                                trigger_error(sprintf(__('Referenced fixture class %s not found', true), $class), E_USER_WARNING);
 
537
                        }
 
538
                }
 
539
        }
 
540
 
 
541
/**
 
542
 * Takes an array $expected and generates a regex from it to match the provided $string.
 
543
 * Samples for $expected:
 
544
 *
 
545
 * Checks for an input tag with a name attribute (contains any non-empty value) and an id
 
546
 * attribute that contains 'my-input':
 
547
 *      array('input' => array('name', 'id' => 'my-input'))
 
548
 *
 
549
 * Checks for two p elements with some text in them:
 
550
 *      array(
 
551
 *              array('p' => true),
 
552
 *              'textA',
 
553
 *              '/p',
 
554
 *              array('p' => true),
 
555
 *              'textB',
 
556
 *              '/p'
 
557
 *      )
 
558
 *
 
559
 * You can also specify a pattern expression as part of the attribute values, or the tag
 
560
 * being defined, if you prepend the value with preg: and enclose it with slashes, like so:
 
561
 *      array(
 
562
 *      array('input' => array('name', 'id' => 'preg:/FieldName\d+/')),
 
563
 *      'preg:/My\s+field/'
 
564
 *      )
 
565
 *
 
566
 * Important: This function is very forgiving about whitespace and also accepts any
 
567
 * permutation of attribute order. It will also allow whitespaces between specified tags.
 
568
 *
 
569
 * @param string $string An HTML/XHTML/XML string
 
570
 * @param array $expected An array, see above
 
571
 * @param string $message SimpleTest failure output string
 
572
 * @return boolean
 
573
 * @access public
 
574
 */
 
575
        function assertTags($string, $expected, $fullDebug = false) {
 
576
                $regex = array();
 
577
                $normalized = array();
 
578
                foreach ((array) $expected as $key => $val) {
 
579
                        if (!is_numeric($key)) {
 
580
                                $normalized[] = array($key => $val);
 
581
                        } else {
 
582
                                $normalized[] = $val;
 
583
                        }
 
584
                }
 
585
                $i = 0;
 
586
                foreach ($normalized as $tags) {
 
587
                        if (!is_array($tags)) {
 
588
                                $tags = (string)$tags;
 
589
                        }
 
590
                        $i++;
 
591
                        if (is_string($tags) && $tags{0} == '<') {
 
592
                                $tags = array(substr($tags, 1) => array());
 
593
                        } elseif (is_string($tags)) {
 
594
                                $tagsTrimmed = preg_replace('/\s+/m', '', $tags);
 
595
 
 
596
                                if (preg_match('/^\*?\//', $tags, $match) && $tagsTrimmed !== '//') {
 
597
                                        $prefix = array(null, null);
 
598
 
 
599
                                        if ($match[0] == '*/') {
 
600
                                                $prefix = array('Anything, ', '.*?');
 
601
                                        }
 
602
                                        $regex[] = array(
 
603
                                                sprintf('%sClose %s tag', $prefix[0], substr($tags, strlen($match[0]))),
 
604
                                                sprintf('%s<[\s]*\/[\s]*%s[\s]*>[\n\r]*', $prefix[1], substr($tags,  strlen($match[0]))),
 
605
                                                $i,
 
606
                                        );
 
607
                                        continue;
 
608
                                }
 
609
                                if (!empty($tags) && preg_match('/^preg\:\/(.+)\/$/i', $tags, $matches)) {
 
610
                                        $tags = $matches[1];
 
611
                                        $type = 'Regex matches';
 
612
                                } else {
 
613
                                        $tags = preg_quote($tags, '/');
 
614
                                        $type = 'Text equals';
 
615
                                }
 
616
                                $regex[] = array(
 
617
                                        sprintf('%s "%s"', $type, $tags),
 
618
                                        $tags,
 
619
                                        $i,
 
620
                                );
 
621
                                continue;
 
622
                        }
 
623
                        foreach ($tags as $tag => $attributes) {
 
624
                                $regex[] = array(
 
625
                                        sprintf('Open %s tag', $tag),
 
626
                                        sprintf('[\s]*<%s', preg_quote($tag, '/')),
 
627
                                        $i,
 
628
                                );
 
629
                                if ($attributes === true) {
 
630
                                        $attributes = array();
 
631
                                }
 
632
                                $attrs = array();
 
633
                                $explanations = array();
 
634
                                $i = 1;
 
635
                                foreach ($attributes as $attr => $val) {
 
636
                                        if (is_numeric($attr) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
 
637
                                                $attrs[] = $matches[1];
 
638
                                                $explanations[] = sprintf('Regex "%s" matches', $matches[1]);
 
639
                                                continue;
 
640
                                        } else {
 
641
                                                $quotes = '["\']';
 
642
                                                if (is_numeric($attr)) {
 
643
                                                        $attr = $val;
 
644
                                                        $val = '.+?';
 
645
                                                        $explanations[] = sprintf('Attribute "%s" present', $attr);
 
646
                                                } elseif (!empty($val) && preg_match('/^preg\:\/(.+)\/$/i', $val, $matches)) {
 
647
                                                        $quotes = '["\']?';
 
648
                                                        $val = $matches[1];
 
649
                                                        $explanations[] = sprintf('Attribute "%s" matches "%s"', $attr, $val);
 
650
                                                } else {
 
651
                                                        $explanations[] = sprintf('Attribute "%s" == "%s"', $attr, $val);
 
652
                                                        $val = preg_quote($val, '/');
 
653
                                                }
 
654
                                                $attrs[] = '[\s]+' . preg_quote($attr, '/') . '=' . $quotes . $val . $quotes;
 
655
                                        }
 
656
                                        $i++;
 
657
                                }
 
658
                                if ($attrs) {
 
659
                                        $permutations = $this->__array_permute($attrs);
 
660
 
 
661
                                        $permutationTokens = array();
 
662
                                        foreach ($permutations as $permutation) {
 
663
                                                $permutationTokens[] = implode('', $permutation);
 
664
                                        }
 
665
                                        $regex[] = array(
 
666
                                                sprintf('%s', implode(', ', $explanations)),
 
667
                                                $permutationTokens,
 
668
                                                $i,
 
669
                                        );
 
670
                                }
 
671
                                $regex[] = array(
 
672
                                        sprintf('End %s tag', $tag),
 
673
                                        '[\s]*\/?[\s]*>[\n\r]*',
 
674
                                        $i,
 
675
                                );
 
676
                        }
 
677
                }
 
678
                foreach ($regex as $i => $assertation) {
 
679
                        list($description, $expressions, $itemNum) = $assertation;
 
680
                        $matches = false;
 
681
                        foreach ((array)$expressions as $expression) {
 
682
                                if (preg_match(sprintf('/^%s/s', $expression), $string, $match)) {
 
683
                                        $matches = true;
 
684
                                        $string = substr($string, strlen($match[0]));
 
685
                                        break;
 
686
                                }
 
687
                        }
 
688
                        if (!$matches) {
 
689
                                $this->assert(new TrueExpectation(), false, sprintf('Item #%d / regex #%d failed: %s', $itemNum, $i, $description));
 
690
                                if ($fullDebug) {
 
691
                                        debug($string, true);
 
692
                                        debug($regex, true);
 
693
                                }
 
694
                                return false;
 
695
                        }
 
696
                }
 
697
                return $this->assert(new TrueExpectation(), true, '%s');
 
698
        }
 
699
 
 
700
/**
 
701
 * Initialize DB connection.
 
702
 *
 
703
 * @return void
 
704
 * @access protected
 
705
 */
 
706
        function _initDb() {
 
707
                $testDbAvailable = in_array('test', array_keys(ConnectionManager::enumConnectionObjects()));
 
708
 
 
709
                $_prefix = null;
 
710
 
 
711
                if ($testDbAvailable) {
 
712
                        // Try for test DB
 
713
                        restore_error_handler();
 
714
                        @$db =& ConnectionManager::getDataSource('test');
 
715
                        set_error_handler('simpleTestErrorHandler');
 
716
                        $testDbAvailable = $db->isConnected();
 
717
                }
 
718
 
 
719
                // Try for default DB
 
720
                if (!$testDbAvailable) {
 
721
                        $db =& ConnectionManager::getDataSource('default');
 
722
                        $_prefix = $db->config['prefix'];
 
723
                        $db->config['prefix'] = 'test_suite_';
 
724
                }
 
725
 
 
726
                ConnectionManager::create('test_suite', $db->config);
 
727
                $db->config['prefix'] = $_prefix;
 
728
 
 
729
                // Get db connection
 
730
                $this->db =& ConnectionManager::getDataSource('test_suite');
 
731
                $this->db->cacheSources  = false;
 
732
 
 
733
                ClassRegistry::config(array('ds' => 'test_suite'));
 
734
        }
 
735
 
 
736
/**
 
737
 * Load fixtures specified in var $fixtures.
 
738
 *
 
739
 * @return void
 
740
 * @access protected
 
741
 */
 
742
        function _loadFixtures() {
 
743
                if (!isset($this->fixtures) || empty($this->fixtures)) {
 
744
                        return;
 
745
                }
 
746
 
 
747
                if (!is_array($this->fixtures)) {
 
748
                        $this->fixtures = array_map('trim', explode(',', $this->fixtures));
 
749
                }
 
750
 
 
751
                $this->_fixtures = array();
 
752
 
 
753
                foreach ($this->fixtures as $index => $fixture) {
 
754
                        $fixtureFile = null;
 
755
 
 
756
                        if (strpos($fixture, 'core.') === 0) {
 
757
                                $fixture = substr($fixture, strlen('core.'));
 
758
                                foreach (App::core('cake') as $key => $path) {
 
759
                                        $fixturePaths[] = $path . 'tests' . DS . 'fixtures';
 
760
                                }
 
761
                        } elseif (strpos($fixture, 'app.') === 0) {
 
762
                                $fixture = substr($fixture, strlen('app.'));
 
763
                                $fixturePaths = array(
 
764
                                        TESTS . 'fixtures',
 
765
                                        VENDORS . 'tests' . DS . 'fixtures'
 
766
                                );
 
767
                        } elseif (strpos($fixture, 'plugin.') === 0) {
 
768
                                $parts = explode('.', $fixture, 3);
 
769
                                $pluginName = $parts[1];
 
770
                                $fixture = $parts[2];
 
771
                                $fixturePaths = array(
 
772
                                        App::pluginPath($pluginName) . 'tests' . DS . 'fixtures',
 
773
                                        TESTS . 'fixtures',
 
774
                                        VENDORS . 'tests' . DS . 'fixtures'
 
775
                                );
 
776
                        } else {
 
777
                                $fixturePaths = array(
 
778
                                        TESTS . 'fixtures',
 
779
                                        VENDORS . 'tests' . DS . 'fixtures',
 
780
                                        TEST_CAKE_CORE_INCLUDE_PATH . DS . 'cake' . DS . 'tests' . DS . 'fixtures'
 
781
                                );
 
782
                        }
 
783
 
 
784
                        foreach ($fixturePaths as $path) {
 
785
                                if (is_readable($path . DS . $fixture . '_fixture.php')) {
 
786
                                        $fixtureFile = $path . DS . $fixture . '_fixture.php';
 
787
                                        break;
 
788
                                }
 
789
                        }
 
790
 
 
791
                        if (isset($fixtureFile)) {
 
792
                                require_once($fixtureFile);
 
793
                                $fixtureClass = Inflector::camelize($fixture) . 'Fixture';
 
794
                                $this->_fixtures[$this->fixtures[$index]] =& new $fixtureClass($this->db);
 
795
                                $this->_fixtureClassMap[Inflector::camelize($fixture)] = $this->fixtures[$index];
 
796
                        }
 
797
                }
 
798
 
 
799
                if (empty($this->_fixtures)) {
 
800
                        unset($this->_fixtures);
 
801
                }
 
802
        }
 
803
 
 
804
/**
 
805
 * Generates all permutation of an array $items and returns them in a new array.
 
806
 *
 
807
 * @param array $items An array of items
 
808
 * @return array
 
809
 * @access private
 
810
 */
 
811
        function __array_permute($items, $perms = array()) {
 
812
                static $permuted;
 
813
                if (empty($perms)) {
 
814
                        $permuted = array();
 
815
                }
 
816
 
 
817
                if (empty($items)) {
 
818
                        $permuted[] = $perms;
 
819
                } else {
 
820
                        $numItems = count($items) - 1;
 
821
                        for ($i = $numItems; $i >= 0; --$i) {
 
822
                                $newItems = $items;
 
823
                                $newPerms = $perms;
 
824
                                list($tmp) = array_splice($newItems, $i, 1);
 
825
                                array_unshift($newPerms, $tmp);
 
826
                                $this->__array_permute($newItems, $newPerms);
 
827
                        }
 
828
                        return $permuted;
 
829
                }
 
830
        }
 
831
}