Browse Source

Start implementation of CommandRunner.

Starting off with all the bad things before attempting to write happy
paths.
Mark Story 8 years ago
parent
commit
73a85be90d

+ 86 - 0
src/Console/CommandRunner.php

@@ -0,0 +1,86 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Console;
+
+use Cake\Console\CommandCollection;
+use Cake\Http\BaseApplication;
+use RuntimeException;
+
+/**
+ * Run CLI commands for the provided application.
+ */
+class CommandRunner
+{
+    protected $app, $root;
+
+    /**
+     * Constructor
+     *
+     * @param \Cake\Http\BaseApplication $app The application to run CLI commands for.
+     * @param string $root The root command name to be removed from argv.
+     */
+    public function __construct(BaseApplication $app, $root = 'cake')
+    {
+        $this->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."
+            );
+        }
+    }
+}

+ 14 - 0
src/Http/BaseApplication.php

@@ -75,6 +75,20 @@ abstract class BaseApplication
     }
 
     /**
+     * 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.
      *
      * - Convert the PSR response into CakePHP equivalents.

+ 146 - 0
tests/TestCase/Console/CommandRunnerTest.php

@@ -0,0 +1,146 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.5.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\Console;
+
+use Cake\Console\CommandCollection;
+use Cake\Console\CommandRunner;
+use Cake\Core\Configure;
+use Cake\Http\BaseApplication;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Test case for the CommandCollection
+ */
+class CommandRunnerTest extends TestCase
+{
+    public function setUp()
+    {
+        parent::setUp();
+        Configure::write('App.namespace', 'TestApp');
+        $this->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();
+    }
+}