From 73a85be90dfeca181a735b7689c18fbafb22a4e3 Mon Sep 17 00:00:00 2001 From: Mark Story Date: Mon, 19 Jun 2017 23:02:43 -0400 Subject: [PATCH] Start implementation of CommandRunner. Starting off with all the bad things before attempting to write happy paths. --- src/Console/CommandRunner.php | 86 +++++++++++ src/Http/BaseApplication.php | 14 ++ tests/TestCase/Console/CommandRunnerTest.php | 146 +++++++++++++++++++ 3 files changed, 246 insertions(+) create mode 100644 src/Console/CommandRunner.php create mode 100644 tests/TestCase/Console/CommandRunnerTest.php diff --git a/src/Console/CommandRunner.php b/src/Console/CommandRunner.php new file mode 100644 index 00000000000..624218329e0 --- /dev/null +++ b/src/Console/CommandRunner.php @@ -0,0 +1,86 @@ +app = $app; + $this->root = $root; + } + + /** + * Run the command contained in $argv. + * + * @param array $argv The arguments from the CLI environment. + * @return int The exit code of the command. + * @throws \RuntimeException + */ + public function run(array $argv) + { + $this->app->bootstrap(); + + $commands = $this->app->console(new CommandCollection()); + if (!($commands instanceof CommandCollection)) { + $type = is_object($commands) ? get_class($commands) : gettype($commands); + throw new RuntimeException( + "The application's `console` method did not return a CommandCollection." . + " Got '{$type}' instead." + ); + } + if (empty($argv) || $argv[0] !== $this->root) { + $command = empty($argv) ? '' : " `{$argv[0]}`"; + throw new RuntimeException( + "Unknown root command{$command}. Was expecting `{$this->root}`." + ); + } + // Remove the root command + array_shift($argv); + + $shell = $this->getShell($commands, $argv); + } + + /** + * Get the shell instance for the argv list. + * + * @return \Cake\Console\Shell + */ + protected function getShell(CommandCollection $commands, array $argv) + { + $command = array_shift($argv); + if (!$commands->has($command)) { + throw new RuntimeException( + "Unknown command `{$this->root} {$command}`." . + " Run `{$this->root} --help` to get the list of valid commands." + ); + } + } +} diff --git a/src/Http/BaseApplication.php b/src/Http/BaseApplication.php index f591f3e7041..e4e67f587b0 100644 --- a/src/Http/BaseApplication.php +++ b/src/Http/BaseApplication.php @@ -74,6 +74,20 @@ public function routes($routes) require $this->configDir . '/routes.php'; } + /** + * Define the console commands for an application. + * + * By default all commands in CakePHP, plugins and the application will be + * loaded using conventions based names. + * + * @param \Cake\Console\CommandCollection $commands The CommandCollection to add commands into. + * @return \Cake\Console\CommandCollection The updated collection. + */ + public function console($commands) + { + return $commands->addMany($commands->autoDiscover()); + } + /** * Invoke the application. * diff --git a/tests/TestCase/Console/CommandRunnerTest.php b/tests/TestCase/Console/CommandRunnerTest.php new file mode 100644 index 00000000000..e3928ab6968 --- /dev/null +++ b/tests/TestCase/Console/CommandRunnerTest.php @@ -0,0 +1,146 @@ +config = dirname(dirname(__DIR__)); + } + + /** + * Test that the console hook not returning a command collection + * raises an error. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage The application's `console` method did not return a CommandCollection. + * @return void + */ + public function testRunConsoleHookFailure() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['console', 'middleware', 'bootstrap']) + ->setConstructorArgs([$this->config]) + ->getMock(); + $runner = new CommandRunner($app); + $runner->run(['cake', '-h']); + } + + /** + * Test that running with empty argv fails + * + * @expectedException \RuntimeException + * @expectedExceptionMessage Unknown root command. Was expecting `cake` + * @return void + */ + public function testRunMissingRootCommand() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $runner = new CommandRunner($app); + $runner->run([]); + } + + /** + * Test that running an unknown command raises an error. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage Unknown root command `bad`. Was expecting `cake` + * @return void + */ + public function testRunInvalidRootCommand() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $runner = new CommandRunner($app); + $runner->run(['bad', 'i18n']); + } + + /** + * Test that running an unknown command raises an error. + * + * @expectedException \RuntimeException + * @expectedExceptionMessage Unknown command `cake nope`. Run `cake --help` to get the list of valid commands. + * @return void + */ + public function testRunInvalidCommand() + { + $app = $this->getMockBuilder(BaseApplication::class) + ->setMethods(['middleware', 'bootstrap']) + ->setConstructorArgs([$this->config]) + ->getMock(); + + $runner = new CommandRunner($app); + $runner->run(['cake', 'nope', 'nope', 'nope']); + } + + /** + * Test using `cake --help` invokes the help command + * + * @return void + */ + public function testRunHelpLongOption() + { + $this->markTestIncomplete(); + } + + /** + * Test using `cake -h` invokes the help command + * + * @return void + */ + public function testRunHelpShortOption() + { + $this->markTestIncomplete(); + } + + /** + * Test using `cake --verson` invokes the version command + * + * @return void + */ + public function testRunVersionLongOption() + { + $this->markTestIncomplete(); + } + + /** + * Test using `cake -v` invokes the version command + * + * @return void + */ + public function testRunVersionShortOption() + { + $this->markTestIncomplete(); + } +}