Skip to content

Commit

Permalink
Merge pull request #10816 from jeremyharris/cli-integrationtest
Browse files Browse the repository at this point in the history
3.next RFC ConsoleIntegrationTestCase
  • Loading branch information
markstory committed Jul 2, 2017
2 parents 1e51bbe + 5aeb621 commit 3a5a10c
Show file tree
Hide file tree
Showing 7 changed files with 630 additions and 3 deletions.
264 changes: 264 additions & 0 deletions src/TestSuite/ConsoleIntegrationTestCase.php
@@ -0,0 +1,264 @@
<?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)
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\TestSuite;

use Cake\Console\CommandRunner;
use Cake\Console\ConsoleInput;
use Cake\Console\ConsoleIo;
use Cake\Core\Configure;
use Cake\TestSuite\Stub\ConsoleOutput;

/**
* A test case class intended to make integration tests of cake console commands
* easier.
*/
class ConsoleIntegrationTestCase extends TestCase
{
/**
* Whether or not to use the CommandRunner
*
* @var bool
*/
protected $_useCommandRunner = false;

/**
* Last exit code
*
* @var int
*/
protected $_exitCode;

/**
* Console output stub
*
* @var ConsoleOutput
*/
protected $_out;

/**
* Console error output stub
*
* @var ConsoleOutput
*/
protected $_err;

/**
* Console input mock
*
* @var ConsoleInput
*/
protected $_in;

/**
* Runs cli integration test
*
* @param string $command Command to run
* @param array $input Input values to pass to an interactive shell
* @return void
*/
public function exec($command, array $input = [])
{
$runner = $this->_makeRunner();

$this->_out = new ConsoleOutput();
$this->_err = new ConsoleOutput();
$this->_in = $this->getMockBuilder(ConsoleInput::class)
->disableOriginalConstructor()
->setMethods(['read'])
->getMock();

$i = 0;
foreach ($input as $in) {
$this->_in
->expects($this->at($i++))
->method('read')
->will($this->returnValue($in));
}

$args = $this->_commandStringToArgs("cake $command");

$io = new ConsoleIo($this->_out, $this->_err, $this->_in);

$this->_exitCode = $runner->run($args, $io);
}

/**
* tearDown
*
* @return void
*/
public function tearDown()
{
parent::tearDown();

$this->_exitCode = null;
$this->_out = null;
$this->_err = null;
$this->_in = null;
$this->_useCommandRunner = false;
}

/**
* Set this test case to use the CommandRunner rather than the legacy
* ShellDispatcher
*
* @return void
*/
public function enableCommandRunner()
{
$this->_useCommandRunner = true;
}

/**
* Asserts shell exited with the expected code
*
* @param int $expected Expected exit code
* @param string $message Failure message to be appended to the generated message
* @return void
*/
public function assertExitCode($expected, $message = '')
{
$message = sprintf(
'Shell exited with code %d instead of the expected code %d. %s',
$this->_exitCode,
$expected,
$message
);
$this->assertSame($expected, $this->_exitCode, $message);
}

/**
* Asserts `stdout` contains expected output
*
* @param string $expected Expected output
* @param string $message Failure message
* @return void
*/
public function assertOutputContains($expected, $message = '')
{
$output = implode(PHP_EOL, $this->_out->messages());
$this->assertContains($expected, $output, $message);
}

/**
* Asserts `stdout` contains expected regexp
*
* @param string $pattern Expected pattern
* @param string $message Failure message
* @return void
*/
public function assertOutputRegExp($pattern, $message = '')
{
$output = implode(PHP_EOL, $this->_out->messages());
$this->assertRegExp($pattern, $output, $message);
}

/**
* Asserts `stderr` contains expected output
*
* @param string $expected Expected output
* @param string $message Failure message
* @return void
*/
public function assertErrorContains($expected, $message = '')
{
$output = implode(PHP_EOL, $this->_err->messages());
$this->assertContains($expected, $output, $message);
}

/**
* Asserts `stderr` contains expected regexp
*
* @param string $pattern Expected pattern
* @param string $message Failure message
* @return void
*/
public function assertErrorRegExp($pattern, $message = '')
{
$output = implode(PHP_EOL, $this->_err->messages());
$this->assertRegExp($pattern, $output, $message);
}

/**
* Builds the appropriate command dispatcher
*
* @return CommandRunner|LegacyCommandRunner
*/
protected function _makeRunner()
{
if ($this->_useCommandRunner) {
$applicationClassName = Configure::read('App.namespace') . '\Application';

return new CommandRunner(new $applicationClassName([CONFIG]));
}

return new LegacyCommandRunner();
}

/**
* Creates an $argv array from a command string
*
* @param string $command Command string
* @return array
*/
protected function _commandStringToArgs($command)
{
$charCount = strlen($command);
$argv = [];
$arg = '';
$inDQuote = false;
$inSQuote = false;
for ($i = 0; $i < $charCount; $i++) {
$char = substr($command, $i, 1);

// end of argument
if ($char === ' ' && !$inDQuote && !$inSQuote) {
if (strlen($arg)) {
$argv[] = $arg;
}
$arg = '';
continue;
}

// exiting single quote
if ($inSQuote && $char === "'") {
$inSQuote = false;
continue;
}

// exiting double quote
if ($inDQuote && $char === '"') {
$inDQuote = false;
continue;
}

// entering double quote
if ($char === '"' && !$inSQuote) {
$inDQuote = true;
continue;
}

// entering single quote
if ($char === "'" && !$inDQuote) {
$inSQuote = true;
continue;
}

$arg .= $char;
}
$argv[] = $arg;

return $argv;
}
}
42 changes: 42 additions & 0 deletions src/TestSuite/LegacyCommandRunner.php
@@ -0,0 +1,42 @@
<?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)
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\TestSuite;

use Cake\Console\ConsoleIo;

/**
* Class that dispatches to the legacy ShellDispatcher using the same signature
* as the newer CommandRunner
*/
class LegacyCommandRunner
{
/**
* @var \Cake\Console\ConsoleIo
*/
protected $_io;

/**
* Mimics functionality of Cake\Console\CommandRunner
*
* @param array $argv Argument array
* @param ConsoleIo $io ConsoleIo
* @return int
*/
public function run(array $argv, ConsoleIo $io = null)
{
$dispatcher = new LegacyShellDispatcher($argv, true, $io);

return $dispatcher->dispatch();
}
}
56 changes: 56 additions & 0 deletions src/TestSuite/LegacyShellDispatcher.php
@@ -0,0 +1,56 @@
<?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)
* @since 3.5.0
* @license http://www.opensource.org/licenses/mit-license.php MIT License
*/
namespace Cake\TestSuite;

use Cake\Console\ShellDispatcher;

/**
* Allows injecting mock IO into shells
*/
class LegacyShellDispatcher extends ShellDispatcher
{
/**
* @var \Cake\Console\ConsoleIo
*/
protected $_io;

/**
* Constructor
*
* @param array $args Argument array
* @param bool $bootstrap Initialize environment
* @param \Cake\Console\ConsoleIo $io ConsoleIo
*/
public function __construct($args = [], $bootstrap = true, $io = null)
{
$this->_io = $io;
parent::__construct($args, $bootstrap);
}

/**
* Injects mock and stub io components into the shell
*
* @param string $className Class name
* @param string $shortName Short name
* @return \Cake\Console\Shell
*/
protected function _createShell($className, $shortName)
{
list($plugin) = pluginSplit($shortName);
$instance = new $className($this->_io);
$instance->plugin = trim($plugin, '.');

return $instance;
}
}
4 changes: 2 additions & 2 deletions tests/TestCase/Shell/CommandListShellTest.php
Expand Up @@ -82,7 +82,7 @@ public function testMain()
$expected = "/\[.*CORE.*\] cache, help, i18n, orm_cache, plugin, routes, server/";
$this->assertRegExp($expected, $output);

$expected = "/\[.*app.*\] i18m, sample/";
$expected = "/\[.*app.*\] i18m, integration, sample/";
$this->assertRegExp($expected, $output);
}

Expand All @@ -103,7 +103,7 @@ public function testMainAppPriority()
$expected = "/\[.*CORE.*\] cache, help, orm_cache, plugin, routes, server/";
$this->assertRegExp($expected, $output);

$expected = "/\[.*app.*\] i18n, sample/";
$expected = "/\[.*app.*\] i18n, integration, sample/";
$this->assertRegExp($expected, $output);
}

Expand Down
2 changes: 1 addition & 1 deletion tests/TestCase/Shell/CompletionShellTest.php
Expand Up @@ -116,7 +116,7 @@ public function testCommands()
$output = $this->out->output;

$expected = 'TestPlugin.example TestPlugin.sample TestPluginTwo.example unique welcome ' .
"cache help i18n orm_cache plugin routes server version i18m sample testing_dispatch\n";
"cache help i18n orm_cache plugin routes server version i18m integration sample testing_dispatch\n";
$this->assertTextEquals($expected, $output);
}

Expand Down

0 comments on commit 3a5a10c

Please sign in to comment.