diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php index 624218329e0..628fefe29f9 100644 --- a/src/Console/CommandRunner.php +++ b/src/Console/CommandRunner.php @@ -15,6 +15,9 @@ namespace Cake\Console; use Cake\Console\CommandCollection; +use Cake\Console\ConsoleIo; +use Cake\Console\Exception\StopException; +use Cake\Console\Shell; use Cake\Http\BaseApplication; use RuntimeException; @@ -41,10 +44,11 @@ public function __construct(BaseApplication $app, $root = 'cake') * Run the command contained in $argv. * * @param array $argv The arguments from the CLI environment. + * @param \Cake\Console\ConsoleIo $io The ConsoleIo instance. Used primarily for testing. * @return int The exit code of the command. * @throws \RuntimeException */ - public function run(array $argv) + public function run(array $argv, ConsoleIo $io = null) { $this->app->bootstrap(); @@ -62,10 +66,30 @@ public function run(array $argv) "Unknown root command{$command}. Was expecting `{$this->root}`." ); } - // Remove the root command + $io = $io ?: new ConsoleIo(); + + // Remove the root executable segment array_shift($argv); - $shell = $this->getShell($commands, $argv); + $shell = $this->getShell($io, $commands, $argv); + + // Remove the command name segment + array_shift($argv); + try { + $shell->initialize(); + $result = $shell->runCommand($argv, true); + } catch (StopException $e) { + return $e->getCode(); + } + + if ($result === null || $result === true) { + return Shell::CODE_SUCCESS; + } + if (is_int($result)) { + return $result; + } + + return Shell::CODE_ERROR; } /** @@ -73,7 +97,7 @@ public function run(array $argv) * * @return \Cake\Console\Shell */ - protected function getShell(CommandCollection $commands, array $argv) + protected function getShell(ConsoleIo $io, CommandCollection $commands, array $argv) { $command = array_shift($argv); if (!$commands->has($command)) { @@ -82,5 +106,10 @@ protected function getShell(CommandCollection $commands, array $argv) " Run `{$this->root} --help` to get the list of valid commands." ); } + $classOrInstance = $commands->get($command); + if (is_string($classOrInstance)) { + return new $classOrInstance($io); + } + return $classOrInstance; } } diff --git a/tests/TestCase/Console/CommandRunnerTest.php b/tests/TestCase/Console/CommandRunnerTest.php index e3928ab6968..c91726ba484 100644 --- a/tests/TestCase/Console/CommandRunnerTest.php +++ b/tests/TestCase/Console/CommandRunnerTest.php @@ -16,9 +16,12 @@ use Cake\Console\CommandCollection; use Cake\Console\CommandRunner; +use Cake\Console\ConsoleIo; +use Cake\Console\Shell; use Cake\Core\Configure; use Cake\Http\BaseApplication; use Cake\TestSuite\TestCase; +use Cake\TestSuite\Stub\ConsoleOutput; /** * Test case for the CommandCollection @@ -143,4 +146,81 @@ public function testRunVersionShortOption() { $this->markTestIncomplete(); } + + /** + * Test running a valid command + * + * @return void + */ + public function testRunValidCommand() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $output = new ConsoleOutput(); + + $runner = new CommandRunner($app, 'cake'); + $result = $runner->run(['cake', 'routes'], $this->getMockIo($output)); + $this->assertSame(Shell::CODE_SUCCESS, $result); + + $contents = implode("\n", $output->messages()); + $this->assertContains('URI template', $contents); + $this->assertContains('Welcome to CakePHP', $contents); + } + + /** + * Test running a valid raising an error + * + * @return void + */ + public function testRunValidCommandWithAbort() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap', 'console']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $commands = new CommandCollection(['failure' => 'TestApp\Shell\SampleShell']); + $app->method('console')->will($this->returnValue($commands)); + + $output = new ConsoleOutput(); + + $runner = new CommandRunner($app, 'cake'); + $result = $runner->run(['cake', 'failure', 'with_abort'], $this->getMockIo($output)); + $this->assertSame(Shell::CODE_ERROR, $result); + } + + /** + * Test returning a non-zero value + * + * @return void + */ + public function testRunValidCommandReturnInteger() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap', 'console']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $commands = new CommandCollection(['failure' => 'TestApp\Shell\SampleShell']); + $app->method('console')->will($this->returnValue($commands)); + + $output = new ConsoleOutput(); + + $runner = new CommandRunner($app, 'cake'); + $result = $runner->run(['cake', 'failure', 'returnValue'], $this->getMockIo($output)); + $this->assertSame(99, $result); + } + + protected function getMockIo($output) + { + $io = $this->getMockBuilder(ConsoleIo::class) + ->setConstructorArgs([$output, $output, null, null]) + ->setMethods(['in']) + ->getMock(); + + return $io; + } } diff --git a/tests/test_app/TestApp/Shell/SampleShell.php b/tests/test_app/TestApp/Shell/SampleShell.php index 5a6c6baa126..a39e5c749c3 100644 --- a/tests/test_app/TestApp/Shell/SampleShell.php +++ b/tests/test_app/TestApp/Shell/SampleShell.php @@ -46,4 +46,14 @@ public function derp() { $this->out('This is the example method called from TestPlugin.SampleShell'); } + + public function withAbort() + { + $this->abort('Bad things'); + } + + public function returnValue() + { + return 99; + } }