Subversion Repositories SmartDukaan

Rev

Details | Last modification | View Log | RSS feed

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