2 namespace Consolidation\AnnotatedCommand;
4 use Consolidation\AnnotatedCommand\AnnotationData;
5 use Consolidation\AnnotatedCommand\CommandData;
6 use Consolidation\AnnotatedCommand\Hooks\HookManager;
7 use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
8 use Consolidation\AnnotatedCommand\Parser\CommandInfo;
9 use Consolidation\TestUtils\ExampleCommandInfoAlterer;
10 use Symfony\Component\Console\Application;
11 use Symfony\Component\Console\Command\Command;
12 use Symfony\Component\Console\Input\InputInterface;
13 use Symfony\Component\Console\Input\ArgvInput;
14 use Symfony\Component\Console\Input\StringInput;
15 use Symfony\Component\Console\Output\BufferedOutput;
16 use Symfony\Component\Console\Output\OutputInterface;
18 class AnnotatedCommandFactoryTests extends \PHPUnit_Framework_TestCase
20 protected $commandFileInstance;
21 protected $commandFactory;
23 function testFibonacci()
25 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
26 $this->commandFactory = new AnnotatedCommandFactory();
27 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'fibonacci');
29 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
30 $this->assertEquals('fibonacci', $command->getName());
31 $this->assertEquals('fibonacci [--graphic] [--] <start> <steps>', $command->getSynopsis());
32 $this->assertEquals('Calculate the fibonacci sequence between two numbers.', $command->getDescription());
33 $this->assertEquals("Graphic output will look like
34 +----+---+-------------+
41 +--------+-------------+", $command->getHelp());
43 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
45 $input = new StringInput('help fibonacci');
46 $this->assertRunCommandViaApplicationContains($command, $input, ['Display the sequence graphically using cube representation']);
51 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
52 $this->commandFactory = new AnnotatedCommandFactory();
53 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'sniff');
55 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
56 $this->assertEquals('sniff', $command->getName());
57 $this->assertEquals('sniff [--autofix] [--strict] [--] [<file>]', $command->getSynopsis());
59 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
61 $input = new StringInput('help sniff');
62 $this->assertRunCommandViaApplicationContains($command, $input, ['A file or directory to analyze.']);
64 $input = new StringInput('sniff --autofix --strict -- foo');
65 $this->assertRunCommandViaApplicationContains($command, $input, ["'autofix' => true",
69 function testOptionDefaultValue()
71 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
72 $this->commandFactory = new AnnotatedCommandFactory();
73 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionOne');
75 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
76 $this->assertEquals('default:option-one', $command->getName());
77 $this->assertEquals('default:option-one [--foo [FOO]]', $command->getSynopsis());
79 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
81 $input = new StringInput('default:option-one');
82 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 1');
84 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionTwo');
86 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
88 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
89 $this->assertEquals('default:option-two', $command->getName());
90 $this->assertEquals('default:option-two [--foo [FOO]]', $command->getSynopsis());
92 $input = new StringInput('default:option-two');
93 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 2');
95 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionNone');
97 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
99 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
100 $this->assertEquals('default:option-none', $command->getName());
101 $this->assertEquals('default:option-none [--foo FOO]', $command->getSynopsis());
103 // Skip failing test until Symfony is fixed.
104 $this->markTestSkipped('Symfony Console 3.2.5 and 3.2.6 do not handle default options with required values correctly.');
106 $input = new StringInput('default:option-none --foo');
107 $this->assertRunCommandViaApplicationContains($command, $input, ['The "--foo" option requires a value.'], 1);
110 function testGlobalOptionsOnly()
112 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
113 $this->commandFactory = new AnnotatedCommandFactory();
114 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'globalOptionsOnly');
116 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
118 $input = new StringInput('global-options-only test');
119 $this->assertRunCommandViaApplicationEquals($command, $input, "Arg is test, options[help] is false");
122 function testOptionWithOptionalValue()
124 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
125 $this->commandFactory = new AnnotatedCommandFactory();
127 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionalValue');
129 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
131 // Test to see if we can differentiate between a missing option, and
132 // an option that has no value at all.
133 $input = new StringInput('default:optional-value --foo=bar');
134 $this->assertRunCommandViaApplicationEquals($command, $input, "Foo is 'bar'");
136 $input = new StringInput('default:optional-value --foo');
137 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
139 $input = new StringInput('default:optional-value');
140 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is NULL');
143 function testOptionThatDefaultsToTrue()
145 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
146 $this->commandFactory = new AnnotatedCommandFactory();
148 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionDefaultsToTrue');
150 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
152 // Test to see if we can differentiate between a missing option, and
153 // an option that has no value at all.
154 $input = new StringInput('default:option-defaults-to-true --foo=bar');
155 $this->assertRunCommandViaApplicationEquals($command, $input, "Foo is 'bar'");
157 $input = new StringInput('default:option-defaults-to-true --foo');
158 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
160 $input = new StringInput('default:option-defaults-to-true');
161 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
163 $input = new StringInput('help default:option-defaults-to-true');
164 $this->assertRunCommandViaApplicationContains(
169 'Negate --foo option',
172 $input = new ArgvInput(['cli.php', 'default:option-defaults-to-true', '--no-foo']);
173 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is false');
175 // Do all of those things again, but with ArgvInput
176 $input = new ArgvInput(['cli.php', 'default:option-defaults-to-true', '--foo=bar']);
177 $this->assertRunCommandViaApplicationEquals($command, $input, "Foo is 'bar'");
179 $input = new ArgvInput(['cli.php', 'default:option-defaults-to-true', '--foo']);
180 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
182 $input = new ArgvInput(['cli.php', 'default:option-defaults-to-true']);
183 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is true');
185 $input = new ArgvInput(['cli.php', 'default:option-defaults-to-true', '--no-foo']);
186 $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is false');
189 * Test CommandInfo command caching.
192 * - Create all of the command info objects from one class, caching them.
193 * - Change the method name of one of the items in the cache to a non-existent method
194 * - Restore all of the cached commandinfo objects
195 * - Ensure that the non-existent method cached commandinfo was not created
196 * - Ensure that the now-missing cached commandinfo was still created
198 * This tests both save/restore, plus adding a new command method to
199 * a class, and removing a command method from a class.
201 function testAnnotatedCommandCache()
203 $testCacheStore = new \Consolidation\TestUtils\InMemoryCacheStore();
205 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
206 $this->commandFactory = new AnnotatedCommandFactory();
207 $this->commandFactory->setDataStore($testCacheStore);
209 // Make commandInfo objects for every command in the test commandfile.
210 // These will also be stored in our cache.
211 $commandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
213 $cachedClassName = get_class($this->commandFileInstance);
215 $this->assertTrue($testCacheStore->has($cachedClassName));
217 $cachedData = $testCacheStore->get($cachedClassName);
218 $this->assertFalse(empty($cachedData));
219 $this->assertTrue(array_key_exists('testArithmatic', $cachedData));
221 $alterCommandInfoCache = $cachedData['testArithmatic'];
222 unset($cachedData['testArithmatic']);
223 $alterCommandInfoCache['method_name'] = 'nonExistentMethod';
224 $cachedData[$alterCommandInfoCache['method_name']] = $alterCommandInfoCache;
226 $testCacheStore->set($cachedClassName, $cachedData);
228 $restoredCommandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
230 $rebuiltCachedData = $testCacheStore->get($cachedClassName);
232 $this->assertFalse(empty($rebuiltCachedData));
233 $this->assertTrue(array_key_exists('testArithmatic', $rebuiltCachedData));
234 $this->assertFalse(array_key_exists('nonExistentMethod', $rebuiltCachedData));
238 * Test CommandInfo command annotation parsing.
240 function testAnnotatedCommandCreation()
242 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
243 $this->commandFactory = new AnnotatedCommandFactory();
244 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArithmatic');
246 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
248 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
249 $this->assertEquals('test:arithmatic', $command->getName());
250 $this->assertEquals('This is the test:arithmatic command', $command->getDescription());
251 $this->assertEquals("This command will add one and two. If the --negate flag\nis provided, then the result is negated.", $command->getHelp());
252 $this->assertEquals('arithmatic', implode(',', $command->getAliases()));
253 $this->assertEquals('test:arithmatic [--negate] [--unused [UNUSED]] [--] <one> [<two>]', $command->getSynopsis());
254 $this->assertEquals('test:arithmatic 2 2 --negate', implode(',', $command->getUsages()));
256 $input = new StringInput('arithmatic 2 3 --negate');
257 $this->assertRunCommandViaApplicationEquals($command, $input, '-5');
261 * Test CommandInfo command annotation altering.
263 function testAnnotatedCommandInfoAlteration()
265 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
266 $this->commandFactory = new AnnotatedCommandFactory();
267 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
269 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
271 $annotationData = $command->getAnnotationData();
272 $this->assertTrue($annotationData->has('arbitrary'));
273 $this->assertFalse($annotationData->has('dynamic'));
275 $this->commandFactory->addCommandInfoAlterer(new ExampleCommandInfoAlterer());
277 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
279 $annotationData = $command->getAnnotationData();
280 $this->assertTrue($annotationData->has('arbitrary'));
281 $this->assertTrue($annotationData->has('dynamic'));
284 function testMyCatCommand()
286 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
287 $this->commandFactory = new AnnotatedCommandFactory();
288 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
290 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
292 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
293 $this->assertEquals('my:cat', $command->getName());
294 $this->assertEquals('This is the my:cat command', $command->getDescription());
295 $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());
296 $this->assertEquals('c', implode(',', $command->getAliases()));
297 $this->assertEquals('my:cat [--flip] [--] <one> [<two>]', $command->getSynopsis());
298 $this->assertEquals('my:cat bet alpha --flip', implode(',', $command->getUsages()));
300 $input = new StringInput('my:cat bet alpha --flip');
301 $this->assertRunCommandViaApplicationEquals($command, $input, 'alphabet');
304 function testJoinCommandHelp()
306 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
307 $this->commandFactory = new AnnotatedCommandFactory();
308 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
310 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
312 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
313 $this->assertEquals('my:join', $command->getName());
314 $this->assertEquals('This is the my:join command', $command->getDescription());
315 $this->assertEquals("This command will join its parameters together. It can also reverse and repeat its arguments.", $command->getHelp());
316 $this->assertEquals('my:join [--flip] [--repeat [REPEAT]] [--] [<args>]...', $command->getSynopsis());
318 // TODO: Extra whitespace character if there are no options et. al. in the
319 // usage. This is uncommon, and the defect is invisible. Maybe find it someday.
320 $actualUsages = implode(',', $command->getUsages());
321 $this->assertEquals('my:join a b,my:join ', $actualUsages);
323 $input = new StringInput('my:join bet alpha --flip --repeat=2');
324 $this->assertRunCommandViaApplicationEquals($command, $input, 'alphabetalphabet');
327 function testDefaultsCommand()
329 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
330 $this->commandFactory = new AnnotatedCommandFactory();
331 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaults');
333 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
335 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
336 $this->assertEquals('defaults', $command->getName());
337 $this->assertEquals('Test default values in arguments', $command->getDescription());
339 $input = new StringInput('defaults');
340 $this->assertRunCommandViaApplicationEquals($command, $input, 'nothing provided');
342 $input = new StringInput('defaults ichi');
343 $this->assertRunCommandViaApplicationEquals($command, $input, 'only ichi');
345 $input = new StringInput('defaults I II');
346 $this->assertRunCommandViaApplicationEquals($command, $input, 'I and II');
349 function testCommandWithNoOptions()
351 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
352 $this->commandFactory = new AnnotatedCommandFactory();
353 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoOptions');
355 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
357 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
358 $this->assertEquals('command:with-no-options', $command->getName());
359 $this->assertEquals('This is a command with no options', $command->getDescription());
360 $this->assertEquals("This command will concatenate two parameters.", $command->getHelp());
361 $this->assertEquals('nope', implode(',', $command->getAliases()));
362 $this->assertEquals('command:with-no-options <one> [<two>]', $command->getSynopsis());
363 $this->assertEquals('command:with-no-options alpha bet', implode(',', $command->getUsages()));
365 $input = new StringInput('command:with-no-options something');
366 $this->assertRunCommandViaApplicationEquals($command, $input, 'somethingdefault');
368 $input = new StringInput('help command:with-no-options something');
369 $this->assertRunCommandViaApplicationContains(
373 'The first parameter.',
374 'The other parameter.',
379 function testCommandWithIOParameters()
381 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
382 $this->commandFactory = new AnnotatedCommandFactory();
383 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithIOParameters');
385 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
387 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
388 $this->assertEquals('command:with-io-parameters', $command->getName());
389 $this->assertEquals("This command work with app's input and output", $command->getDescription());
390 $this->assertEquals('', $command->getHelp());
391 $this->assertEquals('command:with-io-parameters', $command->getSynopsis());
393 $input = new StringInput('command:with-io-parameters');
394 $this->assertRunCommandViaApplicationEquals($command, $input, 'command:with-io-parameters');
397 function testCommandWithNoArguments()
399 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
400 $this->commandFactory = new AnnotatedCommandFactory();
401 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoArguments');
403 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
405 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
406 $this->assertEquals('command:with-no-arguments', $command->getName());
407 $this->assertEquals('This command has no arguments--only options', $command->getDescription());
408 $this->assertEquals("Return a result only if not silent.", $command->getHelp());
409 $this->assertEquals('command:with-no-arguments [-s|--silent]', $command->getSynopsis());
411 $input = new StringInput('command:with-no-arguments');
412 $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
413 $input = new StringInput('command:with-no-arguments -s');
414 $this->assertRunCommandViaApplicationEquals($command, $input, '');
415 $input = new StringInput('command:with-no-arguments --silent');
416 $this->assertRunCommandViaApplicationEquals($command, $input, '');
419 function testCommandWithShortcutOnAnnotation()
421 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
422 $this->commandFactory = new AnnotatedCommandFactory();
423 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'shortcutOnAnnotation');
425 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
427 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
428 $this->assertEquals('shortcut:on-annotation', $command->getName());
429 $this->assertEquals('Shortcut on annotation', $command->getDescription());
430 $this->assertEquals("This command defines the option shortcut on the annotation instead of in the options array.", $command->getHelp());
431 $this->assertEquals('shortcut:on-annotation [-s|--silent]', $command->getSynopsis());
433 $input = new StringInput('shortcut:on-annotation');
434 $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
435 $input = new StringInput('shortcut:on-annotation -s');
436 $this->assertRunCommandViaApplicationEquals($command, $input, '');
437 $input = new StringInput('shortcut:on-annotation --silent');
438 $this->assertRunCommandViaApplicationEquals($command, $input, '');
443 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile('secret secret');
444 $this->commandFactory = new AnnotatedCommandFactory();
445 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testState');
447 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
449 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
450 $this->assertEquals('test:state', $command->getName());
452 $input = new StringInput('test:state');
453 $this->assertRunCommandViaApplicationEquals($command, $input, 'secret secret');
456 function testPassthroughArray()
458 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
459 $this->commandFactory = new AnnotatedCommandFactory();
460 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPassthrough');
462 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
464 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
465 $this->assertEquals('test:passthrough', $command->getName());
467 $input = new StringInput('test:passthrough a b c -- x y z');
468 $this->assertRunCommandViaApplicationEquals($command, $input, 'a,b,c,x,y,z');
471 function testPassThroughNonArray()
473 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
474 $this->commandFactory = new AnnotatedCommandFactory();
475 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
477 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
479 $input = new StringInput('my:join bet --flip -- x y z');
480 $this->assertRunCommandViaApplicationEquals($command, $input, 'zyxbet');
481 // Can't look at 'hasOption' until after the command initializes the
482 // option, because Symfony.
483 $this->assertTrue($input->hasOption('flip'));
486 function testPassThroughWithInputManipulation()
488 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
489 $this->commandFactory = new AnnotatedCommandFactory();
490 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
492 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
494 $input = new StringInput('my:join bet --repeat=2 -- x y z');
495 $this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
496 // Symfony does not allow us to manipulate the options via setOption until
497 // the definition from the command object has been set up.
498 $input->setOption('repeat', 3);
499 $this->assertEquals(3, $input->getOption('repeat'));
500 $input->setArgument(0, 'q');
501 // Manipulating $input does not work -- the changes are not effective.
502 // The end result here should be 'qx y yqx y yqx y y'
503 $this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
506 function testRequiredArrayOption()
508 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
509 $this->commandFactory = new AnnotatedCommandFactory();
510 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testRequiredArrayOption');
512 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
513 $this->assertEquals('test:required-array-option [-a|--arr ARR]', $command->getSynopsis());
515 $input = new StringInput('test:required-array-option --arr=1 --arr=2 --arr=3');
516 $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
518 $input = new StringInput('test:required-array-option -a 1 -a 2 -a 3');
519 $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
522 function testArrayOption()
524 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
525 $this->commandFactory = new AnnotatedCommandFactory();
526 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArrayOption');
528 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
529 $this->assertEquals('test:array-option [-a|--arr [ARR]]', $command->getSynopsis());
531 $input = new StringInput('test:array-option');
532 $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
534 $input = new StringInput('test:array-option --arr=a --arr=b --arr=c');
535 $this->assertRunCommandViaApplicationEquals($command, $input, 'a b c');
537 $input = new StringInput('test:array-option -a a');
538 $this->assertRunCommandViaApplicationEquals($command, $input, 'a');
541 function testHookedCommand()
543 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
544 $this->commandFactory = new AnnotatedCommandFactory();
546 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
548 $this->assertTrue($hookInfo->hasAnnotation('hook'));
549 $this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
551 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
553 $hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);
554 $this->assertTrue($hookCallback != null);
555 $this->assertEquals(1, count($hookCallback));
556 $this->assertEquals(2, count($hookCallback[0]));
557 $this->assertTrue(is_callable($hookCallback[0]));
558 $this->assertEquals('hookTestHook', $hookCallback[0][1]);
560 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
561 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
563 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
564 $this->assertEquals('test:hook', $command->getName());
566 $input = new StringInput('test:hook bar');
567 $this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
569 $input = new StringInput('list --raw');
570 $this->assertRunCommandViaApplicationContains($command, $input, ['This command wraps its parameter in []; its alter hook then wraps the result in .']);
573 function testReplaceCommandHook(){
574 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
575 $this->commandFactory = new AnnotatedCommandFactory();
577 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestReplaceCommandHook');
579 $this->assertTrue($hookInfo->hasAnnotation('hook'));
580 $this->assertEquals('replace-command test:replace-command', $hookInfo->getAnnotation('hook'));
582 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
584 $hookCallback = $this->commandFactory->hookManager()->get('test:replace-command', [HookManager::REPLACE_COMMAND_HOOK]);
585 $this->assertTrue($hookCallback != null);
586 $this->assertEquals(1, count($hookCallback));
587 $this->assertEquals(2, count($hookCallback[0]));
588 $this->assertTrue(is_callable($hookCallback[0]));
589 $this->assertEquals('hookTestReplaceCommandHook', $hookCallback[0][1]);
591 $input = new StringInput('test:replace-command foo');
592 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testReplaceCommand');
593 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
594 $this->assertRunCommandViaApplicationEquals($command, $input, "bar", 0);
597 function testPostCommandCalledAfterCommand()
599 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
600 $this->commandFactory = new AnnotatedCommandFactory();
602 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPostCommandHook');
604 $this->assertTrue($hookInfo->hasAnnotation('hook'));
605 $this->assertEquals('post-command test:post-command', $hookInfo->getAnnotation('hook'));
607 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
609 $hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::POST_COMMAND_HOOK]);
610 $this->assertTrue($hookCallback != null);
611 $this->assertEquals(1, count($hookCallback));
612 $this->assertEquals(2, count($hookCallback[0]));
613 $this->assertTrue(is_callable($hookCallback[0]));
614 $this->assertEquals('hookTestPostCommandHook', $hookCallback[0][1]);
616 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPreCommandHook');
618 $this->assertTrue($hookInfo->hasAnnotation('hook'));
619 $this->assertEquals('pre-command test:post-command', $hookInfo->getAnnotation('hook'));
621 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
623 $hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::PRE_COMMAND_HOOK]);
624 $this->assertTrue($hookCallback != null);
625 $this->assertEquals(1, count($hookCallback));
626 $this->assertEquals(2, count($hookCallback[0]));
627 $this->assertTrue(is_callable($hookCallback[0]));
628 $this->assertEquals('hookTestPreCommandHook', $hookCallback[0][1]);
630 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPostCommand');
631 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
633 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
634 $this->assertEquals('test:post-command', $command->getName());
636 $input = new StringInput('test:post-command bar');
637 $this->assertRunCommandViaApplicationEquals($command, $input, "foo\nbar\nbaz", 0, $this->commandFileInstance);
640 function testHookAllCommands()
642 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
643 $this->commandFactory = new AnnotatedCommandFactory();
645 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterAllCommands');
647 $this->assertTrue($hookInfo->hasAnnotation('hook'));
648 $this->assertEquals('alter', $hookInfo->getAnnotation('hook'));
650 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
652 $hookCallback = $this->commandFactory->hookManager()->get('Consolidation\TestUtils\ExampleHookAllCommandFile', [HookManager::ALTER_RESULT]);
653 $this->assertTrue($hookCallback != null);
654 $this->assertEquals(1, count($hookCallback));
655 $this->assertEquals(2, count($hookCallback[0]));
656 $this->assertTrue(is_callable($hookCallback[0]));
657 $this->assertEquals('alterAllCommands', $hookCallback[0][1]);
659 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
660 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
662 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
663 $this->assertEquals('do:cat', $command->getName());
665 $input = new StringInput('do:cat bar');
666 $this->assertRunCommandViaApplicationEquals($command, $input, '*** bar ***');
669 function testDoubleDashWithVersion()
671 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
672 $this->commandFactory = new AnnotatedCommandFactory();
673 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
674 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
676 $input = new ArgvInput(['placeholder', 'do:cat', 'one', '--', '--version']);
677 list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
679 if ($commandOutput == 'TestApplication version 0.0.0') {
680 $this->markTestSkipped('Symfony/Console 2.x does not respect -- with --version');
682 $this->assertEquals('one--version', $commandOutput);
685 function testAnnotatedHookedCommand()
687 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
688 $this->commandFactory = new AnnotatedCommandFactory();
690 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestAnnotatedHook');
692 $this->assertTrue($hookInfo->hasAnnotation('hook'));
693 $this->assertEquals('alter @hookme', $hookInfo->getAnnotation('hook'));
695 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
696 $hookCallback = $this->commandFactory->hookManager()->get('@hookme', [HookManager::ALTER_RESULT]);
697 $this->assertTrue($hookCallback != null);
698 $this->assertEquals(1, count($hookCallback));
699 $this->assertEquals(2, count($hookCallback[0]));
700 $this->assertTrue(is_callable($hookCallback[0]));
701 $this->assertEquals('hookTestAnnotatedHook', $hookCallback[0][1]);
703 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testAnnotationHook');
704 $annotationData = $commandInfo->getRawAnnotations();
705 $this->assertEquals('hookme,before,after', implode(',', $annotationData->keys()));
706 $this->assertEquals('@hookme,@before,@after', implode(',', array_map(function ($item) { return "@$item"; }, $annotationData->keys())));
708 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
710 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
711 $this->assertEquals('test:annotation-hook', $command->getName());
713 $input = new StringInput('test:annotation-hook baz');
714 $this->assertRunCommandViaApplicationEquals($command, $input, '>(baz)<');
717 function testHookHasCommandAnnotation()
719 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
720 $this->commandFactory = new AnnotatedCommandFactory();
722 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookAddCommandName');
724 $this->assertTrue($hookInfo->hasAnnotation('hook'));
725 $this->assertEquals('alter @addmycommandname', $hookInfo->getAnnotation('hook'));
727 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
728 $hookCallback = $this->commandFactory->hookManager()->get('@addmycommandname', [HookManager::ALTER_RESULT]);
729 $this->assertTrue($hookCallback != null);
730 $this->assertEquals(1, count($hookCallback));
731 $this->assertEquals(2, count($hookCallback[0]));
732 $this->assertTrue(is_callable($hookCallback[0]));
733 $this->assertEquals('hookAddCommandName', $hookCallback[0][1]);
735 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMe');
736 $annotationData = $commandInfo->getRawAnnotations();
737 $this->assertEquals('command,addmycommandname', implode(',', $annotationData->keys()));
739 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
741 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
742 $this->assertEquals('alter-me', $command->getName());
744 $input = new StringInput('alter-me');
745 $this->assertRunCommandViaApplicationEquals($command, $input, 'splendiferous from alter-me');
747 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMeToo');
748 $annotationData = $commandInfo->getRawAnnotations();
749 $this->assertEquals('addmycommandname', implode(',', $annotationData->keys()));
750 $annotationData = $commandInfo->getAnnotations();
751 $this->assertEquals('addmycommandname,command,_path,_classname', implode(',', $annotationData->keys()));
753 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
755 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
756 $this->assertEquals('alter:me-too', $command->getName());
758 $input = new StringInput('alter:me-too');
759 $this->assertRunCommandViaApplicationEquals($command, $input, 'fantabulous from alter:me-too');
762 function testHookedCommandWithHookAddedLater()
764 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
765 $this->commandFactory = new AnnotatedCommandFactory();
766 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
768 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
770 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
771 $this->assertEquals('test:hook', $command->getName());
773 // Run the command once without the hook
774 $input = new StringInput('test:hook foo');
775 $this->assertRunCommandViaApplicationEquals($command, $input, '[foo]');
777 // Register the hook and run the command again
778 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
780 $this->assertTrue($hookInfo->hasAnnotation('hook'));
781 $this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
783 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
784 $hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);;
785 $this->assertTrue($hookCallback != null);
786 $this->assertEquals(1, count($hookCallback));
787 $this->assertEquals(2, count($hookCallback[0]));
788 $this->assertTrue(is_callable($hookCallback[0]));
789 $this->assertEquals('hookTestHook', $hookCallback[0][1]);
791 $input = new StringInput('test:hook bar');
792 $this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
795 function testInitializeHook()
797 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
798 $this->commandFactory = new AnnotatedCommandFactory();
800 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'initializeTestHello');
802 $this->assertTrue($hookInfo->hasAnnotation('hook'));
803 $this->assertEquals($hookInfo->getAnnotation('hook'), 'init test:hello');
805 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
807 $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INITIALIZE]);
808 $this->assertTrue($hookCallback != null);
809 $this->assertEquals(1, count($hookCallback));
810 $this->assertEquals(2, count($hookCallback[0]));
811 $this->assertTrue(is_callable($hookCallback[0]));
812 $this->assertEquals('initializeTestHello', $hookCallback[0][1]);
814 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
815 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
817 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
818 $this->assertEquals('test:hello', $command->getName());
819 $commandGetNames = $this->callProtected($command, 'getNames');
820 $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
822 $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'init');
823 $this->assertTrue($hookCallback != null);
824 $this->assertEquals('initializeTestHello', $hookCallback[0][1]);
826 $input = new StringInput('test:hello');
827 $this->assertRunCommandViaApplicationEquals($command, $input, "Hello, Huey.");
830 function testCommandEventHook()
832 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
833 $this->commandFactory = new AnnotatedCommandFactory();
835 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandEventTestHello');
837 $this->assertTrue($hookInfo->hasAnnotation('hook'));
838 $this->assertEquals($hookInfo->getAnnotation('hook'), 'command-event test:hello');
840 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
842 $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::COMMAND_EVENT]);
843 $this->assertTrue($hookCallback != null);
844 $this->assertEquals(1, count($hookCallback));
845 $this->assertEquals(2, count($hookCallback[0]));
846 $this->assertTrue(is_callable($hookCallback[0]));
847 $this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
849 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
850 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
852 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
853 $this->assertEquals('test:hello', $command->getName());
854 $commandGetNames = $this->callProtected($command, 'getNames');
855 $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
857 $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'command-event');
858 $this->assertTrue($hookCallback != null);
859 $this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
861 $input = new StringInput('test:hello Pluto');
862 $this->assertRunCommandViaApplicationEquals($command, $input, "Here comes Pluto!\nHello, Pluto.");
866 function testInteractAndValidate()
868 $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
869 $this->commandFactory = new AnnotatedCommandFactory();
871 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'interactTestHello');
873 $this->assertTrue($hookInfo->hasAnnotation('hook'));
874 $this->assertEquals($hookInfo->getAnnotation('hook'), 'interact test:hello');
876 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
878 $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'validateTestHello');
880 $this->assertTrue($hookInfo->hasAnnotation('hook'));
881 $this->assertEquals($hookInfo->getAnnotation('hook'), 'validate test:hello');
883 $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
885 $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::ARGUMENT_VALIDATOR]);
886 $this->assertTrue($hookCallback != null);
887 $this->assertEquals(1, count($hookCallback));
888 $this->assertEquals(2, count($hookCallback[0]));
889 $this->assertTrue(is_callable($hookCallback[0]));
890 $this->assertEquals('validateTestHello', $hookCallback[0][1]);
892 $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INTERACT]);
893 $this->assertTrue($hookCallback != null);
894 $this->assertEquals(1, count($hookCallback));
895 $this->assertEquals(2, count($hookCallback[0]));
896 $this->assertTrue(is_callable($hookCallback[0]));
897 $this->assertEquals('interactTestHello', $hookCallback[0][1]);
899 $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
900 $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
902 $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
903 $this->assertEquals('test:hello', $command->getName());
904 $commandGetNames = $this->callProtected($command, 'getNames');
905 $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
907 $testInteractInput = new StringInput('test:hello');
908 $definition = new \Symfony\Component\Console\Input\InputDefinition(
910 new \Symfony\Component\Console\Input\InputArgument('application', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
911 new \Symfony\Component\Console\Input\InputArgument('who', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
914 $testInteractInput->bind($definition);
915 $testInteractOutput = new BufferedOutput();
916 $command->commandProcessor()->interact(
920 $command->getAnnotationData()
922 $this->assertEquals('Goofey', $testInteractInput->getArgument('who'));
924 $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'interact');
925 $this->assertTrue($hookCallback != null);
926 $this->assertEquals('interactTestHello', $hookCallback[0][1]);
928 $input = new StringInput('test:hello "Mickey Mouse"');
929 $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Mickey Mouse.');
931 $input = new StringInput('test:hello');
932 $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Goofey.');
934 $input = new StringInput('test:hello "Donald Duck"');
935 $this->assertRunCommandViaApplicationEquals($command, $input, "I won't say hello to Donald Duck.", 1);
937 $input = new StringInput('test:hello "Drumph"');
938 $this->assertRunCommandViaApplicationEquals($command, $input, "Irrational value error.", 1);
940 // Try the last test again with a display error function installed.
941 $this->commandFactory->commandProcessor()->setDisplayErrorFunction(
942 function ($output, $message) {
943 $output->writeln("*** $message ****");
947 $input = new StringInput('test:hello "Drumph"');
948 $this->assertRunCommandViaApplicationEquals($command, $input, "*** Irrational value error. ****", 1);
951 function callProtected($object, $method, $args = [])
953 $r = new \ReflectionMethod($object, $method);
954 $r->setAccessible(true);
955 return $r->invokeArgs($object, $args);
958 function assertRunCommandViaApplicationContains($command, $input, $containsList, $expectedStatusCode = 0)
960 list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
962 foreach ($containsList as $contains) {
963 $this->assertContains($contains, $commandOutput);
965 $this->assertEquals($expectedStatusCode, $statusCode);
968 function assertRunCommandViaApplicationEquals($command, $input, $expectedOutput, $expectedStatusCode = 0)
970 list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
972 $this->assertEquals($expectedOutput, $commandOutput);
973 $this->assertEquals($expectedStatusCode, $statusCode);
976 function runCommandViaApplication($command, $input)
978 $output = new BufferedOutput();
979 if ($this->commandFileInstance && method_exists($this->commandFileInstance, 'setOutput')) {
980 $this->commandFileInstance->setOutput($output);
983 $application = new Application('TestApplication', '0.0.0');
984 $alterOptionsEventManager = new AlterOptionsCommandEvent($application);
986 $eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
987 $eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
988 $this->commandFactory->commandProcessor()->hookManager()->addCommandEvent($alterOptionsEventManager);
989 $application->setDispatcher($eventDispatcher);
991 $application->setAutoExit(false);
992 $application->add($command);
994 $statusCode = $application->run($input, $output);
995 $commandOutput = trim(str_replace("\r", '', $output->fetch()));
997 return [$statusCode, $commandOutput];