Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Refactoring ShellDispatcher::dispatch

Updating dispatch method to use is_a fixes #5318
Adding _getShell method to ease testing
Updating status code handling in the constructor
Adding and updating tests
  • Loading branch information...
commit 4b89dd2209a7a5ee9c5833ce15f0c31351a12497 1 parent 0ee8889
@davidpersson davidpersson authored
Showing with 471 additions and 108 deletions.
  1. +131 −98 cake/console/cake.php
  2. +340 −10 cake/tests/cases/console/cake.test.php
View
229 cake/console/cake.php
@@ -120,15 +120,21 @@ function ShellDispatcher($args = array()) {
/**
* Constructor
*
- * @param array $args the argv.
+ * The execution of the script is stopped after dispatching the request with
+ * a status code of either 0 or 1 according to the result of the dispatch.
+ *
+ * @param array $args the argv
+ * @return void
+ * @access public
*/
function __construct($args = array()) {
set_time_limit(0);
+
$this->__initConstants();
$this->parseParams($args);
$this->_initEnvironment();
$this->__buildPaths();
- $this->_stop($this->dispatch());
+ $this->_stop($this->dispatch() === false ? 1 : 0);
}
/**
* Defines core configuration.
@@ -269,118 +275,145 @@ function __bootstrap() {
/**
* Dispatches a CLI request
*
+ * @return boolean
* @access public
*/
function dispatch() {
- if (isset($this->args[0])) {
+ if (!$arg = array_shift($this->args)) {
+ $this->help();
+ return false;
+ }
+ if ($arg == 'help') {
+ $this->help();
+ return true;
+ }
+
+ if (strpos($arg, '.') !== false) {
+ list($plugin, $shell) = explode('.', $arg);
+ } else {
$plugin = null;
- $shell = $this->args[0];
- if (strpos($shell, '.') !== false) {
- list($plugin, $shell) = explode('.', $this->args[0]);
+ $shell = $arg;
+ }
+ $this->shell = $shell;
+ $this->shellName = Inflector::camelize($shell);
+ $this->shellClass = $this->shellName . 'Shell';
+
+ if ($arg = array_shift($this->args)) {
+ $this->shellCommand = Inflector::variable($arg);
+ }
+
+ $Shell = $this->_getShell($plugin);
+
+ if (!$Shell) {
+ $message = sprintf(__('Class `%s` could not be loaded', true), $this->shellClass);
+ $this->stderr($message . "\n");
+ return false;
+ }
+
+ if (is_a($Shell, 'Shell')) {
+ $Shell->initialize();
+ $Shell->loadTasks();
+
+ foreach ($Shell->taskNames as $task) {
+ if (is_a($Shell->{$task}, 'Shell')) {
+ $Shell->{$task}->initialize();
+ $Shell->{$task}->loadTasks();
+ }
}
- $this->shell = $shell;
- $this->shiftArgs();
- $this->shellName = Inflector::camelize($this->shell);
- $this->shellClass = $this->shellName . 'Shell';
+ $task = Inflector::camelize($this->shellCommand);
- if ($this->shell === 'help') {
- $this->help();
- } else {
- $loaded = false;
- foreach ($this->shellPaths as $path) {
- $this->shellPath = $path . $this->shell . '.php';
-
- $isPlugin = ($plugin && strpos($path, DS . $plugin . DS . 'vendors' . DS . 'shells' . DS) !== false);
- if (($isPlugin && file_exists($this->shellPath)) || (!$plugin && file_exists($this->shellPath))) {
- $loaded = true;
- break;
+ if (in_array($task, $Shell->taskNames)) {
+ $this->shiftArgs();
+ $Shell->{$task}->startup();
+
+ if (isset($this->args[0]) && $this->args[0] == 'help') {
+ if (method_exists($Shell->{$task}, 'help')) {
+ $Shell->{$task}->help();
+ } else {
+ $this->help();
}
+ return true;
}
+ return $Shell->{$task}->execute();
+ }
- if ($loaded) {
- if (!class_exists('Shell')) {
- require CONSOLE_LIBS . 'shell.php';
- }
- require $this->shellPath;
- if (class_exists($this->shellClass)) {
- $command = null;
- if (isset($this->args[0])) {
- $command = $this->args[0];
- }
- $this->shellCommand = Inflector::variable($command);
- $shell = new $this->shellClass($this);
-
- if (strtolower(get_parent_class($shell)) == 'shell') {
- $shell->initialize();
- $shell->loadTasks();
-
- foreach ($shell->taskNames as $task) {
- if (strtolower(get_parent_class($shell)) == 'shell') {
- $shell->{$task}->initialize();
- $shell->{$task}->loadTasks();
- }
- }
-
- $task = Inflector::camelize($command);
- if (in_array($task, $shell->taskNames)) {
- $this->shiftArgs();
- $shell->{$task}->startup();
- if (isset($this->args[0]) && $this->args[0] == 'help') {
- if (method_exists($shell->{$task}, 'help')) {
- $shell->{$task}->help();
- $this->_stop();
- } else {
- $this->help();
- }
- }
- return $shell->{$task}->execute();
- }
- }
+ }
- $classMethods = get_class_methods($shell);
+ $classMethods = get_class_methods($Shell);
- $privateMethod = $missingCommand = false;
- if ((in_array($command, $classMethods) || in_array(strtolower($command), $classMethods)) && strpos($command, '_', 0) === 0) {
- $privateMethod = true;
- }
+ $privateMethod = $missingCommand = false;
+ if ((in_array($arg, $classMethods) || in_array(strtolower($arg), $classMethods))
+ && $arg[0] == '_') {
+ $privateMethod = true;
+ }
- if (!in_array($command, $classMethods) && !in_array(strtolower($command), $classMethods)) {
- $missingCommand = true;
- }
+ if (!in_array($arg, $classMethods) && !in_array(strtolower($arg), $classMethods)) {
+ $missingCommand = true;
+ }
- $protectedCommands = array(
- 'initialize','in','out','err','hr',
- 'createfile', 'isdir','copydir','object','tostring',
- 'requestaction','log','cakeerror', 'shelldispatcher',
- '__initconstants','__initenvironment','__construct',
- 'dispatch','__bootstrap','getinput','stdout','stderr','parseparams','shiftargs'
- );
+ $protectedCommands = array(
+ 'initialize','in','out','err','hr',
+ 'createfile', 'isdir','copydir','object','tostring',
+ 'requestaction','log','cakeerror', 'shelldispatcher',
+ '__initconstants','__initenvironment','__construct',
+ 'dispatch','__bootstrap','getinput','stdout','stderr','parseparams','shiftargs'
+ );
- if (in_array(strtolower($command), $protectedCommands)) {
- $missingCommand = true;
- }
+ if (in_array(strtolower($arg), $protectedCommands)) {
+ $missingCommand = true;
+ }
- if ($missingCommand && method_exists($shell, 'main')) {
- $shell->startup();
- return $shell->main();
- } elseif (!$privateMethod && method_exists($shell, $command)) {
- $this->shiftArgs();
- $shell->startup();
- return $shell->{$command}();
- } else {
- $this->stderr("Unknown {$this->shellName} command '$command'.\nFor usage, try 'cake {$this->shell} help'.\n\n");
- }
- } else {
- $this->stderr('Class '.$this->shellClass.' could not be loaded');
- }
- } else {
- $this->help();
- }
+ if ($missingCommand && method_exists($Shell, 'main')) {
+ $Shell->startup();
+ return $Shell->main();
+ } elseif (!$privateMethod && method_exists($Shell, $arg)) {
+ $this->shiftArgs();
+ $Shell->startup();
+ return $Shell->{$arg}();
+ }
+
+ $message = sprintf(__('Unknown %1$s command `%2$s`. For usage try `cake %3$s help`.', true),
+ $this->shellName, $this->shellCommand, $this->shell);
+ $this->stderr($message . "\n");
+ return false;
+ }
+/**
+ * Get shell to use, either plugin shell or application shell
+ *
+ * All paths in the shellPaths property are searched.
+ * shell, shellPath and shellClass properties are taken into account.
+ *
+ * @param string $plugin Optionally the name of a plugin
+ * @return mixed False if no shell could be found or an object on success
+ * @access protected
+ */
+ function _getShell($plugin = null) {
+ foreach ($this->shellPaths as $path) {
+ $this->shellPath = $path . $this->shell . '.php';
+ $pluginShellPath = DS . $plugin . DS . 'vendors' . DS . 'shells' . DS;
+
+ if ((strpos($path, $pluginShellPath) !== false || !$plugin) && file_exists($this->shellPath)) {
+ $loaded = true;
+ break;
}
- } else {
- $this->help();
}
+ if (!isset($loaded)) {
+ return false;
+ }
+
+ if (!class_exists('Shell')) {
+ require CONSOLE_LIBS . 'shell.php';
+ }
+
+ if (!class_exists($this->shellClass)) {
+ require $this->shellPath;
+ }
+ if (!class_exists($this->shellClass)) {
+ return false;
+ }
+ $Shell = new $this->shellClass($this);
+ return $Shell;
}
/**
* Prompts the user for input, and returns it.
@@ -480,7 +513,7 @@ function parseParams($params) {
$this->params = array_merge($this->params, $params);
}
/**
- * Helper for recursively paraing params
+ * Helper for recursively parsing params
*
* @return array params
* @access private
@@ -559,6 +592,7 @@ function help() {
} else {
sort($shells);
foreach ($shells as $shell) {
+
if ($shell !== 'shell.php') {
$this->stdout("\t " . str_replace('.php', '', $shell));
}
@@ -568,7 +602,6 @@ function help() {
}
$this->stdout("\nTo run a command, type 'cake shell_name [args]'");
$this->stdout("To get help on a specific command, type 'cake shell_name help'");
- $this->_stop();
}
/**
* Stop execution of the current script
View
350 cake/tests/cases/console/cake.test.php
@@ -34,6 +34,8 @@
require CAKE . 'console' . DS . 'cake.php';
ob_end_clean();
}
+
+require_once CONSOLE_LIBS . 'shell.php';
/**
* TestShellDispatcher class
*
@@ -70,6 +72,13 @@ class TestShellDispatcher extends ShellDispatcher {
*/
var $stopped = null;
/**
+ * TestShell
+ *
+ * @var mixed
+ * @access public
+ */
+ var $TestShell;
+/**
* _initEnvironment method
*
* @access protected
@@ -107,6 +116,30 @@ function stdout($string, $newline = true) {
*/
function _stop($status = 0) {
$this->stopped = 'Stopped with status: ' . $status;
+ return $status;
+ }
+/**
+ * getShell
+ *
+ * @param mixed $plugin
+ * @access public
+ * @return mixed
+ */
+ function getShell($plugin = null) {
+ return $this->_getShell($plugin);
+ }
+/**
+ * _getShell
+ *
+ * @param mixed $plugin
+ * @access protected
+ * @return mixed
+ */
+ function _getShell($plugin = null) {
+ if (isset($this->TestShell)) {
+ return $this->TestShell;
+ }
+ return parent::_getShell($plugin);
}
}
/**
@@ -115,7 +148,7 @@ function _stop($status = 0) {
* @package cake
* @subpackage cake.tests.cases.libs
*/
-class ShellDispatcherTest extends UnitTestCase {
+class ShellDispatcherTest extends CakeTestCase {
/**
* setUp method
*
@@ -419,20 +452,317 @@ function testBuildPaths() {
$this->assertIdentical(array_diff($expected, $result), array());
}
/**
- * testDispatch method
+ * Verify loading of (plugin-) shells
*
* @access public
* @return void
*/
- function testDispatch() {
- $Dispatcher =& new TestShellDispatcher(array('sample'));
- $this->assertPattern('/This is the main method called from SampleShell/', $Dispatcher->stdout);
+ function testGetShell() {
+ $this->skipIf(class_exists('SampleShell'), '%s SampleShell Class already loaded');
+ $this->skipIf(class_exists('ExampleShell'), '%s ExampleShell Class already loaded');
+
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Dispatcher->shell = 'sample';
+ $Dispatcher->shellName = 'Sample';
+ $Dispatcher->shellClass = 'SampleShell';
- $Dispatcher =& new TestShellDispatcher(array('test_plugin_two.example'));
- $this->assertPattern('/This is the main method called from TestPluginTwo.ExampleShell/', $Dispatcher->stdout);
+ $result = $Dispatcher->getShell();
+ $this->assertIsA($result, 'SampleShell');
- $Dispatcher =& new TestShellDispatcher(array('test_plugin_two.welcome', 'say_hello'));
- $this->assertPattern('/This is the say_hello method called from TestPluginTwo.WelcomeShell/', $Dispatcher->stdout);
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Dispatcher->shell = 'example';
+ $Dispatcher->shellName = 'Example';
+ $Dispatcher->shellClass = 'ExampleShell';
+
+ $result = $Dispatcher->getShell('test_plugin');
+ $this->assertIsA($result, 'ExampleShell');
+ }
+/**
+ * Verify correct dispatch of Shell subclasses with a main method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatchShellWithMain() {
+ Mock::generate('Shell', 'MockWithMainShell', array('main', '_secret'));
+
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Shell = new MockWithMainShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('initialize');
+ $Shell->expectOnce('loadTasks');
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main', 'initdb'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectNever('hr');
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main', 'hr'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main', 'dispatch'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main', 'idontexist'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainShell();
+ $Shell->expectNever('startup');
+ $Shell->expectNever('main');
+ $Shell->expectNever('_secret');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main', '_secret'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+ }
+/**
+ * Verify correct dispatch of Shell subclasses without a main method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatchShellWithoutMain() {
+ Mock::generate('Shell', 'MockWithoutMainShell', array('initDb', '_secret'));
+
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Shell = new MockWithoutMainShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectOnce('initialize');
+ $Shell->expectOnce('loadTasks');
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('initDb');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main', 'initdb'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ /* Currently fails when it should not */
+ /* $Shell = new MockWithoutMainShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectNever('startup');
+ $Shell->expectNever('hr');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main', 'hr'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result); */
+
+ $Shell = new MockWithoutMainShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main', 'dispatch'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainShell();
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main', 'idontexist'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainShell();
+ $Shell->expectNever('startup');
+ $Shell->expectNever('_secret');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main', '_secret'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+ }
+/**
+ * Verify correct dispatch of custom classes with a main method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatchNotAShellWithMain() {
+ Mock::generate('Object', 'MockWithMainNotAShell',
+ array('main', 'initialize', 'loadTasks', 'startup', '_secret'));
+
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectNever('initialize');
+ $Shell->expectNever('loadTasks');
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a', 'initdb'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a', 'hr'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a', 'dispatch'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->setReturnValue('main', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('main');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a', 'idontexist'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithMainNotAShell();
+ $Shell->expectNever('startup');
+ $Shell->expectNever('main');
+ $Shell->expectNever('_secret');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_with_main_not_a', '_secret'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+ }
+/**
+ * Verify correct dispatch of custom classes without a main method
+ *
+ * @access public
+ * @return void
+ */
+ function testDispatchNotAShellWithoutMain() {
+ Mock::generate('Object', 'MockWithoutMainNotAShell',
+ array('initDb', 'initialize', 'loadTasks', 'startup', '_secret'));
+
+ $Dispatcher =& new TestShellDispatcher();
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectNever('initialize');
+ $Shell->expectNever('loadTasks');
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectOnce('startup');
+ $Shell->expectOnce('initDb');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a', 'initdb'));
+ $result = $Dispatcher->dispatch();
+ $this->assertTrue($result);
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a', 'hr'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->setReturnValue('initDb', true);
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a', 'dispatch'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->expectNever('startup');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a', 'idontexist'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
+
+ $Shell = new MockWithoutMainNotAShell();
+ $Shell->expectNever('startup');
+ $Shell->expectNever('_secret');
+ $Dispatcher->TestShell =& $Shell;
+
+ $Dispatcher->parseParams(array('mock_without_main_not_a', '_secret'));
+ $result = $Dispatcher->dispatch();
+ $this->assertFalse($result);
}
/**
* testHelpCommand method
@@ -476,7 +806,7 @@ function testHelpCommand() {
$expected = "/ CORE(\\\|\/)tests(\\\|\/)test_app(\\\|\/)vendors(\\\|\/)shells:";
$expected .= "\n\t sample";
$expected .= "\n/";
- $this->assertPattern($expected, $Dispatcher->stdout);
+ $this->assertPattern($expected, $Dispatcher->stdout);
}
}
?>

0 comments on commit 4b89dd2

Please sign in to comment.
Something went wrong with that request. Please try again.