Version 1
[yaffs-website] / vendor / consolidation / annotated-command / tests / testAnnotatedCommandFactory.php
1 <?php
2 namespace Consolidation\AnnotatedCommand;
3
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;
17
18 class AnnotatedCommandFactoryTests extends \PHPUnit_Framework_TestCase
19 {
20     protected $commandFileInstance;
21     protected $commandFactory;
22
23     function testOptionDefaultValue()
24     {
25         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
26         $this->commandFactory = new AnnotatedCommandFactory();
27         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionOne');
28
29         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
30         $this->assertEquals('default:option-one', $command->getName());
31         $this->assertEquals('default:option-one [--foo [FOO]]', $command->getSynopsis());
32
33         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
34
35         $input = new StringInput('default:option-one');
36         $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 1');
37
38         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionTwo');
39
40         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
41
42         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
43         $this->assertEquals('default:option-two', $command->getName());
44         $this->assertEquals('default:option-two [--foo [FOO]]', $command->getSynopsis());
45
46         $input = new StringInput('default:option-two');
47         $this->assertRunCommandViaApplicationEquals($command, $input, 'Foo is 2');
48
49         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaultOptionNone');
50
51         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
52
53         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
54         $this->assertEquals('default:option-none', $command->getName());
55         $this->assertEquals('default:option-none [--foo FOO]', $command->getSynopsis());
56
57         // Skip failing test until Symfony is fixed.
58         $this->markTestSkipped('Symfony Console 3.2.5 and 3.2.6 do not handle default options with required values correctly.');
59
60         $input = new StringInput('default:option-none --foo');
61         $this->assertRunCommandViaApplicationContains($command, $input, ['The "--foo" option requires a value.'], 1);
62     }
63
64     /**
65      * Test CommandInfo command caching.
66      *
67      * Sequence:
68      *  - Create all of the command info objects from one class, caching them.
69      *  - Change the method name of one of the items in the cache to a non-existent method
70      *  - Restore all of the cached commandinfo objects
71      *  - Ensure that the non-existent method cached commandinfo was not created
72      *  - Ensure that the now-missing cached commandinfo was still created
73      *
74      * This tests both save/restore, plus adding a new command method to
75      * a class, and removing a command method from a class.
76      */
77     function testAnnotatedCommandCache()
78     {
79         $testCacheStore = new \Consolidation\TestUtils\InMemoryCacheStore();
80
81         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
82         $this->commandFactory = new AnnotatedCommandFactory();
83         $this->commandFactory->setDataStore($testCacheStore);
84
85         // Make commandInfo objects for every command in the test commandfile.
86         // These will also be stored in our cache.
87         $commandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
88
89         $cachedClassName = get_class($this->commandFileInstance);
90
91         $this->assertTrue($testCacheStore->has($cachedClassName));
92
93         $cachedData = $testCacheStore->get($cachedClassName);
94         $this->assertFalse(empty($cachedData));
95         $this->assertTrue(array_key_exists('testArithmatic', $cachedData));
96
97         $alterCommandInfoCache = $cachedData['testArithmatic'];
98         unset($cachedData['testArithmatic']);
99         $alterCommandInfoCache['method_name'] = 'nonExistentMethod';
100         $cachedData[$alterCommandInfoCache['method_name']] = $alterCommandInfoCache;
101
102         $testCacheStore->set($cachedClassName, $cachedData);
103
104         $restoredCommandInfoList = $this->commandFactory->getCommandInfoListFromClass($this->commandFileInstance);
105
106         $rebuiltCachedData = $testCacheStore->get($cachedClassName);
107
108         $this->assertFalse(empty($rebuiltCachedData));
109         $this->assertTrue(array_key_exists('testArithmatic', $rebuiltCachedData));
110         $this->assertFalse(array_key_exists('nonExistentMethod', $rebuiltCachedData));
111     }
112
113     /**
114      * Test CommandInfo command annotation parsing.
115      */
116     function testAnnotatedCommandCreation()
117     {
118         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
119         $this->commandFactory = new AnnotatedCommandFactory();
120         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArithmatic');
121
122         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
123
124         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
125         $this->assertEquals('test:arithmatic', $command->getName());
126         $this->assertEquals('This is the test:arithmatic command', $command->getDescription());
127         $this->assertEquals("This command will add one and two. If the --negate flag\nis provided, then the result is negated.", $command->getHelp());
128         $this->assertEquals('arithmatic', implode(',', $command->getAliases()));
129         $this->assertEquals('test:arithmatic [--negate] [--unused [UNUSED]] [--] <one> [<two>]', $command->getSynopsis());
130         $this->assertEquals('test:arithmatic 2 2 --negate', implode(',', $command->getUsages()));
131
132         $input = new StringInput('arithmatic 2 3 --negate');
133         $this->assertRunCommandViaApplicationEquals($command, $input, '-5');
134     }
135
136     /**
137      * Test CommandInfo command annotation altering.
138      */
139     function testAnnotatedCommandInfoAlteration()
140     {
141         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
142         $this->commandFactory = new AnnotatedCommandFactory();
143         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
144
145         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
146
147         $annotationData = $command->getAnnotationData();
148         $this->assertTrue($annotationData->has('arbitrary'));
149         $this->assertFalse($annotationData->has('dynamic'));
150
151         $this->commandFactory->addCommandInfoAlterer(new ExampleCommandInfoAlterer());
152
153         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
154
155         $annotationData = $command->getAnnotationData();
156         $this->assertTrue($annotationData->has('arbitrary'));
157         $this->assertTrue($annotationData->has('dynamic'));
158     }
159
160     function testMyCatCommand()
161     {
162         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
163         $this->commandFactory = new AnnotatedCommandFactory();
164         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myCat');
165
166         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
167
168         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
169         $this->assertEquals('my:cat', $command->getName());
170         $this->assertEquals('This is the my:cat command', $command->getDescription());
171         $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());
172         $this->assertEquals('c', implode(',', $command->getAliases()));
173         $this->assertEquals('my:cat [--flip] [--] <one> [<two>]', $command->getSynopsis());
174         $this->assertEquals('my:cat bet alpha --flip', implode(',', $command->getUsages()));
175
176         $input = new StringInput('my:cat bet alpha --flip');
177         $this->assertRunCommandViaApplicationEquals($command, $input, 'alphabet');
178     }
179
180     function testJoinCommandHelp()
181     {
182         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
183         $this->commandFactory = new AnnotatedCommandFactory();
184         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
185
186         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
187
188         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
189         $this->assertEquals('my:join', $command->getName());
190         $this->assertEquals('This is the my:join command', $command->getDescription());
191         $this->assertEquals("This command will join its parameters together. It can also reverse and repeat its arguments.", $command->getHelp());
192         $this->assertEquals('my:join [--flip] [--repeat [REPEAT]] [--] [<args>]...', $command->getSynopsis());
193
194         // Bug in parser: @usage with no parameters or options not passed to us correctly.
195         $actualUsages = implode(',', $command->getUsages());
196         if ($actualUsages == 'my:join a b,my:join Example with no parameters or options') {
197             $this->markTestSkipped();
198         }
199         $this->assertEquals('my:join a b,my:join', $actualUsages);
200
201         $input = new StringInput('my:join bet alpha --flip --repeat=2');
202         $this->assertRunCommandViaApplicationEquals($command, $input, 'alphabetalphabet');
203     }
204
205     function testDefaultsCommand()
206     {
207         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
208         $this->commandFactory = new AnnotatedCommandFactory();
209         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'defaults');
210
211         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
212
213         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
214         $this->assertEquals('defaults', $command->getName());
215         $this->assertEquals('Test default values in arguments', $command->getDescription());
216
217         $input = new StringInput('defaults');
218         $this->assertRunCommandViaApplicationEquals($command, $input, 'nothing provided');
219
220         $input = new StringInput('defaults ichi');
221         $this->assertRunCommandViaApplicationEquals($command, $input, 'only ichi');
222
223         $input = new StringInput('defaults I II');
224         $this->assertRunCommandViaApplicationEquals($command, $input, 'I and II');
225     }
226
227     function testCommandWithNoOptions()
228     {
229         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
230         $this->commandFactory = new AnnotatedCommandFactory();
231         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoOptions');
232
233         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
234
235         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
236         $this->assertEquals('command:with-no-options', $command->getName());
237         $this->assertEquals('This is a command with no options', $command->getDescription());
238         $this->assertEquals("This command will concatenate two parameters.", $command->getHelp());
239         $this->assertEquals('nope', implode(',', $command->getAliases()));
240         $this->assertEquals('command:with-no-options <one> [<two>]', $command->getSynopsis());
241         $this->assertEquals('command:with-no-options alpha bet', implode(',', $command->getUsages()));
242
243         $input = new StringInput('command:with-no-options something');
244         $this->assertRunCommandViaApplicationEquals($command, $input, 'somethingdefault');
245
246         $input = new StringInput('help command:with-no-options something');
247         $this->assertRunCommandViaApplicationContains(
248             $command,
249             $input,
250             [
251                 'The first parameter.',
252                 'The other parameter.',
253             ]
254         );
255     }
256
257     function testCommandWithNoArguments()
258     {
259         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
260         $this->commandFactory = new AnnotatedCommandFactory();
261         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandWithNoArguments');
262
263         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
264
265         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
266         $this->assertEquals('command:with-no-arguments', $command->getName());
267         $this->assertEquals('This command has no arguments--only options', $command->getDescription());
268         $this->assertEquals("Return a result only if not silent.", $command->getHelp());
269         $this->assertEquals('command:with-no-arguments [-s|--silent]', $command->getSynopsis());
270
271         $input = new StringInput('command:with-no-arguments');
272         $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
273         $input = new StringInput('command:with-no-arguments -s');
274         $this->assertRunCommandViaApplicationEquals($command, $input, '');
275         $input = new StringInput('command:with-no-arguments --silent');
276         $this->assertRunCommandViaApplicationEquals($command, $input, '');
277     }
278
279     function testCommandWithShortcutOnAnnotation()
280     {
281         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
282         $this->commandFactory = new AnnotatedCommandFactory();
283         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'shortcutOnAnnotation');
284
285         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
286
287         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
288         $this->assertEquals('shortcut:on-annotation', $command->getName());
289         $this->assertEquals('Shortcut on annotation', $command->getDescription());
290         $this->assertEquals("This command defines the option shortcut on the annotation instead of in the options array.", $command->getHelp());
291         $this->assertEquals('shortcut:on-annotation [-s|--silent]', $command->getSynopsis());
292
293         $input = new StringInput('shortcut:on-annotation');
294         $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, world');
295         $input = new StringInput('shortcut:on-annotation -s');
296         $this->assertRunCommandViaApplicationEquals($command, $input, '');
297         $input = new StringInput('shortcut:on-annotation --silent');
298         $this->assertRunCommandViaApplicationEquals($command, $input, '');
299     }
300
301     function testState()
302     {
303         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile('secret secret');
304         $this->commandFactory = new AnnotatedCommandFactory();
305         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testState');
306
307         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
308
309         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
310         $this->assertEquals('test:state', $command->getName());
311
312         $input = new StringInput('test:state');
313         $this->assertRunCommandViaApplicationEquals($command, $input, 'secret secret');
314     }
315
316     function testPassthroughArray()
317     {
318         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
319         $this->commandFactory = new AnnotatedCommandFactory();
320         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPassthrough');
321
322         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
323
324         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
325         $this->assertEquals('test:passthrough', $command->getName());
326
327         $input = new StringInput('test:passthrough a b c -- x y z');
328         $this->assertRunCommandViaApplicationEquals($command, $input, 'a,b,c,x,y,z');
329     }
330
331     function testPassThroughNonArray()
332     {
333         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
334         $this->commandFactory = new AnnotatedCommandFactory();
335         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
336
337         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
338
339         $input = new StringInput('my:join bet --flip -- x y z');
340         $this->assertRunCommandViaApplicationEquals($command, $input, 'zyxbet');
341         // Can't look at 'hasOption' until after the command initializes the
342         // option, because Symfony.
343         $this->assertTrue($input->hasOption('flip'));
344     }
345
346     function testPassThroughWithInputManipulation()
347     {
348         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
349         $this->commandFactory = new AnnotatedCommandFactory();
350         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'myJoin');
351
352         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
353
354         $input = new StringInput('my:join bet --repeat=2 -- x y z');
355         $this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
356         // Symfony does not allow us to manipulate the options via setOption until
357         // the definition from the command object has been set up.
358         $input->setOption('repeat', 3);
359         $this->assertEquals(3, $input->getOption('repeat'));
360         $input->setArgument(0, 'q');
361         // Manipulating $input does not work -- the changes are not effective.
362         // The end result here should be 'qx y yqx y yqx y y'
363         $this->assertRunCommandViaApplicationEquals($command, $input, 'betxyzbetxyz');
364     }
365
366     function testRequiredArrayOption()
367     {
368         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
369         $this->commandFactory = new AnnotatedCommandFactory();
370         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testRequiredArrayOption');
371
372         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
373         $this->assertEquals('test:required-array-option [-a|--arr ARR]', $command->getSynopsis());
374
375         $input = new StringInput('test:required-array-option --arr=1 --arr=2 --arr=3');
376         $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
377
378         $input = new StringInput('test:required-array-option -a 1 -a 2 -a 3');
379         $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
380     }
381
382     function testArrayOption()
383     {
384         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile;
385         $this->commandFactory = new AnnotatedCommandFactory();
386         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testArrayOption');
387
388         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
389         $this->assertEquals('test:array-option [-a|--arr [ARR]]', $command->getSynopsis());
390
391         $input = new StringInput('test:array-option');
392         $this->assertRunCommandViaApplicationEquals($command, $input, '1 2 3');
393
394         $input = new StringInput('test:array-option --arr=a --arr=b --arr=c');
395         $this->assertRunCommandViaApplicationEquals($command, $input, 'a b c');
396
397         $input = new StringInput('test:array-option -a a');
398         $this->assertRunCommandViaApplicationEquals($command, $input, 'a');
399     }
400
401     function testHookedCommand()
402     {
403         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
404         $this->commandFactory = new AnnotatedCommandFactory();
405
406         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
407
408         $this->assertTrue($hookInfo->hasAnnotation('hook'));
409         $this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
410
411         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
412
413         $hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);
414         $this->assertTrue($hookCallback != null);
415         $this->assertEquals(1, count($hookCallback));
416         $this->assertEquals(2, count($hookCallback[0]));
417         $this->assertTrue(is_callable($hookCallback[0]));
418         $this->assertEquals('hookTestHook', $hookCallback[0][1]);
419
420         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
421         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
422
423         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
424         $this->assertEquals('test:hook', $command->getName());
425
426         $input = new StringInput('test:hook bar');
427         $this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
428
429         $input = new StringInput('list --raw');
430         $this->assertRunCommandViaApplicationContains($command, $input, ['This command wraps its parameter in []; its alter hook then wraps the result in .']);
431     }
432
433     function testReplaceCommandHook(){
434         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
435         $this->commandFactory = new AnnotatedCommandFactory();
436
437         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestReplaceCommandHook');
438
439         $this->assertTrue($hookInfo->hasAnnotation('hook'));
440         $this->assertEquals('replace-command test:replace-command', $hookInfo->getAnnotation('hook'));
441
442         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
443
444         $hookCallback = $this->commandFactory->hookManager()->get('test:replace-command', [HookManager::REPLACE_COMMAND_HOOK]);
445         $this->assertTrue($hookCallback != null);
446         $this->assertEquals(1, count($hookCallback));
447         $this->assertEquals(2, count($hookCallback[0]));
448         $this->assertTrue(is_callable($hookCallback[0]));
449         $this->assertEquals('hookTestReplaceCommandHook', $hookCallback[0][1]);
450
451         $input = new StringInput('test:replace-command foo');
452         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testReplaceCommand');
453         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
454         $this->assertRunCommandViaApplicationEquals($command, $input, "bar", 0);
455     }
456
457     function testPostCommandCalledAfterCommand()
458     {
459         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
460         $this->commandFactory = new AnnotatedCommandFactory();
461
462         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPostCommandHook');
463
464         $this->assertTrue($hookInfo->hasAnnotation('hook'));
465         $this->assertEquals('post-command test:post-command', $hookInfo->getAnnotation('hook'));
466
467         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
468
469         $hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::POST_COMMAND_HOOK]);
470         $this->assertTrue($hookCallback != null);
471         $this->assertEquals(1, count($hookCallback));
472         $this->assertEquals(2, count($hookCallback[0]));
473         $this->assertTrue(is_callable($hookCallback[0]));
474         $this->assertEquals('hookTestPostCommandHook', $hookCallback[0][1]);
475
476         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestPreCommandHook');
477
478         $this->assertTrue($hookInfo->hasAnnotation('hook'));
479         $this->assertEquals('pre-command test:post-command', $hookInfo->getAnnotation('hook'));
480
481         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
482
483         $hookCallback = $this->commandFactory->hookManager()->get('test:post-command', [HookManager::PRE_COMMAND_HOOK]);
484         $this->assertTrue($hookCallback != null);
485         $this->assertEquals(1, count($hookCallback));
486         $this->assertEquals(2, count($hookCallback[0]));
487         $this->assertTrue(is_callable($hookCallback[0]));
488         $this->assertEquals('hookTestPreCommandHook', $hookCallback[0][1]);
489
490         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testPostCommand');
491         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
492
493         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
494         $this->assertEquals('test:post-command', $command->getName());
495
496         $input = new StringInput('test:post-command bar');
497         $this->assertRunCommandViaApplicationEquals($command, $input, "foo\nbar\nbaz", 0, $this->commandFileInstance);
498     }
499
500     function testHookAllCommands()
501     {
502         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
503         $this->commandFactory = new AnnotatedCommandFactory();
504
505         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterAllCommands');
506
507         $this->assertTrue($hookInfo->hasAnnotation('hook'));
508         $this->assertEquals('alter', $hookInfo->getAnnotation('hook'));
509
510         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
511
512         $hookCallback = $this->commandFactory->hookManager()->get('Consolidation\TestUtils\ExampleHookAllCommandFile', [HookManager::ALTER_RESULT]);
513         $this->assertTrue($hookCallback != null);
514         $this->assertEquals(1, count($hookCallback));
515         $this->assertEquals(2, count($hookCallback[0]));
516         $this->assertTrue(is_callable($hookCallback[0]));
517         $this->assertEquals('alterAllCommands', $hookCallback[0][1]);
518
519         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
520         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
521
522         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
523         $this->assertEquals('do:cat', $command->getName());
524
525         $input = new StringInput('do:cat bar');
526         $this->assertRunCommandViaApplicationEquals($command, $input, '*** bar ***');
527     }
528
529     function testDoubleDashWithVersion()
530     {
531         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleHookAllCommandFile();
532         $this->commandFactory = new AnnotatedCommandFactory();
533         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'doCat');
534         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
535
536         $input = new ArgvInput(['placeholder', 'do:cat', 'one', '--', '--version']);
537         list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
538
539         if ($commandOutput == 'TestApplication version 0.0.0') {
540             $this->markTestSkipped('Symfony/Console 2.x does not respect -- with --version');
541         }
542         $this->assertEquals('one--version', $commandOutput);
543     }
544
545     function testAnnotatedHookedCommand()
546     {
547         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
548         $this->commandFactory = new AnnotatedCommandFactory();
549
550         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestAnnotatedHook');
551
552         $this->assertTrue($hookInfo->hasAnnotation('hook'));
553         $this->assertEquals('alter @hookme', $hookInfo->getAnnotation('hook'));
554
555         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
556         $hookCallback = $this->commandFactory->hookManager()->get('@hookme', [HookManager::ALTER_RESULT]);
557         $this->assertTrue($hookCallback != null);
558         $this->assertEquals(1, count($hookCallback));
559         $this->assertEquals(2, count($hookCallback[0]));
560         $this->assertTrue(is_callable($hookCallback[0]));
561         $this->assertEquals('hookTestAnnotatedHook', $hookCallback[0][1]);
562
563         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testAnnotationHook');
564         $annotationData = $commandInfo->getRawAnnotations();
565         $this->assertEquals('hookme,before,after', implode(',', $annotationData->keys()));
566         $this->assertEquals('@hookme,@before,@after', implode(',', array_map(function ($item) { return "@$item"; }, $annotationData->keys())));
567
568         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
569
570         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
571         $this->assertEquals('test:annotation-hook', $command->getName());
572
573         $input = new StringInput('test:annotation-hook baz');
574         $this->assertRunCommandViaApplicationEquals($command, $input, '>(baz)<');
575     }
576
577     function testHookHasCommandAnnotation()
578     {
579         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
580         $this->commandFactory = new AnnotatedCommandFactory();
581
582         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookAddCommandName');
583
584         $this->assertTrue($hookInfo->hasAnnotation('hook'));
585         $this->assertEquals('alter @addmycommandname', $hookInfo->getAnnotation('hook'));
586
587         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
588         $hookCallback = $this->commandFactory->hookManager()->get('@addmycommandname', [HookManager::ALTER_RESULT]);
589         $this->assertTrue($hookCallback != null);
590         $this->assertEquals(1, count($hookCallback));
591         $this->assertEquals(2, count($hookCallback[0]));
592         $this->assertTrue(is_callable($hookCallback[0]));
593         $this->assertEquals('hookAddCommandName', $hookCallback[0][1]);
594
595         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMe');
596         $annotationData = $commandInfo->getRawAnnotations();
597         $this->assertEquals('command,addmycommandname', implode(',', $annotationData->keys()));
598
599         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
600
601         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
602         $this->assertEquals('alter-me', $command->getName());
603
604         $input = new StringInput('alter-me');
605         $this->assertRunCommandViaApplicationEquals($command, $input, 'splendiferous from alter-me');
606
607         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'alterMeToo');
608         $annotationData = $commandInfo->getRawAnnotations();
609         $this->assertEquals('addmycommandname', implode(',', $annotationData->keys()));
610         $annotationData = $commandInfo->getAnnotations();
611         $this->assertEquals('addmycommandname,command,_path,_classname', implode(',', $annotationData->keys()));
612
613         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
614
615         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
616         $this->assertEquals('alter:me-too', $command->getName());
617
618         $input = new StringInput('alter:me-too');
619         $this->assertRunCommandViaApplicationEquals($command, $input, 'fantabulous from alter:me-too');
620     }
621
622     function testHookedCommandWithHookAddedLater()
623     {
624         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
625         $this->commandFactory = new AnnotatedCommandFactory();
626         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHook');
627
628         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
629
630         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
631         $this->assertEquals('test:hook', $command->getName());
632
633         // Run the command once without the hook
634         $input = new StringInput('test:hook foo');
635         $this->assertRunCommandViaApplicationEquals($command, $input, '[foo]');
636
637         // Register the hook and run the command again
638         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'hookTestHook');
639
640         $this->assertTrue($hookInfo->hasAnnotation('hook'));
641         $this->assertEquals('alter test:hook', $hookInfo->getAnnotation('hook'));
642
643         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
644         $hookCallback = $this->commandFactory->hookManager()->get('test:hook', [HookManager::ALTER_RESULT]);;
645         $this->assertTrue($hookCallback != null);
646         $this->assertEquals(1, count($hookCallback));
647         $this->assertEquals(2, count($hookCallback[0]));
648         $this->assertTrue(is_callable($hookCallback[0]));
649         $this->assertEquals('hookTestHook', $hookCallback[0][1]);
650
651         $input = new StringInput('test:hook bar');
652         $this->assertRunCommandViaApplicationEquals($command, $input, '<[bar]>');
653     }
654
655     function testInitializeHook()
656     {
657         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
658         $this->commandFactory = new AnnotatedCommandFactory();
659
660         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'initializeTestHello');
661
662         $this->assertTrue($hookInfo->hasAnnotation('hook'));
663         $this->assertEquals($hookInfo->getAnnotation('hook'), 'init test:hello');
664
665         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
666
667         $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INITIALIZE]);
668         $this->assertTrue($hookCallback != null);
669         $this->assertEquals(1, count($hookCallback));
670         $this->assertEquals(2, count($hookCallback[0]));
671         $this->assertTrue(is_callable($hookCallback[0]));
672         $this->assertEquals('initializeTestHello', $hookCallback[0][1]);
673
674         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
675         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
676
677         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
678         $this->assertEquals('test:hello', $command->getName());
679         $commandGetNames = $this->callProtected($command, 'getNames');
680         $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
681
682         $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'init');
683         $this->assertTrue($hookCallback != null);
684         $this->assertEquals('initializeTestHello', $hookCallback[0][1]);
685
686         $input = new StringInput('test:hello');
687         $this->assertRunCommandViaApplicationEquals($command, $input, "Hello, Huey.");
688     }
689
690     function testCommandEventHook()
691     {
692         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
693         $this->commandFactory = new AnnotatedCommandFactory();
694
695         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'commandEventTestHello');
696
697         $this->assertTrue($hookInfo->hasAnnotation('hook'));
698         $this->assertEquals($hookInfo->getAnnotation('hook'), 'command-event test:hello');
699
700         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
701
702         $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::COMMAND_EVENT]);
703         $this->assertTrue($hookCallback != null);
704         $this->assertEquals(1, count($hookCallback));
705         $this->assertEquals(2, count($hookCallback[0]));
706         $this->assertTrue(is_callable($hookCallback[0]));
707         $this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
708
709         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
710         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
711
712         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
713         $this->assertEquals('test:hello', $command->getName());
714         $commandGetNames = $this->callProtected($command, 'getNames');
715         $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
716
717         $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'command-event');
718         $this->assertTrue($hookCallback != null);
719         $this->assertEquals('commandEventTestHello', $hookCallback[0][1]);
720
721         $input = new StringInput('test:hello Pluto');
722         $this->assertRunCommandViaApplicationEquals($command, $input, "Here comes Pluto!\nHello, Pluto.");
723     }
724
725
726     function testInteractAndValidate()
727     {
728         $this->commandFileInstance = new \Consolidation\TestUtils\ExampleCommandFile();
729         $this->commandFactory = new AnnotatedCommandFactory();
730
731         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'interactTestHello');
732
733         $this->assertTrue($hookInfo->hasAnnotation('hook'));
734         $this->assertEquals($hookInfo->getAnnotation('hook'), 'interact test:hello');
735
736         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
737
738         $hookInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'validateTestHello');
739
740         $this->assertTrue($hookInfo->hasAnnotation('hook'));
741         $this->assertEquals($hookInfo->getAnnotation('hook'), 'validate test:hello');
742
743         $this->commandFactory->registerCommandHook($hookInfo, $this->commandFileInstance);
744
745         $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::ARGUMENT_VALIDATOR]);
746         $this->assertTrue($hookCallback != null);
747         $this->assertEquals(1, count($hookCallback));
748         $this->assertEquals(2, count($hookCallback[0]));
749         $this->assertTrue(is_callable($hookCallback[0]));
750         $this->assertEquals('validateTestHello', $hookCallback[0][1]);
751
752         $hookCallback = $this->commandFactory->hookManager()->get('test:hello', [HookManager::INTERACT]);
753         $this->assertTrue($hookCallback != null);
754         $this->assertEquals(1, count($hookCallback));
755         $this->assertEquals(2, count($hookCallback[0]));
756         $this->assertTrue(is_callable($hookCallback[0]));
757         $this->assertEquals('interactTestHello', $hookCallback[0][1]);
758
759         $commandInfo = $this->commandFactory->createCommandInfo($this->commandFileInstance, 'testHello');
760         $command = $this->commandFactory->createCommand($commandInfo, $this->commandFileInstance);
761
762         $this->assertInstanceOf('\Symfony\Component\Console\Command\Command', $command);
763         $this->assertEquals('test:hello', $command->getName());
764         $commandGetNames = $this->callProtected($command, 'getNames');
765         $this->assertEquals('test:hello,Consolidation\TestUtils\ExampleCommandFile', implode(',', $commandGetNames));
766
767         $testInteractInput = new StringInput('test:hello');
768         $definition = new \Symfony\Component\Console\Input\InputDefinition(
769             [
770                 new \Symfony\Component\Console\Input\InputArgument('application', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
771                 new \Symfony\Component\Console\Input\InputArgument('who', \Symfony\Component\Console\Input\InputArgument::REQUIRED),
772             ]
773         );
774         $testInteractInput->bind($definition);
775         $testInteractOutput = new BufferedOutput();
776         $command->commandProcessor()->interact(
777             $testInteractInput,
778             $testInteractOutput,
779             $commandGetNames,
780             $command->getAnnotationData()
781         );
782         $this->assertEquals('Goofey', $testInteractInput->getArgument('who'));
783
784         $hookCallback = $command->commandProcessor()->hookManager()->get('test:hello', 'interact');
785         $this->assertTrue($hookCallback != null);
786         $this->assertEquals('interactTestHello', $hookCallback[0][1]);
787
788         $input = new StringInput('test:hello "Mickey Mouse"');
789         $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Mickey Mouse.');
790
791         $input = new StringInput('test:hello');
792         $this->assertRunCommandViaApplicationEquals($command, $input, 'Hello, Goofey.');
793
794         $input = new StringInput('test:hello "Donald Duck"');
795         $this->assertRunCommandViaApplicationEquals($command, $input, "I won't say hello to Donald Duck.", 1);
796
797         $input = new StringInput('test:hello "Drumph"');
798         $this->assertRunCommandViaApplicationEquals($command, $input, "Irrational value error.", 1);
799
800         // Try the last test again with a display error function installed.
801         $this->commandFactory->commandProcessor()->setDisplayErrorFunction(
802             function ($output, $message) {
803                 $output->writeln("*** $message ****");
804             }
805         );
806
807         $input = new StringInput('test:hello "Drumph"');
808         $this->assertRunCommandViaApplicationEquals($command, $input, "*** Irrational value error. ****", 1);
809     }
810
811     function callProtected($object, $method, $args = [])
812     {
813         $r = new \ReflectionMethod($object, $method);
814         $r->setAccessible(true);
815         return $r->invokeArgs($object, $args);
816     }
817
818     function assertRunCommandViaApplicationContains($command, $input, $containsList, $expectedStatusCode = 0)
819     {
820         list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
821
822         foreach ($containsList as $contains) {
823             $this->assertContains($contains, $commandOutput);
824         }
825         $this->assertEquals($expectedStatusCode, $statusCode);
826     }
827
828     function assertRunCommandViaApplicationEquals($command, $input, $expectedOutput, $expectedStatusCode = 0)
829     {
830         list($statusCode, $commandOutput) = $this->runCommandViaApplication($command, $input);
831
832         $this->assertEquals($expectedOutput, $commandOutput);
833         $this->assertEquals($expectedStatusCode, $statusCode);
834     }
835
836     function runCommandViaApplication($command, $input)
837     {
838         $output = new BufferedOutput();
839         if ($this->commandFileInstance && method_exists($this->commandFileInstance, 'setOutput')) {
840             $this->commandFileInstance->setOutput($output);
841         }
842
843         $application = new Application('TestApplication', '0.0.0');
844         $alterOptionsEventManager = new AlterOptionsCommandEvent($application);
845
846         $eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
847         $eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
848         $eventDispatcher->addSubscriber($alterOptionsEventManager);
849         $application->setDispatcher($eventDispatcher);
850
851         $application->setAutoExit(false);
852         $application->add($command);
853
854         $statusCode = $application->run($input, $output);
855         $commandOutput = trim($output->fetch());
856
857         return [$statusCode, $commandOutput];
858     }
859 }