First commit

This commit is contained in:
Theodotos Andreou 2018-01-14 13:10:16 +00:00
commit c6e2478c40
13918 changed files with 2303184 additions and 0 deletions

View file

@ -0,0 +1,59 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\TestUtils\ExampleCommandInfoAlterer;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use \Consolidation\AnnotatedCommand\Parser\Internal\FullyQualifiedClassCache;
class FullyQualifiedClassCacheTests extends \PHPUnit_Framework_TestCase
{
function testFqcn()
{
$reflectionMethod = new \ReflectionMethod('\Consolidation\TestUtils\alpha\AlphaCommandFile', 'exampleTableTwo');
$filename = $reflectionMethod->getFileName();
$fqcnCache = new FullyQualifiedClassCache();
$handle = fopen($filename, "r");
$this->assertTrue($handle !== false);
$namespaceName = $this->callProtected($fqcnCache, 'readNamespace', [$handle]);
$this->assertEquals('Consolidation\TestUtils\alpha', $namespaceName);
$usedClasses = $this->callProtected($fqcnCache, 'readUseStatements', [$handle]);
$this->assertTrue(isset($usedClasses['RowsOfFields']));
$this->assertEquals('Consolidation\OutputFormatters\StructuredData\RowsOfFields', $usedClasses['RowsOfFields']);
fclose($handle);
$fqcn = $fqcnCache->qualify($filename, 'RowsOfFields');
$this->assertEquals('Consolidation\OutputFormatters\StructuredData\RowsOfFields', $fqcn);
$fqcn = $fqcnCache->qualify($filename, 'ClassWithoutUse');
$this->assertEquals('Consolidation\TestUtils\alpha\ClassWithoutUse', $fqcn);
$fqcn = $fqcnCache->qualify($filename, 'ExampleAliasedClass');
$this->assertEquals('Consolidation\TestUtils\ExampleCommandFile', $fqcn);
}
function callProtected($object, $method, $args = [])
{
$r = new \ReflectionMethod($object, $method);
$r->setAccessible(true);
return $r->invokeArgs($object, $args);
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Consolidation\TestUtils;
use Symfony\Component\Console\Application;
class ApplicationWithTerminalWidth extends Application
{
protected $width = 0;
protected $height = 0;
public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN')
{
parent::__construct($name, $version);
}
public function setWidthAndHeight($width, $height)
{
$this->width = $width;
$this->height = $height;
}
public function getTerminalDimensions()
{
return [ $this->width, $this->height ];
}
}

View file

@ -0,0 +1,63 @@
<?php
namespace Consolidation\TestUtils;
use Consolidation\AnnotatedCommand\AnnotatedCommand;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Test file used in the Annotation Factory tests. It is also
* discovered in the testCommandDiscovery() test.
*
* The testCommandDiscovery test search base is the 'src' directory;
* any command files located immediately inside the search base are
* eligible for discovery, and will be included in the search results.
*/
class ExampleAnnotatedCommand extends AnnotatedCommand
{
/**
* Do the main function of the my:cat command.
*/
public function myCat($one, $two = '', $multiple = [], $flip = false)
{
if ($flip) {
return "{$two}{$one}" . implode('', array_reverse($multiple));
}
return "{$one}{$two}" . implode('', $multiple);
}
/**
* This is the my:cat command implemented as an AnnotatedCommand subclass.
*
* This command will concatenate two parameters. If the --flip flag
* is provided, then the result is the concatenation of two and one.
*
* @command my:cat
* @arg string $one The first parameter.
* @arg string $two The other parameter.
* @default $two ''
* @option array $multiple An array of values
* @default $multiple []
* @option boolean $flip Whether or not the second parameter should come first in the result.
* @aliases c
* @usage bet alpha --flip
* Concatenate "alpha" and "bet".
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$one = $input->getArgument('one');
$two = $input->getArgument('two');
$multiple = $input->getOption('multiple');
$flip = $input->getOption('flip');
$result = $this->myCat($one, $two, $multiple, $flip);
// We could also just use $output->writeln($result) here,
// but calling processResults enables the use of output
// formatters. Note also that if you use processResults, you
// should correctly inject the command processor into your
// annotated command via AnnotatedCommand::setCommandProcessor().
return $this->processResults($input, $output, $result);
}
}

View file

@ -0,0 +1,521 @@
<?php
namespace Consolidation\TestUtils;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandError;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Event\ConsoleCommandEvent;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Test file used in the Annotation Factory tests. It is also
* discovered in the testCommandDiscovery() test.
*
* The testCommandDiscovery test search base is the 'src' directory;
* any command files located immediately inside the search base are
* eligible for discovery, and will be included in the search results.
*/
class ExampleCommandFile
{
protected $state;
protected $output;
public function __construct($state = '')
{
$this->state = $state;
}
public function setOutput($output)
{
$this->output = $output;
}
/**
* Import config from a config directory.
*
* @command config:import
* @param $label A config directory label (i.e. a key in \$config_directories array in settings.php).
* @interact-config-label
* @option preview Format for displaying proposed changes. Recognized values: list, diff.
* @option source An arbitrary directory that holds the configuration files. An alternative to label argument
* @option partial Allows for partial config imports from the source directory. Only updates and new configs will be processed with this flag (missing configs will not be deleted).
* @aliases cim,config-import
*/
public function import($label = null, $options = ['preview' => 'list', 'source' => InputOption::VALUE_REQUIRED, 'partial' => false])
{
}
/**
* Calculate the fibonacci sequence between two numbers.
*
* Graphic output will look like
* +----+---+-------------+
* | | | |
* | |-+-| |
* |----+-+-+ |
* | | |
* | | |
* | | |
* +--------+-------------+
*
* @param int $start Number to start from
* @param int $steps Number of steps to perform
* @param array $opts
* @option $graphic Display the sequence graphically using cube
* representation
*/
public function fibonacci($start, $steps, $opts = ['graphic' => false])
{
}
/**
* Code sniffer.
*
* Run the PHP Codesniffer on a file or directory.
*
* @param string $file
* A file or directory to analyze.
* @option $autofix Whether to run the automatic fixer or not.
* @option $strict Show warnings as well as errors.
* Default is to show only errors.
*/
public function sniff(
$file = 'src',
array $options = [
'autofix' => false,
'strict' => false,
]
) {
return var_export($options, true);
}
/**
* This is the my:cat command
*
* This command will concatenate two parameters. If the --flip flag
* is provided, then the result is the concatenation of two and one.
*
* @param string $one The first parameter.
* @param string $two The other parameter.
* @option boolean $flip Whether or not the second parameter should come first in the result.
* @aliases c
* @usage bet alpha --flip
* Concatenate "alpha" and "bet".
* @arbitrary This annotation is here merely as a marker used in testing.
*/
public function myCat($one, $two = '', array $options = ['flip' => false])
{
if ($options['flip']) {
return "{$two}{$one}";
}
return "{$one}{$two}";
}
/**
* @command my:repeat
*/
public function myRepeat($one, $two = '', array $options = ['repeat' => 1])
{
return str_repeat("{$one}{$two}", $options['repeat']);
}
/**
* This is the my:join command
*
* This command will join its parameters together. It can also reverse and repeat its arguments.
*
* @command my:join
* @usage a b
* Join a and b to produce "a,b"
* @usage
* Example with no parameters or options
*/
public function myJoin(array $args, array $options = ['flip' => false, 'repeat' => 1])
{
if ($options['flip']) {
$args = array_reverse($args);
}
$result = implode('', $args);
return str_repeat($result, $options['repeat']);
}
/**
* This is a command with no options
*
* This command will concatenate two parameters.
*
* @param $one The first parameter.
* @param $two The other parameter.
* @aliases nope
* @usage alpha bet
* Concatenate "alpha" and "bet".
*/
public function commandWithNoOptions($one, $two = 'default')
{
return "{$one}{$two}";
}
/**
* This command work with app's input and output
*
* @command command:with-io-parameters
*/
public function commandWithIOParameters(InputInterface $input, OutputInterface $output)
{
return $input->getFirstArgument();
}
/**
* This command has no arguments--only options
*
* Return a result only if not silent.
*
* @option silent Supress output.
*/
public function commandWithNoArguments(array $opts = ['silent|s' => false])
{
if (!$opts['silent']) {
return "Hello, world";
}
}
/**
* Shortcut on annotation
*
* This command defines the option shortcut on the annotation instead of in the options array.
*
* @param $opts The options
* @option silent|s Supress output.
*/
public function shortcutOnAnnotation(array $opts = ['silent' => false])
{
if (!$opts['silent']) {
return "Hello, world";
}
}
/**
* This is the test:arithmatic command
*
* This command will add one and two. If the --negate flag
* is provided, then the result is negated.
*
* @command test:arithmatic
* @param integer $one The first number to add.
* @param integer $two The other number to add.
* @option negate Whether or not the result should be negated.
* @aliases arithmatic
* @usage 2 2 --negate
* Add two plus two and then negate.
* @custom
* @dup one
* @dup two
*/
public function testArithmatic($one, $two = 2, array $options = ['negate' => false, 'unused' => 'bob'])
{
$result = $one + $two;
if ($options['negate']) {
$result = -$result;
}
// Integer return codes are exit codes (errors), so
// return a the result as a string so that it will be printed.
return "$result";
}
/**
* This is the test:state command
*
* This command tests to see if the state of the Commandfile instance
*/
public function testState()
{
return $this->state;
}
/**
* This is the test:passthrough command
*
* This command takes a variable number of parameters as
* an array and returns them as a csv.
*/
public function testPassthrough(array $params)
{
return implode(',', $params);
}
/**
* This command wraps its parameter in []; its alter hook
* then wraps the result in <>.
*/
public function testHook($parameter)
{
return "[$parameter]";
}
/**
* Wrap the results of test:hook in <>.
*
* @hook alter test:hook
*/
public function hookTestHook($result)
{
return "<$result>";
}
/**
* This test is very similar to the preceding test, except
* it uses an annotation hook instead of a named-function hook.
*
* @hookme
* @before >
* @after <
*/
public function testAnnotationHook($parameter)
{
return "($parameter)";
}
/**
* Wrap the results of test:hook in whatever the @before and @after
* annotations contain.
*
* @hook alter @hookme
*/
public function hookTestAnnotatedHook($result, CommandData $commandData)
{
$before = $commandData->annotationData()->get('before', '-');
$after = $commandData->annotationData()->get('after', '-');
return "$before$result$after";
}
/**
* Alter the results of the hook with its command name.
*
* @hook alter @addmycommandname
*/
public function hookAddCommandName($result, CommandData $commandData)
{
$annotationData = $commandData->annotationData();
return "$result from " . $annotationData['command'];
}
/**
* Here is a hook with an explicit command annotation that we will alter
* with the preceeding hook
*
* @command alter-me
* @addmycommandname
*/
public function alterMe()
{
return "splendiferous";
}
/**
* Here is another hook that has no command annotation that should be
* altered with the default value for the command name
*
* @addmycommandname
*/
public function alterMeToo()
{
return "fantabulous";
}
/**
* @command test:replace-command
*/
public function testReplaceCommand($value)
{
$this->output->writeln($value);
}
/**
* @hook replace-command test:replace-command
*/
public function hookTestReplaceCommandHook($value)
{
$this->output->writeln("bar");
}
/**
* @hook pre-command test:post-command
*/
public function hookTestPreCommandHook(CommandData $commandData)
{
// Use 'writeln' to detect order that hooks are called
$this->output->writeln("foo");
}
/**
* @command test:post-command
*/
public function testPostCommand($value)
{
$this->output->writeln($value);
}
/**
* @hook post-command test:post-command
*/
public function hookTestPostCommandHook($result, CommandData $commandData)
{
// Use 'writeln' to detect order that hooks are called
$this->output->writeln("baz");
}
public function testHello($who)
{
return "Hello, $who.";
}
public function testException($what)
{
throw new \Exception($what);
}
/**
* @hook init test:hello
*/
public function initializeTestHello($input, AnnotationData $annotationData)
{
$who = $input->getArgument('who');
if (!$who) {
$input->setArgument('who', 'Huey');
}
}
/**
* @hook command-event test:hello
*/
public function commandEventTestHello(ConsoleCommandEvent $event)
{
// Note that Symfony Console will not allow us to alter the
// input from this hook, so we'll just print something to
// show that this hook was executed.
$input = $event->getInput();
$who = $input->getArgument('who');
$this->output->writeln("Here comes $who!");
}
/**
* @hook interact test:hello
*/
public function interactTestHello($input, $output)
{
$who = $input->getArgument('who');
if (!$who) {
$input->setArgument('who', 'Goofey');
}
}
/**
* @hook validate test:hello
*/
public function validateTestHello($commandData)
{
$args = $commandData->arguments();
if ($args['who'] == 'Donald Duck') {
return new CommandError("I won't say hello to Donald Duck.");
}
if ($args['who'] == 'Drumph') {
throw new \Exception('Irrational value error.');
}
}
/**
* Test default values in arguments
*
* @param string|null $one
* @param string|null $two
* @return string
*/
public function defaults($one = null, $two = null)
{
if ($one && $two) {
return "$one and $two";
}
if ($one) {
return "only $one";
}
return "nothing provided";
}
/**
* @return string
*/
public function defaultOptionOne(array $options = ['foo' => '1'])
{
return "Foo is " . $options['foo'];
}
/**
* @return string
*/
public function defaultOptionTwo(array $options = ['foo' => '2'])
{
return "Foo is " . $options['foo'];
}
/**
* @return string
*/
public function defaultOptionNone(array $options = ['foo' => InputOption::VALUE_REQUIRED])
{
return "Foo is " . $options['foo'];
}
/**
* @return string
*/
public function defaultOptionalValue(array $options = ['foo' => InputOption::VALUE_OPTIONAL])
{
return "Foo is " . var_export($options['foo'], true);
}
/**
* @return string
*/
public function defaultOptionDefaultsToTrue(array $options = ['foo' => true])
{
return "Foo is " . var_export($options['foo'], true);
}
/**
* This is the test:required-array-option command
*
* This command will print all the valused of passed option
*
* @param array $opts
* @return string
*/
public function testRequiredArrayOption(array $opts = ['arr|a' => []])
{
return implode(' ', $opts['arr']);
}
/**
* This is the test:array-option command
*
* This command will print all the valused of passed option
*
* @param array $opts
* @return string
*/
public function testArrayOption(array $opts = ['arr|a' => ['1', '2', '3']])
{
return implode(' ', $opts['arr']);
}
/**
* @command global-options-only
*/
public function globalOptionsOnly($arg, array $options = [])
{
return "Arg is $arg, options[help] is " . var_export($options['help'], true) . "\n";
}
}

View file

@ -0,0 +1,15 @@
<?php
namespace Consolidation\TestUtils;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\CommandInfoAltererInterface;
class ExampleCommandInfoAlterer implements CommandInfoAltererInterface
{
public function alterCommandInfo(CommandInfo $commandInfo, $commandFileInstance)
{
if ($commandInfo->hasAnnotation('arbitrary')) {
$commandInfo->addAnnotation('dynamic', "This annotation was dynamically added by ExampleCommandInfoAlterer");
}
}
}

View file

@ -0,0 +1,41 @@
<?php
namespace Consolidation\TestUtils;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\AnnotatedCommand\CommandError;
use Consolidation\AnnotatedCommand\AnnotationData;
/**
*
*/
class ExampleHookAllCommandFile
{
public function doCat($one, $two = '', $options = ['flip' => false])
{
if ($options['flip']) {
return "{$two}{$one}";
}
return "{$one}{$two}";
}
public function doRepeat($one, $two = '', $options = ['repeat' => 1])
{
return str_repeat("{$one}{$two}", $options['repeat']);
}
/**
* This hook function does not specify which command or annotation
* it is hooking; that makes it apply to every command in the same class.
*
* @hook alter
*/
public function alterAllCommands($result)
{
if (is_string($result)) {
$result = "*** $result ***";
}
return $result;
}
}

View file

@ -0,0 +1,50 @@
<?php
namespace Consolidation\TestUtils;
use Consolidation\AnnotatedCommand\Cache\SimpleCacheInterface;
/**
* A simple in-memory cache for testing
*/
class InMemoryCacheStore implements SimpleCacheInterface
{
protected $cache;
public function __construct()
{
$this->cache = [];
}
/**
* Test for an entry from the cache
* @param string $key
* @return boolean
*/
public function has($key)
{
return array_key_exists($key, $this->cache);
}
/**
* Get an entry from the cache
* @param string $key
* @return array
*/
public function get($key)
{
if (!$this->has($key)) {
return [];
}
return $this->cache[$key];
}
/**
* Store an entry in the cache
* @param string $key
* @param array $data
*/
public function set($key, $data)
{
$this->cache[$key] = $data;
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Consolidation\TestUtils;
class TestTerminal
{
protected $width = 0;
public function __construct($width)
{
$this->width = $width;
}
public function getWidth()
{
return $this->width;
}
public function setWidth($width)
{
$this->width = $width;
}
}

View file

@ -0,0 +1,325 @@
<?php
namespace Consolidation\TestUtils\alpha;
use Consolidation\AnnotatedCommand\CommandError;
use Consolidation\OutputFormatters\StructuredData\RowsOfFields;
use Consolidation\OutputFormatters\StructuredData\AssociativeList;
use Consolidation\AnnotatedCommand\AnnotationData;
use Symfony\Component\Console\Input\InputOption;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
use Symfony\Component\Console\Command\Command;
use Consolidation\TestUtils\ExampleCommandFile as ExampleAliasedClass;
/**
* Test file used in the testCommandDiscovery() test.
*
* This commandfile is found by the test. The test search base is the
* 'src' directory, and 'alpha' is one of the search directories available
* for searching.
*/
class AlphaCommandFile implements CustomEventAwareInterface
{
use CustomEventAwareTrait;
/**
* @command always:fail
*/
public function alwaysFail()
{
return new CommandError('This command always fails.', 13);
}
public static function ignoredStaticMethod()
{
return 'This method is static; it should not generate a command.';
}
/**
* @command simulated:status
*/
public function simulatedStatus()
{
return ['status-code' => 42];
}
/**
* @command example:output
*/
public function exampleOutput()
{
return 'Hello, World.';
}
/**
* @command example:cat
*/
public function exampleCat($one, $two = '', $options = ['flip' => false])
{
if ($options['flip']) {
return "{$two}{$one}";
}
return "{$one}{$two}";
}
/**
* @command example:echo
*/
public function exampleEcho(array $args)
{
return ['item-list' => $args];
}
/**
* @command example:message
*/
public function exampleMessage()
{
return ['message' => 'Shipwrecked; send bananas.'];
}
/**
* Test command with formatters
*
* @command example:table
* @param $unused An unused argument
* @field-labels
* first: I
* second: II
* third: III
* @usage example:table --format=yml
* Show the example table in yml format.
* @usage example:table --fields=first,third
* Show only the first and third fields in the table.
* @usage example:table --fields=II,III
* Note that either the field ID or the visible field label may be used.
* @aliases extab
* @topics docs-tables
* @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields Fully-qualified class name
*/
public function exampleTable($unused = '', $options = ['format' => 'table', 'fields' => ''])
{
$outputData = [
[ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ],
[ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ],
[ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ],
[ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ],
];
return new RowsOfFields($outputData);
}
/**
* Test command with formatters using a short classname in @return
*
* @command example:table2
* @param $unused An unused argument
* @field-labels
* first: I
* second: II
* third: III
* @usage example:table --format=yml
* Show the example table in yml format.
* @usage example:table --fields=first,third
* Show only the first and third fields in the table.
* @usage example:table --fields=II,III
* Note that either the field ID or the visible field label may be used.
* @aliases extab
* @topics docs-tables
* @return RowsOfFields Short class names are converted to fqcns
*/
public function exampleTableTwo($unused = '', $options = ['format' => 'table', 'fields' => ''])
{
$outputData = [
[ 'first' => 'One', 'second' => 'Two', 'third' => 'Three' ],
[ 'first' => 'Eins', 'second' => 'Zwei', 'third' => 'Drei' ],
[ 'first' => 'Ichi', 'second' => 'Ni', 'third' => 'San' ],
[ 'first' => 'Uno', 'second' => 'Dos', 'third' => 'Tres' ],
];
return new RowsOfFields($outputData);
}
/**
* Test word wrapping
*
* @command example:wrap
* @field-labels
* first: First
* second: Second
*
* @return \Consolidation\OutputFormatters\StructuredData\RowsOfFields
*/
public function exampleWrap()
{
$data = [
[
'first' => 'This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines.',
'second' => 'This is the second column of the same table. It is also very long, and should be wrapped across multiple lines, just like the first column.',
]
];
return new RowsOfFields($data);
}
/**
* @hook option example:table
*/
public function additionalOptionForExampleTable(Command $command, AnnotationData $annotationData)
{
$command->addOption(
'dynamic',
'',
InputOption::VALUE_NONE,
'Option added by @hook option example:table'
);
}
/**
* Demonstrate an alter hook with an option
*
* @hook alter example:table
* @option french Add a row with French numbers.
* @usage example:table --french
*/
public function alterFormatters($result, CommandData $commandData)
{
if ($commandData->input()->getOption('french')) {
$result[] = [ 'first' => 'Un', 'second' => 'Deux', 'third' => 'Trois' ];
}
return $result;
}
/**
* Test command with formatters using an associative list
*
* @command example:list
* @field-labels
* sftp_command: SFTP Command
* sftp_username: SFTP Username
* sftp_host: SFTP Host
* sftp_password: SFTP Password
* sftp_url: SFTP URL
* git_command: Git Command
* git_username: Git Username
* git_host: Git Host
* git_port: Git Port
* git_url: Git URL
* mysql_command: MySQL Command
* mysql_username: MySQL Username
* mysql_host: MySQL Host
* mysql_password: MySQL Password
* mysql_url: MySQL URL
* mysql_port: MySQL Port
* mysql_database: MySQL Database
* redis_command: Redis Command
* redis_port: Redis Port
* redis_url: Redis URL
* redis_password: Redis Password
* @default-fields *_command
* @return \Consolidation\OutputFormatters\StructuredData\AssociativeList
*/
public function exampleAssociativeList()
{
$outputData = [
'sftp_command' => 'sftp -o Port=2222 dev@appserver.dev.drush.in',
'sftp_username' => 'dev',
'sftp_host' => 'appserver.dev.drush.in',
'sftp_password' => 'Use your account password',
'sftp_url' => 'sftp://dev@appserver.dev.drush.in:2222',
'git_command' => 'git clone ssh://codeserver.dev@codeserver.dev.drush.in:2222/~/repository.git wp-update',
'git_username' => 'codeserver.dev',
'git_host' => 'codeserver.dev.drush.in',
'git_port' => 2222,
'git_url' => 'ssh://codeserver.dev@codeserver.dev.drush.in:2222/~/repository.git',
'mysql_command' => 'mysql -u pantheon -p4b33cb -h dbserver.dev.drush.in -P 16191 pantheon',
'mysql_username' => 'pantheon',
'mysql_host' => 'dbserver.dev.drush.in',
'mysql_password' => '4b33cb',
'mysql_url' => 'mysql://pantheon:4b33cb@dbserver.dev.drush.in:16191/pantheon',
'mysql_port' => 16191,
'mysql_database' => 'pantheon',
];
return new AssociativeList($outputData);
}
/**
* This command has no annotations; this means that it will not be
* found when createCommandsFromClass() is called with
* '$includeAllPublicMethods' set to false.
*/
public function withoutAnnotations()
{
return 'ok';
}
/**
* @command command:with-one-optional-argument
*
* This command has just one optional argument.
*
* Return a result only if not silent.
*
* @option silent Supress output.
*/
public function commandWithOneOptionalArgument($who = 'world', $opts = ['silent|s' => false])
{
if (!$opts['silent']) {
return "Hello, $who";
}
}
/**
* This should be a command, because it is annotated like one.
*
* @command get:serious
*/
public function getSerious()
{
return 'very serious';
}
/**
* This should not be a command, because it looks like an accessor and
* has no @command annotation.
*/
public function getLost()
{
return 'very lost';
}
/**
* This command uses a custom event 'my-event' to collect data. Note that
* the event handlers will not be found unless the hook manager is
* injected into this command handler object via `setHookManager()`
* (defined in CustomEventAwareTrait).
*
* @command use:event
*/
public function useEvent()
{
$myEventHandlers = $this->getCustomEventHandlers('my-event');
$result = [];
foreach ($myEventHandlers as $handler) {
$result[] = $handler();
}
sort($result);
return implode(',', $result);
}
/**
* @hook on-event my-event
*/
public function hookOne()
{
return 'one';
}
/**
* @hook on-event my-event
*/
public function hookTwo()
{
return 'two';
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Consolidation\TestUtils\alpha\Exclude;
/**
* Test file used in the testCommandDiscovery() test.
*
* This commandfile is NOT found by the test. It is in a searched
* location (@see Consolidation\TestUtils\alpha\Exclude\IncludedCommandFile),
* but it is in a folder named 'Exclude', which is excluded form search.
*/
class ExcludedCommandFile
{
}

View file

@ -0,0 +1,16 @@
<?php
namespace Consolidation\TestUtils\alpha\Inclusive;
/**
* Test file used in the testCommandDiscovery() test.
*
* This commandfile is found by the test. The test search base is the
* 'src' directory, and 'alpha' is one of the search directories available
* for searching. Directories such as this in the search locations list
* are searched deeply (to a depth of two), so command files may be
* organized into sub-namespaces, if desired.
*/
class IncludedCommandFile
{
}

View file

@ -0,0 +1,52 @@
<?php
namespace Consolidation\TestUtils\beta;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
/**
* Test file used in the testCommandDiscovery() test.
*
* This commandfile is not found by the test. The test search base is the
* 'src' directory, but 'beta' is NOT one of the search directories available
* for searching, so nothing in this folder will be examined.
*/
class BetaCommandFile
{
public function unavailableCommand()
{
return 'This command is not available, because this commandfile is not in a location that is searched by the tests.';
}
/**
* Demonstrate an alter hook with an option
*
* @hook alter example:table
* @option chinese Add a row with Chinese numbers.
* @usage example:table --chinese
*/
public function alterFormattersChinese($result, CommandData $commandData)
{
if ($commandData->input()->getOption('chinese')) {
$result[] = [ 'first' => '壹', 'second' => '貳', 'third' => '叁' ];
}
return $result;
}
/**
* Demonstrate an alter hook with an option
*
* @hook alter *
* @option kanji Add a row with Kanji numbers.
* @usage example:table --kanji
*/
public function alterFormattersKanji($result, CommandData $commandData)
{
if ($commandData->input()->getOption('kanji')) {
$result[] = [ 'first' => '一', 'second' => '二', 'third' => '三' ];
}
return $result;
}
}

View file

@ -0,0 +1,45 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Application;
class AnnotatedCommandTests extends \PHPUnit_Framework_TestCase
{
function testMyCatCommand()
{
$command = new \Consolidation\TestUtils\ExampleAnnotatedCommand();
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('my:cat', $command->getName());
$this->assertEquals('This is the my:cat command implemented as an AnnotatedCommand subclass.', $command->getDescription());
$this->assertEquals("This command will concatenate two parameters. If the --flip flag\nis provided, then the result is the concatenation of two and one.", $command->getHelp());
$this->assertEquals('c', implode(',', $command->getAliases()));
// Symfony Console composes the synopsis; perhaps we should not test it. Remove if this gives false failures.
$this->assertEquals('my:cat [--multiple MULTIPLE] [--flip] [--] <one> [<two>]', $command->getSynopsis());
$this->assertEquals('my:cat bet alpha --flip', implode(',', $command->getUsages()));
$input = new StringInput('my:cat b alpha --multiple=t --multiple=e --flip');
$this->assertRunCommandViaApplicationEquals($command, $input, 'alphabet');
}
// TODO: Make a base test class to hold this.
function assertRunCommandViaApplicationEquals($command, $input, $expectedOutput, $expectedStatusCode = 0)
{
$output = new BufferedOutput();
$application = new Application('TestApplication', '0.0.0');
$application->setAutoExit(false);
$application->add($command);
$statusCode = $application->run($input, $output);
$commandOutput = trim($output->fetch());
$this->assertEquals($expectedOutput, $commandOutput);
$this->assertEquals($expectedStatusCode, $statusCode);
}
}

View file

@ -0,0 +1,986 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\TestUtils\ExampleCommandInfoAlterer;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
class AnnotatedCommandFactoryTests extends \PHPUnit_Framework_TestCase
{
protected $commandFileInstance;
protected $commandFactory;
function testFibonacci()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'fibonacci');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('fibonacci', $command->getName());
$this->assertEquals('fibonacci [--graphic] [--] <start> <steps>', $command->getSynopsis());
$this->assertEquals('Calculate the fibonacci sequence between two numbers.', $command->getDescription());
$this->assertEquals("Graphic output will look like
+----+---+-------------+
| | | |
| |-+-| |
|----+-+-+ |
| | |
| | |
| | |
+--------+-------------+", $command->getHelp());
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$input = new StringInput('help fibonacci');
$this->assertRunCommandViaApplicationContains($command, $input, ['Display the sequence graphically using cube representation']);
}
function testSniff()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'sniff');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('sniff', $command->getName());
$this->assertEquals('sniff [--autofix] [--strict] [--] [<file>]', $command->getSynopsis());
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$input = new StringInput('help sniff');
$this->assertRunCommandViaApplicationContains($command, $input, ['A file or directory to analyze.']);
$input = new StringInput('sniff --autofix --strict -- foo');
$this->assertRunCommandViaApplicationContains($command, $input, ["'autofix' => true",
"'strict' => true"]);
}
function testOptionDefaultValue()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionOne');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('default:option-one', $command->getName());
$this->assertEquals('default:option-one [--foo [FOO]]', $command->getSynopsis());
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$input = new StringInput('default:option-one');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 1');
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionTwo');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('default:option-two', $command->getName());
$this->assertEquals('default:option-two [--foo [FOO]]', $command->getSynopsis());
$input = new StringInput('default:option-two');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 2');
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionNone');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('default:option-none', $command->getName());
$this->assertEquals('default:option-none [--foo FOO]', $command->getSynopsis());
// Skip failing test until Symfony is fixed.
$this->markTestSkipped('Symfony Console 3.2.5 and 3.2.6 do not handle default options with required values correctly.');
$input = new StringInput('default:option-none --foo');
$this->assertRunCommandViaApplicationContains($command, $input, ['The "--foo" option requires a value.'], 1);
}
function testGlobalOptionsOnly()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'globalOptionsOnly');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$input = new StringInput('global-options-only test');
$this->assertRunCommandViaApplicationEquals($command, $input, "Arg is test, options[help] is false");
}
function testOptionWithOptionalValue()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionalValue');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
// Test to see if we can differentiate between a missing option, and
// an option that has no value at all.
$input = new StringInput('default:optional-value --foo=bar');
$this->assertRunCommandViaApplicationEquals($command, $input, "Foo is 'bar'");
$input = new StringInput('default:optional-value --foo');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
$input = new StringInput('default:optional-value');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is NULL');
}
function testOptionThatDefaultsToTrue()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionDefaultsToTrue');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
// Test to see if we can differentiate between a missing option, and
// an option that has no value at all.
$input = new StringInput('default:option-defaults-to-true --foo=bar');
$this->assertRunCommandViaApplicationEquals($command, $input, "Foo is 'bar'");
$input = new StringInput('default:option-defaults-to-true --foo');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
$input = new StringInput('default:option-defaults-to-true');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
$input = new StringInput('help default:option-defaults-to-true');
$this->assertRunCommandViaApplicationContains(
$command,
$input,
[
'--no-foo',
'Negate --foo option',
]
);
$input = new StringInput('default:option-defaults-to-true --no-foo');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is false');
}
/**
* Test CommandInfo command caching.
*
* Sequence:
* - Create all of the command info objects from one class, caching them.
* - Change the method name of one of the items in the cache to a non-existent method
* - Restore all of the cached commandinfo objects
* - Ensure that the non-existent method cached commandinfo was not created
* - Ensure that the now-missing cached commandinfo was still created
*
* This tests both save/restore, plus adding a new command method to
* a class, and removing a command method from a class.
*/
function testAnnotatedCommandCache()
{
$testCacheStore = new \Consolidation\TestUtils\InMemoryCacheStore();
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$this->commandFactory->setDataStore($testCacheStore);
// Make commandInfo objects for every command in the test commandfile.
// These will also be stored in our cache.
$commandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
$cachedClassName = get_class($this->commandFileInstance);
$this->assertTrue($testCacheStore->has($cachedClassName));
$cachedData = $testCacheStore->get($cachedClassName);
$this->assertFalse(empty($cachedData));
$this->assertTrue(array_key_exists('testArithmatic', $cachedData));
$alterCommandInfoCache = $cachedData['testArithmatic'];
unset($cachedData['testArithmatic']);
$alterCommandInfoCache['method_name'] = 'nonExistentMethod';
$cachedData[$alterCommandInfoCache['method_name']] = $alterCommandInfoCache;
$testCacheStore->set($cachedClassName, $cachedData);
$restoredCommandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
$rebuiltCachedData = $testCacheStore->get($cachedClassName);
$this->assertFalse(empty($rebuiltCachedData));
$this->assertTrue(array_key_exists('testArithmatic', $rebuiltCachedData));
$this->assertFalse(array_key_exists('nonExistentMethod', $rebuiltCachedData));
}
/**
* Test CommandInfo command annotation parsing.
*/
function testAnnotatedCommandCreation()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArithmatic');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:arithmatic', $command->getName());
$this->assertEquals('This is the test:arithmatic command', $command->getDescription());
$this->assertEquals("This command will add one and two. If the --negate flag\nis provided, then the result is negated.", $command->getHelp());
$this->assertEquals('arithmatic', implode(',', $command->getAliases()));
$this->assertEquals('test:arithmatic [--negate] [--unused [UNUSED]] [--] <one> [<two>]', $command->getSynopsis());
$this->assertEquals('test:arithmatic 2 2 --negate', implode(',', $command->getUsages()));
$input = new StringInput('arithmatic 2 3 --negate');
$this->assertRunCommandViaApplicationEquals($command, $input, '-5');
}
/**
* Test CommandInfo command annotation altering.
*/
function testAnnotatedCommandInfoAlteration()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$annotationData = $command->getAnnotationData();
$this->assertTrue($annotationData->has('arbitrary'));
$this->assertFalse($annotationData->has('dynamic'));
$this->commandFactory->addCommandInfoAlterer(new ExampleCommandInfoAlterer());
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$annotationData = $command->getAnnotationData();
$this->assertTrue($annotationData->has('arbitrary'));
$this->assertTrue($annotationData->has('dynamic'));
}
function testMyCatCommand()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('my:cat', $command->getName());
$this->assertEquals('This is the my:cat command', $command->getDescription());
$this->assertEquals("This command will concatenate two parameters. If the --flip flag\nis provided, then the result is the concatenation of two and one.", $command->getHelp());
$this->assertEquals('c', implode(',', $command->getAliases()));
$this->assertEquals('my:cat [--flip] [--] <one> [<two>]', $command->getSynopsis());
$this->assertEquals('my:cat bet alpha --flip', implode(',', $command->getUsages()));
$input = new StringInput('my:cat bet alpha --flip');
$this->assertRunCommandViaApplicationEquals($command, $input, 'alphabet');
}
function testJoinCommandHelp()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('my:join', $command->getName());
$this->assertEquals('This is the my:join command', $command->getDescription());
$this->assertEquals("This command will join its parameters together. It can also reverse and repeat its arguments.", $command->getHelp());
$this->assertEquals('my:join [--flip] [--repeat [REPEAT]] [--] [<args>]...', $command->getSynopsis());
// TODO: Extra whitespace character if there are no options et. al. in the
// usage. This is uncommon, and the defect is invisible. Maybe find it someday.
$actualUsages = implode(',', $command->getUsages());
$this->assertEquals('my:join a b,my:join ', $actualUsages);
$input = new StringInput('my:join bet alpha --flip --repeat=2');
$this->assertRunCommandViaApplicationEquals($command, $input, 'alphabetalphabet');
}
function testDefaultsCommand()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaults');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('defaults', $command->getName());
$this->assertEquals('Test default values in arguments', $command->getDescription());
$input = new StringInput('defaults');
$this->assertRunCommandViaApplicationEquals($command, $input, 'nothing provided');
$input = new StringInput('defaults ichi');
$this->assertRunCommandViaApplicationEquals($command, $input, 'only ichi');
$input = new StringInput('defaults I II');
$this->assertRunCommandViaApplicationEquals($command, $input, 'I and II');
}
function testCommandWithNoOptions()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoOptions');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('command:with-no-options', $command->getName());
$this->assertEquals('This is a command with no options', $command->getDescription());
$this->assertEquals("This command will concatenate two parameters.", $command->getHelp());
$this->assertEquals('nope', implode(',', $command->getAliases()));
$this->assertEquals('command:with-no-options <one> [<two>]', $command->getSynopsis());
$this->assertEquals('command:with-no-options alpha bet', implode(',', $command->getUsages()));
$input = new StringInput('command:with-no-options something');
$this->assertRunCommandViaApplicationEquals($command, $input, 'somethingdefault');
$input = new StringInput('help command:with-no-options something');
$this->assertRunCommandViaApplicationContains(
$command,
$input,
[
'The first parameter.',
'The other parameter.',
]
);
}
function testCommandWithIOParameters()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithIOParameters');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('command:with-io-parameters', $command->getName());
$this->assertEquals("This command work with app's input and output", $command->getDescription());
$this->assertEquals('', $command->getHelp());
$this->assertEquals('command:with-io-parameters', $command->getSynopsis());
$input = new StringInput('command:with-io-parameters');
$this->assertRunCommandViaApplicationEquals($command, $input, 'command:with-io-parameters');
}
function testCommandWithNoArguments()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoArguments');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('command:with-no-arguments', $command->getName());
$this->assertEquals('This command has no arguments--only options', $command->getDescription());
$this->assertEquals("Return a result only if not silent.", $command->getHelp());
$this->assertEquals('command:with-no-arguments [-s|--silent]', $command->getSynopsis());
$input = new StringInput('command:with-no-arguments');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
$input = new StringInput('command:with-no-arguments -s');
$this->assertRunCommandViaApplicationEquals($command, $input, '');
$input = new StringInput('command:with-no-arguments --silent');
$this->assertRunCommandViaApplicationEquals($command, $input, '');
}
function testCommandWithShortcutOnAnnotation()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'shortcutOnAnnotation');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('shortcut:on-annotation', $command->getName());
$this->assertEquals('Shortcut on annotation', $command->getDescription());
$this->assertEquals("This command defines the option shortcut on the annotation instead of in the options array.", $command->getHelp());
$this->assertEquals('shortcut:on-annotation [-s|--silent]', $command->getSynopsis());
$input = new StringInput('shortcut:on-annotation');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
$input = new StringInput('shortcut:on-annotation -s');
$this->assertRunCommandViaApplicationEquals($command, $input, '');
$input = new StringInput('shortcut:on-annotation --silent');
$this->assertRunCommandViaApplicationEquals($command, $input, '');
}
function testState()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile('secret secret');
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testState');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:state', $command->getName());
$input = new StringInput('test:state');
$this->assertRunCommandViaApplicationEquals($command, $input, 'secret secret');
}
function testPassthroughArray()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPassthrough');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:passthrough', $command->getName());
$input = new StringInput('test:passthrough a b c -- x y z');
$this->assertRunCommandViaApplicationEquals($command, $input, 'a,b,c,x,y,z');
}
function testPassThroughNonArray()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$input = new StringInput('my:join bet --flip -- x y z');
$this->assertRunCommandViaApplicationEquals($command, $input, 'zyxbet');
// Can't look at 'hasOption' until after the command initializes the
// option, because Symfony.
$this->assertTrue($input->hasOption('flip'));
}
function testPassThroughWithInputManipulation()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$input = new StringInput('my:join bet --repeat=2 -- x y z');
$this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
// Symfony does not allow us to manipulate the options via setOption until
// the definition from the command object has been set up.
$input->setOption('repeat', 3);
$this->assertEquals(3, $input->getOption('repeat'));
$input->setArgument(0, 'q');
// Manipulating $input does not work -- the changes are not effective.
// The end result here should be 'qx y yqx y yqx y y'
$this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
}
function testRequiredArrayOption()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testRequiredArrayOption');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('test:required-array-option [-a|--arr ARR]', $command->getSynopsis());
$input = new StringInput('test:required-array-option --arr=1 --arr=2 --arr=3');
$this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
$input = new StringInput('test:required-array-option -a 1 -a 2 -a 3');
$this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
}
function testArrayOption()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArrayOption');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertEquals('test:array-option [-a|--arr [ARR]]', $command->getSynopsis());
$input = new StringInput('test:array-option');
$this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
$input = new StringInput('test:array-option --arr=a --arr=b --arr=c');
$this->assertRunCommandViaApplicationEquals($command, $input, 'a b c');
$input = new StringInput('test:array-option -a a');
$this->assertRunCommandViaApplicationEquals($command, $input, 'a');
}
function testHookedCommand()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestHook', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:hook', $command->getName());
$input = new StringInput('test:hook bar');
$this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
$input = new StringInput('list --raw');
$this->assertRunCommandViaApplicationContains($command, $input, ['This command wraps its parameter in []; its alter hook then wraps the result in .']);
}
function testReplaceCommandHook(){
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestReplaceCommandHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('replace-command test:replace-command', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:replace-command', [HookManager::REPLACE_COMMAND_HOOK]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestReplaceCommandHook', $hookCallback[0][1]);
$input = new StringInput('test:replace-command foo');
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testReplaceCommand');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertRunCommandViaApplicationEquals($command, $input, "bar", 0);
}
function testPostCommandCalledAfterCommand()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPostCommandHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('post-command test:post-command', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::POST_COMMAND_HOOK]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestPostCommandHook', $hookCallback[0][1]);
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPreCommandHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('pre-command test:post-command', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::PRE_COMMAND_HOOK]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestPreCommandHook', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPostCommand');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:post-command', $command->getName());
$input = new StringInput('test:post-command bar');
$this->assertRunCommandViaApplicationEquals($command, $input, "foo\nbar\nbaz", 0, $this->commandFileInstance);
}
function testHookAllCommands()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterAllCommands');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('alter', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('Consolidation\TestUtils\ExampleHookAllCommandFile', [HookManager::ALTER_RESULT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('alterAllCommands', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('do:cat', $command->getName());
$input = new StringInput('do:cat bar');
$this->assertRunCommandViaApplicationEquals($command, $input, '*** bar ***');
}
function testDoubleDashWithVersion()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$input = new ArgvInput(['placeholder', 'do:cat', 'one', '--', '--version']);
list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
if ($commandOutput == 'TestApplication version 0.0.0') {
$this->markTestSkipped('Symfony/Console 2.x does not respect -- with --version');
}
$this->assertEquals('one--version', $commandOutput);
}
function testAnnotatedHookedCommand()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestAnnotatedHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('alter @hookme', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('@hookme', [HookManager::ALTER_RESULT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestAnnotatedHook', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testAnnotationHook');
$annotationData = $commandInfo->getRawAnnotations();
$this->assertEquals('hookme,before,after', implode(',', $annotationData->keys()));
$this->assertEquals('@hookme,@before,@after', implode(',', array_map(function ($item) { return "@$item"; }, $annotationData->keys())));
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:annotation-hook', $command->getName());
$input = new StringInput('test:annotation-hook baz');
$this->assertRunCommandViaApplicationEquals($command, $input, '>(baz)<');
}
function testHookHasCommandAnnotation()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookAddCommandName');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('alter @addmycommandname', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('@addmycommandname', [HookManager::ALTER_RESULT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookAddCommandName', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMe');
$annotationData = $commandInfo->getRawAnnotations();
$this->assertEquals('command,addmycommandname', implode(',', $annotationData->keys()));
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('alter-me', $command->getName());
$input = new StringInput('alter-me');
$this->assertRunCommandViaApplicationEquals($command, $input, 'splendiferous from alter-me');
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMeToo');
$annotationData = $commandInfo->getRawAnnotations();
$this->assertEquals('addmycommandname', implode(',', $annotationData->keys()));
$annotationData = $commandInfo->getAnnotations();
$this->assertEquals('addmycommandname,command,_path,_classname', implode(',', $annotationData->keys()));
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('alter:me-too', $command->getName());
$input = new StringInput('alter:me-too');
$this->assertRunCommandViaApplicationEquals($command, $input, 'fantabulous from alter:me-too');
}
function testHookedCommandWithHookAddedLater()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:hook', $command->getName());
// Run the command once without the hook
$input = new StringInput('test:hook foo');
$this->assertRunCommandViaApplicationEquals($command, $input, '[foo]');
// Register the hook and run the command again
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);;
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('hookTestHook', $hookCallback[0][1]);
$input = new StringInput('test:hook bar');
$this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
}
function testInitializeHook()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'initializeTestHello');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals($hookInfo->getAnnotation('hook'), 'init test:hello');
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INITIALIZE]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('initializeTestHello', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:hello', $command->getName());
$commandGetNames = $this->callProtected($command, 'getNames');
$this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
$hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'init');
$this->assertTrue($hookCallback != null);
$this->assertEquals('initializeTestHello', $hookCallback[0][1]);
$input = new StringInput('test:hello');
$this->assertRunCommandViaApplicationEquals($command, $input, "Hello, Huey.");
}
function testCommandEventHook()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandEventTestHello');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals($hookInfo->getAnnotation('hook'), 'command-event test:hello');
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::COMMAND_EVENT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:hello', $command->getName());
$commandGetNames = $this->callProtected($command, 'getNames');
$this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
$hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'command-event');
$this->assertTrue($hookCallback != null);
$this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
$input = new StringInput('test:hello Pluto');
$this->assertRunCommandViaApplicationEquals($command, $input, "Here comes Pluto!\nHello, Pluto.");
}
function testInteractAndValidate()
{
$this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
$this->commandFactory = new AnnotatedCommandFactory();
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'interactTestHello');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals($hookInfo->getAnnotation('hook'), 'interact test:hello');
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'validateTestHello');
$this->assertTrue($hookInfo->hasAnnotation('hook'));
$this->assertEquals($hookInfo->getAnnotation('hook'), 'validate test:hello');
$this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
$hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::ARGUMENT_VALIDATOR]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('validateTestHello', $hookCallback[0][1]);
$hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INTERACT]);
$this->assertTrue($hookCallback != null);
$this->assertEquals(1, count($hookCallback));
$this->assertEquals(2, count($hookCallback[0]));
$this->assertTrue(is_callable($hookCallback[0]));
$this->assertEquals('interactTestHello', $hookCallback[0][1]);
$commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
$command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
$this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
$this->assertEquals('test:hello', $command->getName());
$commandGetNames = $this->callProtected($command, 'getNames');
$this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
$testInteractInput = new StringInput('test:hello');
$definition = new \Symfony\Component\Console\Input\InputDefinition(
[
new \Symfony\Component\Console\Input\InputArgument('application', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
new \Symfony\Component\Console\Input\InputArgument('who', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
]
);
$testInteractInput->bind($definition);
$testInteractOutput = new BufferedOutput();
$command->commandProcessor()->interact(
$testInteractInput,
$testInteractOutput,
$commandGetNames,
$command->getAnnotationData()
);
$this->assertEquals('Goofey', $testInteractInput->getArgument('who'));
$hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'interact');
$this->assertTrue($hookCallback != null);
$this->assertEquals('interactTestHello', $hookCallback[0][1]);
$input = new StringInput('test:hello "Mickey Mouse"');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Mickey Mouse.');
$input = new StringInput('test:hello');
$this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Goofey.');
$input = new StringInput('test:hello "Donald Duck"');
$this->assertRunCommandViaApplicationEquals($command, $input, "I won't say hello to Donald Duck.", 1);
$input = new StringInput('test:hello "Drumph"');
$this->assertRunCommandViaApplicationEquals($command, $input, "Irrational value error.", 1);
// Try the last test again with a display error function installed.
$this->commandFactory->commandProcessor()->setDisplayErrorFunction(
function ($output, $message) {
$output->writeln("*** $message ****");
}
);
$input = new StringInput('test:hello "Drumph"');
$this->assertRunCommandViaApplicationEquals($command, $input, "*** Irrational value error. ****", 1);
}
function callProtected($object, $method, $args = [])
{
$r = new \ReflectionMethod($object, $method);
$r->setAccessible(true);
return $r->invokeArgs($object, $args);
}
function assertRunCommandViaApplicationContains($command, $input, $containsList, $expectedStatusCode = 0)
{
list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
foreach ($containsList as $contains) {
$this->assertContains($contains, $commandOutput);
}
$this->assertEquals($expectedStatusCode, $statusCode);
}
function assertRunCommandViaApplicationEquals($command, $input, $expectedOutput, $expectedStatusCode = 0)
{
list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
$this->assertEquals($expectedOutput, $commandOutput);
$this->assertEquals($expectedStatusCode, $statusCode);
}
function runCommandViaApplication($command, $input)
{
$output = new BufferedOutput();
if ($this->commandFileInstance && method_exists($this->commandFileInstance, 'setOutput')) {
$this->commandFileInstance->setOutput($output);
}
$application = new Application('TestApplication', '0.0.0');
$alterOptionsEventManager = new AlterOptionsCommandEvent($application);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
$this->commandFactory->commandProcessor()->hookManager()->addCommandEvent($alterOptionsEventManager);
$application->setDispatcher($eventDispatcher);
$application->setAutoExit(false);
$application->add($command);
$statusCode = $application->run($input, $output);
$commandOutput = trim(str_replace("\r", '', $output->fetch()));
return [$statusCode, $commandOutput];
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Consolidation\AnnotatedCommand;
class CommandFileDiscoveryTests extends \PHPUnit_Framework_TestCase
{
function testCommandDiscovery()
{
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setSearchLocations(['alpha']);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$commandFilePaths = array_keys($commandFiles);
$commandFileNamespaces = array_values($commandFiles);
// Ensure that the command files that we expected to
// find were all found. We don't find anything in
// 'beta' because only 'alpha' is in the search path.
$this->assertContains('./src/ExampleCommandFile.php', $commandFilePaths);
$this->assertContains('./src/ExampleHookAllCommandFile.php', $commandFilePaths);
$this->assertContains('./src/alpha/AlphaCommandFile.php', $commandFilePaths);
$this->assertContains('./src/alpha/Inclusive/IncludedCommandFile.php', $commandFilePaths);
// Make sure that there are no additional items found.
$this->assertEquals(4, count($commandFilePaths));
// Ensure that the command file namespaces that we expected
// to be generated all match.
$this->assertContains('\Consolidation\TestUtils\ExampleCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\ExampleHookAllCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\alpha\AlphaCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\alpha\Inclusive\IncludedCommandFile', $commandFileNamespaces);
// We do not need to test for additional namespace items, because we
// know that the length of the array_keys must be the same as the
// length of the array_values.
}
function testDeepCommandDiscovery()
{
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setSearchDepth(1)
->setSearchLocations([]);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$commandFilePaths = array_keys($commandFiles);
$commandFileNamespaces = array_values($commandFiles);
// Ensure that the command files that we expected to
// find were all found. We find both 'alpha' and 'beta'
// items because the search locations is empty, which
// causes the search at the base directory to be deep.
// We do not find alpha/Inclusive, though, as the search
// depth is only 2, which excludes directories that are
// three levels deep.
$this->assertContains('./src/ExampleCommandFile.php', $commandFilePaths);
$this->assertContains('./src/ExampleHookAllCommandFile.php', $commandFilePaths);
$this->assertContains('./src/alpha/AlphaCommandFile.php', $commandFilePaths);
$this->assertContains('./src/beta/BetaCommandFile.php', $commandFilePaths);
// Make sure that there are no additional items found.
$this->assertEquals(4, count($commandFilePaths));
// Ensure that the command file namespaces that we expected
// to be generated all match.
$this->assertContains('\Consolidation\TestUtils\ExampleCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\ExampleHookAllCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\alpha\AlphaCommandFile', $commandFileNamespaces);
$this->assertContains('\Consolidation\TestUtils\beta\BetaCommandFile', $commandFileNamespaces);
// We do not need to test for additional namespace items, because we
// know that the length of the array_keys must be the same as the
// length of the array_values.
}
}

View file

@ -0,0 +1,102 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\AnnotatedCommand\Parser\CommandInfoSerializer;
use Consolidation\AnnotatedCommand\Parser\CommandInfoDeserializer;
class CommandInfoTests extends \PHPUnit_Framework_TestCase
{
function flattenArray($actualValue)
{
$result = [];
foreach ($actualValue as $key => $value) {
if (!is_string($value)) {
$value = var_export($value, true);
}
$result[] = "{$key}=>{$value}";
}
return implode("\n", $result);
}
/**
* Test CommandInfo command annotation parsing.
*/
function testParsing()
{
$commandInfo = CommandInfo::create('\Consolidation\TestUtils\ExampleCommandFile', 'testArithmatic');
$this->assertCommandInfoIsAsExpected($commandInfo);
$serializer = new CommandInfoSerializer();
$serialized = $serializer->serialize($commandInfo);
$deserializer = new CommandInfoDeserializer();
$deserializedCommandInfo = $deserializer->deserialize($serialized);
$this->assertCommandInfoIsAsExpected($deserializedCommandInfo);
}
function testWithConfigImport()
{
$commandInfo = CommandInfo::create('\Consolidation\TestUtils\ExampleCommandFile', 'import');
$this->assertEquals('config:import', $commandInfo->getName());
$this->assertEquals(
'A config directory label (i.e. a key in \$config_directories array in settings.php).',
$commandInfo->arguments()->getDescription('label')
);
}
function assertCommandInfoIsAsExpected($commandInfo)
{
$this->assertEquals('test:arithmatic', $commandInfo->getName());
$this->assertEquals(
'This is the test:arithmatic command',
$commandInfo->getDescription()
);
$this->assertEquals(
"This command will add one and two. If the --negate flag\nis provided, then the result is negated.",
$commandInfo->getHelp()
);
$this->assertEquals('arithmatic', implode(',', $commandInfo->getAliases()));
$this->assertEquals(
'2 2 --negate=>Add two plus two and then negate.',
$this->flattenArray($commandInfo->getExampleUsages())
);
$this->assertEquals(
'The first number to add.',
$commandInfo->arguments()->getDescription('one')
);
$this->assertEquals(
'The other number to add.',
$commandInfo->arguments()->getDescription('two')
);
$this->assertEquals(
'2',
$commandInfo->arguments()->get('two')
);
$this->assertEquals(
'Whether or not the result should be negated.',
$commandInfo->options()->getDescription('negate')
);
$this->assertEquals(
'bob',
$commandInfo->options()->get('unused')
);
$this->assertEquals(
'one,two',
$commandInfo->getAnnotation('dup')
);
$this->assertEquals(
['one','two'],
$commandInfo->getAnnotationList('dup')
);
}
function testReturnValue()
{
$commandInfo = CommandInfo::create('\Consolidation\TestUtils\alpha\AlphaCommandFile', 'exampleTable');
$this->assertEquals('example:table', $commandInfo->getName());
$this->assertEquals('\Consolidation\OutputFormatters\StructuredData\RowsOfFields', $commandInfo->getReturnType());
}
}

View file

@ -0,0 +1,530 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandProcessor;
use Consolidation\AnnotatedCommand\Hooks\AlterResultInterface;
use Consolidation\AnnotatedCommand\Hooks\ExtractOutputInterface;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\Hooks\StatusDeterminerInterface;
use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\OutputFormatters\FormatterManager;
use Consolidation\TestUtils\TestTerminal;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\TestUtils\ApplicationWithTerminalWidth;
use Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption;
use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
/**
* Do a test of all of the classes in this project, top-to-bottom.
*/
class FullStackTests extends \PHPUnit_Framework_TestCase
{
protected $application;
protected $commandFactory;
function setup() {
$this->application = new ApplicationWithTerminalWidth('TestApplication', '0.0.0');
$this->commandFactory = new AnnotatedCommandFactory();
$alterOptionsEventManager = new AlterOptionsCommandEvent($this->application);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
$this->commandFactory->commandProcessor()->hookManager()->addCommandEvent($alterOptionsEventManager);
$this->application->setDispatcher($eventDispatcher);
$this->application->setAutoExit(false);
}
function testValidFormats()
{
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$commandInfo = CommandInfo::create('\Consolidation\TestUtils\alpha\AlphaCommandFile', 'exampleTable');
$this->assertEquals('example:table', $commandInfo->getName());
$this->assertEquals('\Consolidation\OutputFormatters\StructuredData\RowsOfFields', $commandInfo->getReturnType());
}
function testAutomaticOptions()
{
$commandFileInstance = new \Consolidation\TestUtils\alpha\AlphaCommandFile;
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$this->commandFactory->commandProcessor()->setFormatterManager($formatter);
$this->assertAutomaticOptionsForCommand($commandFileInstance, 'exampleTable', 'example:table');
$this->assertAutomaticOptionsForCommand($commandFileInstance, 'exampleTableTwo', 'example:table2');
}
function assertAutomaticOptionsForCommand($commandFileInstance, $functionName, $commandName)
{
$commandInfo = $this->commandFactory->createCommandInfo($commandFileInstance, $functionName);
$command = $this->commandFactory->createCommand($commandInfo, $commandFileInstance);
$this->application->add($command);
$containsList =
[
'--format[=FORMAT] Format the result data. Available formats: csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml [default: "table"]',
'--fields[=FIELDS] Available fields: I (first), II (second), III (third) [default: ""]',
];
$this->assertRunCommandViaApplicationContains('help ' . $commandName, $containsList);
}
function testCommandsAndHooks()
{
// First, search for commandfiles in the 'alpha'
// directory. Note that this same functionality
// is tested more thoroughly in isolation in
// testCommandFileDiscovery.php
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setIncludeFilesAtBase(false)
->setSearchLocations(['alpha']);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$hookManager = new HookManager();
$terminalWidthOption = new PrepareTerminalWidthOption();
$terminalWidthOption->enableWrap(true);
$terminalWidthOption->setApplication($this->application);
$testTerminal = new TestTerminal(0);
$terminalWidthOption->setTerminal($testTerminal);
$commandProcessor = new CommandProcessor($hookManager);
$commandProcessor->setFormatterManager($formatter);
$commandProcessor->addPrepareFormatter($terminalWidthOption);
// Create a new factory, and load all of the files
// discovered above.
$factory = new AnnotatedCommandFactory();
$factory->setCommandProcessor($commandProcessor);
// Add a listener to configure our command handler object
$factory->addListernerCallback(function($command) use($hookManager) {
if ($command instanceof CustomEventAwareInterface) {
$command->setHookManager($hookManager);
}
} );
$factory->setIncludeAllPublicMethods(false);
$this->addDiscoveredCommands($factory, $commandFiles);
$this->assertRunCommandViaApplicationContains('list', ['example:table'], ['additional:option', 'without:annotations']);
$this->assertTrue($this->application->has('example:table'));
$this->assertFalse($this->application->has('without:annotations'));
// Run the use:event command that defines a custom event, my-event.
$this->assertRunCommandViaApplicationEquals('use:event', 'one,two');
// Watch as we dynamically add a custom event to the hook manager to change the command results:
$hookManager->add(function () { return 'three'; }, HookManager::ON_EVENT, 'my-event');
$this->assertRunCommandViaApplicationEquals('use:event', 'one,three,two');
// Fetch a reference to the 'example:table' command and test its valid format types
$exampleTableCommand = $this->application->find('example:table');
$returnType = $exampleTableCommand->getReturnType();
$this->assertEquals('\Consolidation\OutputFormatters\StructuredData\RowsOfFields', $returnType);
$validFormats = $formatter->validFormats($returnType);
$this->assertEquals('csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml', implode(',', $validFormats));
// Control: run commands without hooks.
$this->assertRunCommandViaApplicationEquals('always:fail', 'This command always fails.', 13);
$this->assertRunCommandViaApplicationEquals('simulated:status', '42');
$this->assertRunCommandViaApplicationEquals('example:output', 'Hello, World.');
$this->assertRunCommandViaApplicationEquals('example:cat bet alpha --flip', 'alphabet');
$this->assertRunCommandViaApplicationEquals('example:echo a b c', "a\tb\tc");
$this->assertRunCommandViaApplicationEquals('example:message', 'Shipwrecked; send bananas.');
$this->assertRunCommandViaApplicationEquals('command:with-one-optional-argument', 'Hello, world');
$this->assertRunCommandViaApplicationEquals('command:with-one-optional-argument Joe', 'Hello, Joe');
// Add some hooks.
$factory->hookManager()->addValidator(new ExampleValidator());
$factory->hookManager()->addResultProcessor(new ExampleResultProcessor());
$factory->hookManager()->addAlterResult(new ExampleResultAlterer());
$factory->hookManager()->addStatusDeterminer(new ExampleStatusDeterminer());
$factory->hookManager()->addOutputExtractor(new ExampleOutputExtractor());
// Run the same commands as before, and confirm that results
// are different now that the hooks are in place.
$this->assertRunCommandViaApplicationEquals('simulated:status', '', 42);
$this->assertRunCommandViaApplicationEquals('example:output', 'Hello, World!');
$this->assertRunCommandViaApplicationEquals('example:cat bet alpha --flip', 'alphareplaced');
$this->assertRunCommandViaApplicationEquals('example:echo a b c', 'a,b,c');
$this->assertRunCommandViaApplicationEquals('example:message', 'Shipwrecked; send bananas.');
$expected = <<<EOT
------ ------ -------
I II III
------ ------ -------
One Two Three
Eins Zwei Drei
Ichi Ni San
Uno Dos Tres
------ ------ -------
EOT;
$this->assertRunCommandViaApplicationEquals('example:table', $expected);
$expected = <<<EOT
------- ------
III II
------- ------
Three Two
Drei Zwei
San Ni
Tres Dos
------- ------
EOT;
$this->assertRunCommandViaApplicationEquals('example:table --fields=III,II', $expected);
$expectedSingleField = <<<EOT
Two
Zwei
Ni
Dos
EOT;
// When --field is specified (instead of --fields), then the format
// is forced to 'string'.
$this->assertRunCommandViaApplicationEquals('example:table --field=II', $expectedSingleField);
// Check the help for the example table command and see if the options
// from the alter hook were added. We expect that we should not see
// any of the information from the alter hook in the 'beta' folder yet.
$this->assertRunCommandViaApplicationContains('help example:table',
[
'Option added by @hook option example:table',
'example:table --french',
'Add a row with French numbers.'
],
[
'chinese',
'kanji',
]
);
$expectedOutputWithFrench = <<<EOT
------ ------ -------
I II III
------ ------ -------
One Two Three
Eins Zwei Drei
Ichi Ni San
Uno Dos Tres
Un Deux Trois
------ ------ -------
EOT;
$this->assertRunCommandViaApplicationEquals('example:table --french', $expectedOutputWithFrench);
$expectedAssociativeListTable = <<<EOT
--------------- ----------------------------------------------------------------------------------------
SFTP Command sftp -o Port=2222 dev@appserver.dev.drush.in
Git Command git clone ssh://codeserver.dev@codeserver.dev.drush.in:2222/~/repository.git wp-update
MySQL Command mysql -u pantheon -p4b33cb -h dbserver.dev.drush.in -P 16191 pantheon
--------------- ----------------------------------------------------------------------------------------
EOT;
$this->assertRunCommandViaApplicationEquals('example:list', $expectedAssociativeListTable);
$this->assertRunCommandViaApplicationEquals('example:list --field=sftp_command', 'sftp -o Port=2222 dev@appserver.dev.drush.in');
$this->assertRunCommandViaApplicationEquals('get:serious', 'very serious');
$this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1);
$this->assertRunCommandViaApplicationContains('help example:wrap',
[
'Test word wrapping',
'[default: "table"]',
]
);
$expectedUnwrappedOutput = <<<EOT
-------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
First Second
-------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines. This is the second column of the same table. It is also very long, and should be wrapped across multiple lines, just like the first column.
-------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
EOT;
$this->application->setWidthAndHeight(0, 0);
$this->assertRunCommandViaApplicationEquals('example:wrap', $expectedUnwrappedOutput);
$expectedWrappedOutput = <<<EOT
------------------ --------------------
First Second
------------------ --------------------
This is a really This is the second
long cell that column of the same
contains a lot table. It is also
of data. When it very long, and
is rendered, it should be wrapped
should be across multiple
wrapped across lines, just like
multiple lines. the first column.
------------------ --------------------
EOT;
$this->application->setWidthAndHeight(42, 24);
$testTerminal->setWidth(42);
$this->assertRunCommandViaApplicationEquals('example:wrap', $expectedWrappedOutput);
}
function testCommandsAndHooksIncludeAllPublicMethods()
{
// First, search for commandfiles in the 'alpha'
// directory. Note that this same functionality
// is tested more thoroughly in isolation in
// testCommandFileDiscovery.php
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setIncludeFilesAtBase(false)
->setSearchLocations(['alpha']);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$hookManager = new HookManager();
$commandProcessor = new CommandProcessor($hookManager);
$commandProcessor->setFormatterManager($formatter);
// Create a new factory, and load all of the files
// discovered above. The command factory class is
// tested in isolation in testAnnotatedCommandFactory.php,
// but this is the only place where
$factory = new AnnotatedCommandFactory();
$factory->setCommandProcessor($commandProcessor);
// $factory->addListener(...);
// Now we will once again add all commands, this time including all
// public methods. The command 'withoutAnnotations' should now be found.
$factory->setIncludeAllPublicMethods(true);
$this->addDiscoveredCommands($factory, $commandFiles);
$this->assertTrue($this->application->has('without:annotations'));
$this->assertRunCommandViaApplicationContains('list', ['example:table', 'without:annotations'], ['alter:formatters']);
$this->assertRunCommandViaApplicationEquals('get:serious', 'very serious');
$this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1);
}
function testCommandsAndHooksWithBetaFolder()
{
// First, search for commandfiles in the 'alpha'
// directory. Note that this same functionality
// is tested more thoroughly in isolation in
// testCommandFileDiscovery.php
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setIncludeFilesAtBase(false)
->setSearchLocations(['alpha', 'beta']);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$hookManager = new HookManager();
$commandProcessor = new CommandProcessor($hookManager);
$commandProcessor->setFormatterManager($formatter);
// Create a new factory, and load all of the files
// discovered above. The command factory class is
// tested in isolation in testAnnotatedCommandFactory.php,
// but this is the only place where
$factory = new AnnotatedCommandFactory();
$factory->setCommandProcessor($commandProcessor);
// $factory->addListener(...);
$factory->setIncludeAllPublicMethods(true);
$this->addDiscoveredCommands($factory, $commandFiles);
// A few asserts, to make sure that our hooks all get registered.
$allRegisteredHooks = $hookManager->getAllHooks();
$registeredHookNames = array_keys($allRegisteredHooks);
sort($registeredHookNames);
$this->assertEquals('*,example:table,my-event', implode(',', $registeredHookNames));
$allHooksForExampleTable = $allRegisteredHooks['example:table'];
$allHookPhasesForExampleTable = array_keys($allHooksForExampleTable);
sort($allHookPhasesForExampleTable);
$this->assertEquals('alter,option', implode(',', $allHookPhasesForExampleTable));
$this->assertContains('alterFormattersChinese', var_export($allHooksForExampleTable, true));
$alterHooksForExampleTable = $this->callProtected($hookManager, 'getHooks', [['example:table'], 'alter']);
$this->assertContains('alterFormattersKanji', var_export($alterHooksForExampleTable, true));
$allHooksForAnyCommand = $allRegisteredHooks['*'];
$allHookPhasesForAnyCommand = array_keys($allHooksForAnyCommand);
sort($allHookPhasesForAnyCommand);
$this->assertEquals('alter', implode(',', $allHookPhasesForAnyCommand));
$this->assertContains('alterFormattersKanji', var_export($allHooksForAnyCommand, true));
// Help should have the information from the hooks in the 'beta' folder
$this->assertRunCommandViaApplicationContains('help example:table',
[
'Option added by @hook option example:table',
'example:table --french',
'Add a row with French numbers.',
'chinese',
'kanji',
]
);
// Confirm that the "unavailable" command is now available
$this->assertTrue($this->application->has('unavailable:command'));
$expectedOutputWithChinese = <<<EOT
------ ------ -------
I II III
------ ------ -------
One Two Three
Eins Zwei Drei
Ichi Ni San
Uno Dos Tres
------ ------ -------
EOT;
$this->assertRunCommandViaApplicationEquals('example:table --chinese', $expectedOutputWithChinese);
$expectedOutputWithKanji = <<<EOT
------ ------ -------
I II III
------ ------ -------
One Two Three
Eins Zwei Drei
Ichi Ni San
Uno Dos Tres
------ ------ -------
EOT;
$this->assertRunCommandViaApplicationEquals('example:table --kanji', $expectedOutputWithKanji);
}
public function addDiscoveredCommands($factory, $commandFiles) {
foreach ($commandFiles as $path => $commandClass) {
$this->assertFileExists($path);
if (!class_exists($commandClass)) {
include $path;
}
$commandInstance = new $commandClass();
$commandList = $factory->createCommandsFromClass($commandInstance);
foreach ($commandList as $command) {
$this->application->add($command);
}
}
}
function assertRunCommandViaApplicationEquals($cmd, $expectedOutput, $expectedStatusCode = 0)
{
$input = new StringInput($cmd);
$output = new BufferedOutput();
$statusCode = $this->application->run($input, $output);
$commandOutput = trim($output->fetch());
$expectedOutput = $this->simplifyWhitespace($expectedOutput);
$commandOutput = $this->simplifyWhitespace($commandOutput);
$this->assertEquals($expectedOutput, $commandOutput);
$this->assertEquals($expectedStatusCode, $statusCode);
}
function assertRunCommandViaApplicationContains($cmd, $containsList, $doesNotContainList = [], $expectedStatusCode = 0)
{
$input = new StringInput($cmd);
$output = new BufferedOutput();
$containsList = (array) $containsList;
$statusCode = $this->application->run($input, $output);
$commandOutput = trim($output->fetch());
$commandOutput = $this->simplifyWhitespace($commandOutput);
foreach ($containsList as $expectedToContain) {
$this->assertContains($this->simplifyWhitespace($expectedToContain), $commandOutput);
}
foreach ($doesNotContainList as $expectedToNotContain) {
$this->assertNotContains($this->simplifyWhitespace($expectedToNotContain), $commandOutput);
}
$this->assertEquals($expectedStatusCode, $statusCode);
}
function simplifyWhitespace($data)
{
return trim(preg_replace('#\s+$#m', '', $data));
}
function callProtected($object, $method, $args = [])
{
$r = new \ReflectionMethod($object, $method);
$r->setAccessible(true);
return $r->invokeArgs($object, $args);
}
}
class ExampleValidator implements ValidatorInterface
{
public function validate(CommandData $commandData)
{
$args = $commandData->arguments();
if (isset($args['one']) && ($args['one'] == 'bet')) {
$commandData->input()->setArgument('one', 'replaced');
return $args;
}
}
}
class ExampleResultProcessor implements ProcessResultInterface
{
public function process($result, CommandData $commandData)
{
if (is_array($result) && array_key_exists('item-list', $result)) {
return implode(',', $result['item-list']);
}
}
}
class ExampleResultAlterer implements AlterResultInterface
{
public function process($result, CommandData $commandData)
{
if (is_string($result) && ($result == 'Hello, World.')) {
return 'Hello, World!';
}
}
}
class ExampleStatusDeterminer implements StatusDeterminerInterface
{
public function determineStatusCode($result)
{
if (is_array($result) && array_key_exists('status-code', $result)) {
return $result['status-code'];
}
}
}
class ExampleOutputExtractor implements ExtractOutputInterface
{
public function extractOutput($result)
{
if (is_array($result) && array_key_exists('message', $result)) {
return $result['message'];
}
}
}

View file

@ -0,0 +1,313 @@
<?php
namespace Consolidation\AnnotatedCommand;
use Consolidation\AnnotatedCommand\Help\HelpCommand;
use Consolidation\AnnotatedCommand\AnnotationData;
use Consolidation\AnnotatedCommand\CommandData;
use Consolidation\AnnotatedCommand\CommandProcessor;
use Consolidation\AnnotatedCommand\Hooks\AlterResultInterface;
use Consolidation\AnnotatedCommand\Hooks\ExtractOutputInterface;
use Consolidation\AnnotatedCommand\Hooks\HookManager;
use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
use Consolidation\AnnotatedCommand\Hooks\StatusDeterminerInterface;
use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
use Consolidation\AnnotatedCommand\Parser\CommandInfo;
use Consolidation\OutputFormatters\FormatterManager;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\StringInput;
use Symfony\Component\Console\Output\BufferedOutput;
use Symfony\Component\Console\Output\OutputInterface;
use Consolidation\TestUtils\ApplicationWithTerminalWidth;
use Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption;
/**
* Test our 'help' command.
*/
class HelpTests extends \PHPUnit_Framework_TestCase
{
protected $application;
protected $commandFactory;
function setup()
{
$this->application = new ApplicationWithTerminalWidth('TestApplication', '0.0.0');
$this->commandFactory = new AnnotatedCommandFactory();
// $factory->addListener(...);
$alterOptionsEventManager = new AlterOptionsCommandEvent($this->application);
$eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
$eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
$this->commandFactory->commandProcessor()->hookManager()->addCommandEvent($alterOptionsEventManager);
$this->application->setDispatcher($eventDispatcher);
$this->application->setAutoExit(false);
$discovery = new CommandFileDiscovery();
$discovery
->setSearchPattern('*CommandFile.php')
->setIncludeFilesAtBase(false)
->setSearchLocations(['alpha']);
chdir(__DIR__);
$commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
$formatter = new FormatterManager();
$formatter->addDefaultFormatters();
$formatter->addDefaultSimplifiers();
$terminalWidthOption = new PrepareTerminalWidthOption();
$terminalWidthOption->setApplication($this->application);
$this->commandFactory->commandProcessor()->setFormatterManager($formatter);
$this->commandFactory->commandProcessor()->addPrepareFormatter($terminalWidthOption);
$this->commandFactory->setIncludeAllPublicMethods(false);
$this->addDiscoveredCommands($this->commandFactory, $commandFiles);
$helpCommandfile = new HelpCommand($this->application);
$commandList = $this->commandFactory->createCommandsFromClass($helpCommandfile);
foreach ($commandList as $command) {
$this->application->add($command);
}
}
public function addDiscoveredCommands($factory, $commandFiles) {
foreach ($commandFiles as $path => $commandClass) {
$this->assertFileExists($path);
if (!class_exists($commandClass)) {
include $path;
}
$commandInstance = new $commandClass();
$commandList = $factory->createCommandsFromClass($commandInstance);
foreach ($commandList as $command) {
$this->application->add($command);
}
}
}
function assertRunCommandViaApplicationEquals($cmd, $expectedOutput, $expectedStatusCode = 0)
{
$input = new StringInput($cmd);
$output = new BufferedOutput();
$statusCode = $this->application->run($input, $output);
$commandOutput = trim($output->fetch());
$expectedOutput = $this->simplifyWhitespace($expectedOutput);
$commandOutput = $this->simplifyWhitespace($commandOutput);
$this->assertEquals($expectedOutput, $commandOutput);
$this->assertEquals($expectedStatusCode, $statusCode);
}
function simplifyWhitespace($data)
{
return trim(preg_replace('#\s+$#m', '', $data));
}
function testHelp()
{
$expectedXML = <<<EOT
<?xml version="1.0" encoding="UTF-8"?>
<command id="example:table" name="example:table">
<usages>
<usage>example:table [--format [FORMAT]] [--fields [FIELDS]] [--field FIELD] [--] [&lt;unused&gt;]</usage>
</usages>
<examples>
<example>
<usage>example:table --format=yml</usage>
<description>Show the example table in yml format.</description>
</example>
<example>
<usage>example:table --fields=first,third</usage>
<description>Show only the first and third fields in the table.</description>
</example>
<example>
<usage>example:table --fields=II,III</usage>
<description>Note that either the field ID or the visible field label may be used.</description>
</example>
</examples>
<description>Test command with formatters</description>
<arguments>
<argument name="unused" is_required="0" is_array="0">
<description>An unused argument</description>
<defaults/>
</argument>
</arguments>
<options>
<option name="--format" shortcut="" accept_value="1" is_value_required="0" is_multiple="0">
<description>Format the result data. Available formats: csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml</description>
<defaults>
<default>table</default>
</defaults>
</option>
<option name="--fields" shortcut="" accept_value="1" is_value_required="0" is_multiple="0">
<description>Available fields: I (first), II (second), III (third)</description>
<defaults/>
</option>
<option name="--field" shortcut="" accept_value="1" is_value_required="1" is_multiple="0">
<description>Select just one field, and force format to 'string'.</description>
<defaults/>
</option>
<option name="--help" shortcut="-h" accept_value="0" is_value_required="0" is_multiple="0">
<description>Display this help message</description>
</option>
<option name="--quiet" shortcut="-q" accept_value="0" is_value_required="0" is_multiple="0">
<description>Do not output any message</description>
</option>
<option name="--verbose" shortcut="-v" shortcuts="-v|-vv|-vvv" accept_value="0" is_value_required="0" is_multiple="0">
<description>Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug</description>
</option>
<option name="--version" shortcut="-V" accept_value="0" is_value_required="0" is_multiple="0">
<description>Display this application version</description>
</option>
<option name="--ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
<description>Force ANSI output</description>
</option>
<option name="--no-ansi" shortcut="" accept_value="0" is_value_required="0" is_multiple="0">
<description>Disable ANSI output</description>
</option>
<option name="--no-interaction" shortcut="-n" accept_value="0" is_value_required="0" is_multiple="0">
<description>Do not ask any interactive question</description>
</option>
</options>
<help>Test command with formatters</help>
<aliases>
<alias>extab</alias>
</aliases>
<topics>
<topic>docs-tables</topic>
</topics>
</command>
EOT;
$this->assertRunCommandViaApplicationEquals('my-help --format=xml example:table', $expectedXML);
$expectedJSON = <<<EOT
{
"id": "example:table",
"name": "example:table",
"usages": [
"example:table [--format [FORMAT]] [--fields [FIELDS]] [--field FIELD] [--] [<unused>]"
],
"examples": [
{
"usage": "example:table --format=yml",
"description": "Show the example table in yml format."
},
{
"usage": "example:table --fields=first,third",
"description": "Show only the first and third fields in the table."
},
{
"usage": "example:table --fields=II,III",
"description": "Note that either the field ID or the visible field label may be used."
}
],
"description": "Test command with formatters",
"arguments": {
"unused": {
"name": "unused",
"is_required": "0",
"is_array": "0",
"description": "An unused argument"
}
},
"options": {
"format": {
"name": "--format",
"shortcut": "",
"accept_value": "1",
"is_value_required": "0",
"is_multiple": "0",
"description": "Format the result data. Available formats: csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml",
"defaults": [
"table"
]
},
"fields": {
"name": "--fields",
"shortcut": "",
"accept_value": "1",
"is_value_required": "0",
"is_multiple": "0",
"description": "Available fields: I (first), II (second), III (third)"
},
"field": {
"name": "--field",
"shortcut": "",
"accept_value": "1",
"is_value_required": "1",
"is_multiple": "0",
"description": "Select just one field, and force format to 'string'."
},
"help": {
"name": "--help",
"shortcut": "-h",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Display this help message"
},
"quiet": {
"name": "--quiet",
"shortcut": "-q",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Do not output any message"
},
"verbose": {
"name": "--verbose",
"shortcut": "-v",
"shortcuts": "-v|-vv|-vvv",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug"
},
"version": {
"name": "--version",
"shortcut": "-V",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Display this application version"
},
"ansi": {
"name": "--ansi",
"shortcut": "",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Force ANSI output"
},
"no-ansi": {
"name": "--no-ansi",
"shortcut": "",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Disable ANSI output"
},
"no-interaction": {
"name": "--no-interaction",
"shortcut": "-n",
"accept_value": "0",
"is_value_required": "0",
"is_multiple": "0",
"description": "Do not ask any interactive question"
}
},
"help": "Test command with formatters",
"aliases": [
"extab"
],
"topics": [
"docs-tables"
]
}
EOT;
$this->assertRunCommandViaApplicationEquals('my-help --format=json example:table', $expectedJSON);
}
}