Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
13532 anikendra 1
<?php
2
/**
3
 * Test Shell
4
 *
5
 * This Shell allows the running of test suites via the cake command line
6
 *
7
 * CakePHP(tm) Tests <http://book.cakephp.org/2.0/en/development/testing.html>
8
 * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
9
 *
10
 * Licensed under The MIT License
11
 * For full copyright and license information, please see the LICENSE.txt
12
 * Redistributions of files must retain the above copyright notice
13
 *
14
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
15
 * @link          http://book.cakephp.org/2.0/en/development/testing.html
16
 * @since         CakePHP(tm) v 1.2.0.4433
17
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
18
 */
19
 
20
App::uses('Shell', 'Console');
21
App::uses('CakeTestSuiteDispatcher', 'TestSuite');
22
App::uses('CakeTestSuiteCommand', 'TestSuite');
23
App::uses('CakeTestLoader', 'TestSuite');
24
 
25
/**
26
 * Provides a CakePHP wrapper around PHPUnit.
27
 * Adds in CakePHP's fixtures and gives access to plugin, app and core test cases
28
 *
29
 * @package       Cake.Console.Command
30
 */
31
class TestShell extends Shell {
32
 
33
/**
34
 * Dispatcher object for the run.
35
 *
36
 * @var CakeTestDispatcher
37
 */
38
	protected $_dispatcher = null;
39
 
40
/**
41
 * get the option parser for the test suite.
42
 *
43
 * @return void
44
 */
45
	public function getOptionParser() {
46
		$parser = new ConsoleOptionParser($this->name);
47
		$parser->description(array(
48
			__d('cake_console', 'The CakePHP Testsuite allows you to run test cases from the command line'),
49
		))->addArgument('category', array(
50
			'help' => __d('cake_console', 'The category for the test, or test file, to test.'),
51
			'required' => false,
52
		))->addArgument('file', array(
53
			'help' => __d('cake_console', 'The path to the file, or test file, to test.'),
54
			'required' => false,
55
		))->addOption('log-junit', array(
56
			'help' => __d('cake_console', '<file> Log test execution in JUnit XML format to file.'),
57
			'default' => false
58
		))->addOption('log-json', array(
59
			'help' => __d('cake_console', '<file> Log test execution in JSON format to file.'),
60
			'default' => false
61
		))->addOption('log-tap', array(
62
			'help' => __d('cake_console', '<file> Log test execution in TAP format to file.'),
63
			'default' => false
64
		))->addOption('log-dbus', array(
65
			'help' => __d('cake_console', 'Log test execution to DBUS.'),
66
			'default' => false
67
		))->addOption('coverage-html', array(
68
			'help' => __d('cake_console', '<dir> Generate code coverage report in HTML format.'),
69
			'default' => false
70
		))->addOption('coverage-clover', array(
71
			'help' => __d('cake_console', '<file> Write code coverage data in Clover XML format.'),
72
			'default' => false
73
		))->addOption('testdox-html', array(
74
			'help' => __d('cake_console', '<file> Write agile documentation in HTML format to file.'),
75
			'default' => false
76
		))->addOption('testdox-text', array(
77
			'help' => __d('cake_console', '<file> Write agile documentation in Text format to file.'),
78
			'default' => false
79
		))->addOption('filter', array(
80
			'help' => __d('cake_console', '<pattern> Filter which tests to run.'),
81
			'default' => false
82
		))->addOption('group', array(
83
			'help' => __d('cake_console', '<name> Only runs tests from the specified group(s).'),
84
			'default' => false
85
		))->addOption('exclude-group', array(
86
			'help' => __d('cake_console', '<name> Exclude tests from the specified group(s).'),
87
			'default' => false
88
		))->addOption('list-groups', array(
89
			'help' => __d('cake_console', 'List available test groups.'),
90
			'boolean' => true
91
		))->addOption('loader', array(
92
			'help' => __d('cake_console', 'TestSuiteLoader implementation to use.'),
93
			'default' => false
94
		))->addOption('repeat', array(
95
			'help' => __d('cake_console', '<times> Runs the test(s) repeatedly.'),
96
			'default' => false
97
		))->addOption('tap', array(
98
			'help' => __d('cake_console', 'Report test execution progress in TAP format.'),
99
			'boolean' => true
100
		))->addOption('testdox', array(
101
			'help' => __d('cake_console', 'Report test execution progress in TestDox format.'),
102
			'default' => false,
103
			'boolean' => true
104
		))->addOption('no-colors', array(
105
			'help' => __d('cake_console', 'Do not use colors in output.'),
106
			'boolean' => true
107
		))->addOption('stderr', array(
108
			'help' => __d('cake_console', 'Write to STDERR instead of STDOUT.'),
109
			'boolean' => true
110
		))->addOption('stop-on-error', array(
111
			'help' => __d('cake_console', 'Stop execution upon first error or failure.'),
112
			'boolean' => true
113
		))->addOption('stop-on-failure', array(
114
			'help' => __d('cake_console', 'Stop execution upon first failure.'),
115
			'boolean' => true
116
		))->addOption('stop-on-skipped', array(
117
			'help' => __d('cake_console', 'Stop execution upon first skipped test.'),
118
			'boolean' => true
119
		))->addOption('stop-on-incomplete', array(
120
			'help' => __d('cake_console', 'Stop execution upon first incomplete test.'),
121
			'boolean' => true
122
		))->addOption('strict', array(
123
			'help' => __d('cake_console', 'Mark a test as incomplete if no assertions are made.'),
124
			'boolean' => true
125
		))->addOption('wait', array(
126
			'help' => __d('cake_console', 'Waits for a keystroke after each test.'),
127
			'boolean' => true
128
		))->addOption('process-isolation', array(
129
			'help' => __d('cake_console', 'Run each test in a separate PHP process.'),
130
			'boolean' => true
131
		))->addOption('no-globals-backup', array(
132
			'help' => __d('cake_console', 'Do not backup and restore $GLOBALS for each test.'),
133
			'boolean' => true
134
		))->addOption('static-backup', array(
135
			'help' => __d('cake_console', 'Backup and restore static attributes for each test.'),
136
			'boolean' => true
137
		))->addOption('syntax-check', array(
138
			'help' => __d('cake_console', 'Try to check source files for syntax errors.'),
139
			'boolean' => true
140
		))->addOption('bootstrap', array(
141
			'help' => __d('cake_console', '<file> A "bootstrap" PHP file that is run before the tests.'),
142
			'default' => false
143
		))->addOption('configuration', array(
144
			'help' => __d('cake_console', '<file> Read configuration from XML file.'),
145
			'default' => false
146
		))->addOption('no-configuration', array(
147
			'help' => __d('cake_console', 'Ignore default configuration file (phpunit.xml).'),
148
			'boolean' => true
149
		))->addOption('include-path', array(
150
			'help' => __d('cake_console', '<path(s)> Prepend PHP include_path with given path(s).'),
151
			'default' => false
152
		))->addOption('directive', array(
153
			'help' => __d('cake_console', 'key[=value] Sets a php.ini value.'),
154
			'default' => false
155
		))->addOption('fixture', array(
156
			'help' => __d('cake_console', 'Choose a custom fixture manager.'),
157
		))->addOption('debug', array(
158
			'help' => __d('cake_console', 'More verbose output.'),
159
		));
160
 
161
		return $parser;
162
	}
163
 
164
/**
165
 * Initialization method installs PHPUnit and loads all plugins
166
 *
167
 * @return void
168
 * @throws Exception
169
 */
170
	public function initialize() {
171
		$this->_dispatcher = new CakeTestSuiteDispatcher();
172
		$success = $this->_dispatcher->loadTestFramework();
173
		if (!$success) {
174
			throw new Exception(__d('cake_dev', 'Please install PHPUnit framework <info>(http://www.phpunit.de)</info>'));
175
		}
176
	}
177
 
178
/**
179
 * Parse the CLI options into an array CakeTestDispatcher can use.
180
 *
181
 * @return array Array of params for CakeTestDispatcher
182
 */
183
	protected function _parseArgs() {
184
		if (empty($this->args)) {
185
			return;
186
		}
187
		$params = array(
188
			'core' => false,
189
			'app' => false,
190
			'plugin' => null,
191
			'output' => 'text',
192
		);
193
 
194
		if (strpos($this->args[0], '.php')) {
195
			$category = $this->_mapFileToCategory($this->args[0]);
196
			$params['case'] = $this->_mapFileToCase($this->args[0], $category);
197
		} else {
198
			$category = $this->args[0];
199
			if (isset($this->args[1])) {
200
				$params['case'] = $this->args[1];
201
			}
202
		}
203
 
204
		if ($category === 'core') {
205
			$params['core'] = true;
206
		} elseif ($category === 'app') {
207
			$params['app'] = true;
208
		} else {
209
			$params['plugin'] = $category;
210
		}
211
 
212
		return $params;
213
	}
214
 
215
/**
216
 * Converts the options passed to the shell as options for the PHPUnit cli runner
217
 *
218
 * @return array Array of params for CakeTestDispatcher
219
 */
220
	protected function _runnerOptions() {
221
		$options = array();
222
		$params = $this->params;
223
		unset($params['help']);
224
 
225
		if (!empty($params['no-colors'])) {
226
			unset($params['no-colors'], $params['colors']);
227
		} else {
228
			$params['colors'] = true;
229
		}
230
 
231
		foreach ($params as $param => $value) {
232
			if ($value === false) {
233
				continue;
234
			}
235
			$options[] = '--' . $param;
236
			if (is_string($value)) {
237
				$options[] = $value;
238
			}
239
		}
240
		return $options;
241
	}
242
 
243
/**
244
 * Main entry point to this shell
245
 *
246
 * @return void
247
 */
248
	public function main() {
249
		$this->out(__d('cake_console', 'CakePHP Test Shell'));
250
		$this->hr();
251
 
252
		$args = $this->_parseArgs();
253
 
254
		if (empty($args['case'])) {
255
			return $this->available();
256
		}
257
 
258
		$this->_run($args, $this->_runnerOptions());
259
	}
260
 
261
/**
262
 * Runs the test case from $runnerArgs
263
 *
264
 * @param array $runnerArgs list of arguments as obtained from _parseArgs()
265
 * @param array $options list of options as constructed by _runnerOptions()
266
 * @return void
267
 */
268
	protected function _run($runnerArgs, $options = array()) {
269
		restore_error_handler();
270
		restore_error_handler();
271
 
272
		$testCli = new CakeTestSuiteCommand('CakeTestLoader', $runnerArgs);
273
		$testCli->run($options);
274
	}
275
 
276
/**
277
 * Shows a list of available test cases and gives the option to run one of them
278
 *
279
 * @return void
280
 */
281
	public function available() {
282
		$params = $this->_parseArgs();
283
		$testCases = CakeTestLoader::generateTestList($params);
284
		$app = $params['app'];
285
		$plugin = $params['plugin'];
286
 
287
		$title = "Core Test Cases:";
288
		$category = 'core';
289
		if ($app) {
290
			$title = "App Test Cases:";
291
			$category = 'app';
292
		} elseif ($plugin) {
293
			$title = Inflector::humanize($plugin) . " Test Cases:";
294
			$category = $plugin;
295
		}
296
 
297
		if (empty($testCases)) {
298
			$this->out(__d('cake_console', "No test cases available \n\n"));
299
			return $this->out($this->OptionParser->help());
300
		}
301
 
302
		$this->out($title);
303
		$i = 1;
304
		$cases = array();
305
		foreach ($testCases as $testCase) {
306
			$case = str_replace('Test.php', '', $testCase);
307
			$this->out("[$i] $case");
308
			$cases[$i] = $case;
309
			$i++;
310
		}
311
 
312
		while ($choice = $this->in(__d('cake_console', 'What test case would you like to run?'), null, 'q')) {
313
			if (is_numeric($choice) && isset($cases[$choice])) {
314
				$this->args[0] = $category;
315
				$this->args[1] = $cases[$choice];
316
				$this->_run($this->_parseArgs(), $this->_runnerOptions());
317
				break;
318
			}
319
 
320
			if (is_string($choice) && in_array($choice, $cases)) {
321
				$this->args[0] = $category;
322
				$this->args[1] = $choice;
323
				$this->_run($this->_parseArgs(), $this->_runnerOptions());
324
				break;
325
			}
326
 
327
			if ($choice === 'q') {
328
				break;
329
			}
330
		}
331
	}
332
 
333
/**
334
 * Find the test case for the passed file. The file could itself be a test.
335
 *
336
 * @param string $file
337
 * @param string $category
338
 * @param boolean $throwOnMissingFile
339
 * @return array array(type, case)
340
 * @throws Exception
341
 */
342
	protected function _mapFileToCase($file, $category, $throwOnMissingFile = true) {
343
		if (!$category || (substr($file, -4) !== '.php')) {
344
			return false;
345
		}
346
 
347
		$_file = realpath($file);
348
		if ($_file) {
349
			$file = $_file;
350
		}
351
 
352
		$testFile = $testCase = null;
353
 
354
		if (preg_match('@Test[\\\/]@', $file)) {
355
 
356
			if (substr($file, -8) === 'Test.php') {
357
 
358
				$testCase = substr($file, 0, -8);
359
				$testCase = str_replace(DS, '/', $testCase);
360
 
361
				if ($testCase = preg_replace('@.*Test\/Case\/@', '', $testCase)) {
362
 
363
					if ($category === 'core') {
364
						$testCase = str_replace('lib/Cake', '', $testCase);
365
					}
366
 
367
					return $testCase;
368
				}
369
 
370
				throw new Exception(__d('cake_dev', 'Test case %s cannot be run via this shell', $testFile));
371
			}
372
		}
373
 
374
		$file = substr($file, 0, -4);
375
		if ($category === 'core') {
376
 
377
			$testCase = str_replace(DS, '/', $file);
378
			$testCase = preg_replace('@.*lib/Cake/@', '', $file);
379
			$testCase[0] = strtoupper($testCase[0]);
380
			$testFile = CAKE . 'Test/Case/' . $testCase . 'Test.php';
381
 
382
			if (!file_exists($testFile) && $throwOnMissingFile) {
383
				throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile));
384
			}
385
 
386
			return $testCase;
387
		}
388
 
389
		if ($category === 'app') {
390
			$testFile = str_replace(APP, APP . 'Test/Case/', $file) . 'Test.php';
391
		} else {
392
			$testFile = preg_replace(
393
				"@((?:plugins|Plugin)[\\/]{$category}[\\/])(.*)$@",
394
				'\1Test/Case/\2Test.php',
395
				$file
396
			);
397
		}
398
 
399
		if (!file_exists($testFile) && $throwOnMissingFile) {
400
			throw new Exception(__d('cake_dev', 'Test case %s not found', $testFile));
401
		}
402
 
403
		$testCase = substr($testFile, 0, -8);
404
		$testCase = str_replace(DS, '/', $testCase);
405
		$testCase = preg_replace('@.*Test/Case/@', '', $testCase);
406
 
407
		return $testCase;
408
	}
409
 
410
/**
411
 * For the given file, what category of test is it? returns app, core or the name of the plugin
412
 *
413
 * @param string $file
414
 * @return string
415
 */
416
	protected function _mapFileToCategory($file) {
417
		$_file = realpath($file);
418
		if ($_file) {
419
			$file = $_file;
420
		}
421
 
422
		$file = str_replace(DS, '/', $file);
423
		if (strpos($file, 'lib/Cake/') !== false) {
424
			return 'core';
425
		} elseif (preg_match('@(?:plugins|Plugin)/([^/]*)@', $file, $match)) {
426
			return $match[1];
427
		}
428
		return 'app';
429
	}
430
 
431
}