From 4b89dd2209a7a5ee9c5833ce15f0c31351a12497 Mon Sep 17 00:00:00 2001 From: davidpersson Date: Thu, 30 Apr 2009 19:25:58 +0200 Subject: [PATCH] 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 --- cake/console/cake.php | 229 +++++++++------- cake/tests/cases/console/cake.test.php | 350 ++++++++++++++++++++++++- 2 files changed, 471 insertions(+), 108 deletions(-) diff --git a/cake/console/cake.php b/cake/console/cake.php index 0866d53b61b..22683ad8b6c 100644 --- a/cake/console/cake.php +++ b/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 diff --git a/cake/tests/cases/console/cake.test.php b/cake/tests/cases/console/cake.test.php index 9ec2e57ddf1..2c87d6628ed 100644 --- a/cake/tests/cases/console/cake.test.php +++ b/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 * @@ -69,6 +71,13 @@ class TestShellDispatcher extends ShellDispatcher { * @access public */ var $stopped = null; +/** + * TestShell + * + * @var mixed + * @access public + */ + var $TestShell; /** * _initEnvironment method * @@ -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); } } ?> \ No newline at end of file