Security update for Core, with self-updated composer
[yaffs-website] / vendor / consolidation / annotated-command / tests / testFullStack.php
1 <?php
2 namespace Consolidation\AnnotatedCommand;
3
4 use Consolidation\AnnotatedCommand\AnnotationData;
5 use Consolidation\AnnotatedCommand\CommandData;
6 use Consolidation\AnnotatedCommand\CommandProcessor;
7 use Consolidation\AnnotatedCommand\Hooks\AlterResultInterface;
8 use Consolidation\AnnotatedCommand\Hooks\ExtractOutputInterface;
9 use Consolidation\AnnotatedCommand\Hooks\HookManager;
10 use Consolidation\AnnotatedCommand\Hooks\ProcessResultInterface;
11 use Consolidation\AnnotatedCommand\Hooks\StatusDeterminerInterface;
12 use Consolidation\AnnotatedCommand\Hooks\ValidatorInterface;
13 use Consolidation\AnnotatedCommand\Options\AlterOptionsCommandEvent;
14 use Consolidation\AnnotatedCommand\Parser\CommandInfo;
15 use Consolidation\OutputFormatters\FormatterManager;
16 use Consolidation\TestUtils\TestTerminal;
17 use Symfony\Component\Console\Application;
18 use Symfony\Component\Console\Command\Command;
19 use Symfony\Component\Console\Input\InputInterface;
20 use Symfony\Component\Console\Input\StringInput;
21 use Symfony\Component\Console\Output\BufferedOutput;
22 use Symfony\Component\Console\Output\OutputInterface;
23 use Consolidation\TestUtils\ApplicationWithTerminalWidth;
24 use Consolidation\AnnotatedCommand\Options\PrepareTerminalWidthOption;
25 use Consolidation\AnnotatedCommand\Events\CustomEventAwareInterface;
26 use Consolidation\AnnotatedCommand\Events\CustomEventAwareTrait;
27
28 /**
29  * Do a test of all of the classes in this project, top-to-bottom.
30  */
31 class FullStackTests extends \PHPUnit_Framework_TestCase
32 {
33     protected $application;
34     protected $commandFactory;
35
36     function setup() {
37         $this->application = new ApplicationWithTerminalWidth('TestApplication', '0.0.0');
38         $this->commandFactory = new AnnotatedCommandFactory();
39         $alterOptionsEventManager = new AlterOptionsCommandEvent($this->application);
40         $eventDispatcher = new \Symfony\Component\EventDispatcher\EventDispatcher();
41         $eventDispatcher->addSubscriber($this->commandFactory->commandProcessor()->hookManager());
42         $this->commandFactory->commandProcessor()->hookManager()->addCommandEvent($alterOptionsEventManager);
43         $this->application->setDispatcher($eventDispatcher);
44         $this->application->setAutoExit(false);
45     }
46
47     function testValidFormats()
48     {
49         $formatter = new FormatterManager();
50         $formatter->addDefaultFormatters();
51         $formatter->addDefaultSimplifiers();
52         $commandInfo = CommandInfo::create('\Consolidation\TestUtils\alpha\AlphaCommandFile', 'exampleTable');
53         $this->assertEquals('example:table', $commandInfo->getName());
54         $this->assertEquals('\Consolidation\OutputFormatters\StructuredData\RowsOfFields', $commandInfo->getReturnType());
55     }
56
57     function testAutomaticOptions()
58     {
59         $commandFileInstance = new \Consolidation\TestUtils\alpha\AlphaCommandFile;
60         $formatter = new FormatterManager();
61         $formatter->addDefaultFormatters();
62         $formatter->addDefaultSimplifiers();
63
64         $this->commandFactory->commandProcessor()->setFormatterManager($formatter);
65         $this->assertAutomaticOptionsForCommand($commandFileInstance, 'exampleTable', 'example:table');
66         $this->assertAutomaticOptionsForCommand($commandFileInstance, 'exampleTableTwo', 'example:table2');
67     }
68
69     function assertAutomaticOptionsForCommand($commandFileInstance, $functionName, $commandName)
70     {
71         $commandInfo = $this->commandFactory->createCommandInfo($commandFileInstance, $functionName);
72
73         $command = $this->commandFactory->createCommand($commandInfo, $commandFileInstance);
74         $this->application->add($command);
75
76         $containsList =
77         [
78             '--format[=FORMAT]  Format the result data. Available formats: csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml [default: "table"]',
79             '--fields[=FIELDS]  Available fields: I (first), II (second), III (third) [default: ""]',
80         ];
81         $this->assertRunCommandViaApplicationContains('help ' . $commandName, $containsList);
82     }
83
84     function testCommandsAndHooks()
85     {
86         // First, search for commandfiles in the 'alpha'
87         // directory. Note that this same functionality
88         // is tested more thoroughly in isolation in
89         // testCommandFileDiscovery.php
90         $discovery = new CommandFileDiscovery();
91         $discovery
92           ->setSearchPattern('*CommandFile.php')
93           ->setIncludeFilesAtBase(false)
94           ->setSearchLocations(['alpha']);
95
96         chdir(__DIR__);
97         $commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
98
99         $formatter = new FormatterManager();
100         $formatter->addDefaultFormatters();
101         $formatter->addDefaultSimplifiers();
102         $hookManager = new HookManager();
103         $terminalWidthOption = new PrepareTerminalWidthOption();
104         $terminalWidthOption->enableWrap(true);
105         $terminalWidthOption->setApplication($this->application);
106         $testTerminal = new TestTerminal(0);
107         $terminalWidthOption->setTerminal($testTerminal);
108         $commandProcessor = new CommandProcessor($hookManager);
109         $commandProcessor->setFormatterManager($formatter);
110         $commandProcessor->addPrepareFormatter($terminalWidthOption);
111
112         // Create a new factory, and load all of the files
113         // discovered above.
114         $factory = new AnnotatedCommandFactory();
115         $factory->setCommandProcessor($commandProcessor);
116         // Add a listener to configure our command handler object
117         $factory->addListernerCallback(function($command) use($hookManager) {
118             if ($command instanceof CustomEventAwareInterface) {
119                 $command->setHookManager($hookManager);
120             }
121         } );
122         $factory->setIncludeAllPublicMethods(false);
123         $this->addDiscoveredCommands($factory, $commandFiles);
124
125         $this->assertRunCommandViaApplicationContains('list', ['example:table'], ['additional:option', 'without:annotations']);
126
127         $this->assertTrue($this->application->has('example:table'));
128         $this->assertFalse($this->application->has('without:annotations'));
129
130         // Run the use:event command that defines a custom event, my-event.
131         $this->assertRunCommandViaApplicationEquals('use:event', 'one,two');
132         // Watch as we dynamically add a custom event to the hook manager to change the command results:
133         $hookManager->add(function () { return 'three'; }, HookManager::ON_EVENT, 'my-event');
134         $this->assertRunCommandViaApplicationEquals('use:event', 'one,three,two');
135
136         // Fetch a reference to the 'example:table' command and test its valid format types
137         $exampleTableCommand = $this->application->find('example:table');
138         $returnType = $exampleTableCommand->getReturnType();
139         $this->assertEquals('\Consolidation\OutputFormatters\StructuredData\RowsOfFields', $returnType);
140         $validFormats = $formatter->validFormats($returnType);
141         $this->assertEquals('csv,json,list,php,print-r,sections,string,table,tsv,var_export,xml,yaml', implode(',', $validFormats));
142
143         // Control: run commands without hooks.
144         $this->assertRunCommandViaApplicationEquals('always:fail', 'This command always fails.', 13);
145         $this->assertRunCommandViaApplicationEquals('simulated:status', '42');
146         $this->assertRunCommandViaApplicationEquals('example:output', 'Hello, World.');
147         $this->assertRunCommandViaApplicationEquals('example:cat bet alpha --flip', 'alphabet');
148         $this->assertRunCommandViaApplicationEquals('example:echo a b c', "a\tb\tc");
149         $this->assertRunCommandViaApplicationEquals('example:message', 'Shipwrecked; send bananas.');
150         $this->assertRunCommandViaApplicationEquals('command:with-one-optional-argument', 'Hello, world');
151         $this->assertRunCommandViaApplicationEquals('command:with-one-optional-argument Joe', 'Hello, Joe');
152
153         // Add some hooks.
154         $factory->hookManager()->addValidator(new ExampleValidator());
155         $factory->hookManager()->addResultProcessor(new ExampleResultProcessor());
156         $factory->hookManager()->addAlterResult(new ExampleResultAlterer());
157         $factory->hookManager()->addStatusDeterminer(new ExampleStatusDeterminer());
158         $factory->hookManager()->addOutputExtractor(new ExampleOutputExtractor());
159
160         // Run the same commands as before, and confirm that results
161         // are different now that the hooks are in place.
162         $this->assertRunCommandViaApplicationEquals('simulated:status', '', 42);
163         $this->assertRunCommandViaApplicationEquals('example:output', 'Hello, World!');
164         $this->assertRunCommandViaApplicationEquals('example:cat bet alpha --flip', 'alphareplaced');
165         $this->assertRunCommandViaApplicationEquals('example:echo a b c', 'a,b,c');
166         $this->assertRunCommandViaApplicationEquals('example:message', 'Shipwrecked; send bananas.');
167
168         $expected = <<<EOT
169  ------ ------ -------
170   I      II     III
171  ------ ------ -------
172   One    Two    Three
173   Eins   Zwei   Drei
174   Ichi   Ni     San
175   Uno    Dos    Tres
176  ------ ------ -------
177 EOT;
178         $this->assertRunCommandViaApplicationEquals('example:table', $expected);
179
180         $expected = <<<EOT
181  ------- ------
182   III     II
183  ------- ------
184   Three   Two
185   Drei    Zwei
186   San     Ni
187   Tres    Dos
188  ------- ------
189 EOT;
190         $this->assertRunCommandViaApplicationEquals('example:table --fields=III,II', $expected);
191
192         $expectedSingleField = <<<EOT
193 Two
194 Zwei
195 Ni
196 Dos
197 EOT;
198
199         // When --field is specified (instead of --fields), then the format
200         // is forced to 'string'.
201         $this->assertRunCommandViaApplicationEquals('example:table --field=II', $expectedSingleField);
202
203         // Check the help for the example table command and see if the options
204         // from the alter hook were added.  We expect that we should not see
205         // any of the information from the alter hook in the 'beta' folder yet.
206         $this->assertRunCommandViaApplicationContains('help example:table',
207             [
208                 'Option added by @hook option example:table',
209                 'example:table --french',
210                 'Add a row with French numbers.'
211             ],
212             [
213                 'chinese',
214                 'kanji',
215             ]
216         );
217
218         $expectedOutputWithFrench = <<<EOT
219  ------ ------ -------
220   I      II     III
221  ------ ------ -------
222   One    Two    Three
223   Eins   Zwei   Drei
224   Ichi   Ni     San
225   Uno    Dos    Tres
226   Un     Deux   Trois
227  ------ ------ -------
228 EOT;
229         $this->assertRunCommandViaApplicationEquals('example:table --french', $expectedOutputWithFrench);
230
231         $expectedAssociativeListTable = <<<EOT
232  --------------- ----------------------------------------------------------------------------------------
233   SFTP Command    sftp -o Port=2222 dev@appserver.dev.drush.in
234   Git Command     git clone ssh://codeserver.dev@codeserver.dev.drush.in:2222/~/repository.git wp-update
235   MySQL Command   mysql -u pantheon -p4b33cb -h dbserver.dev.drush.in -P 16191 pantheon
236  --------------- ----------------------------------------------------------------------------------------
237 EOT;
238         $this->assertRunCommandViaApplicationEquals('example:list', $expectedAssociativeListTable);
239         $this->assertRunCommandViaApplicationEquals('example:list --field=sftp_command', 'sftp -o Port=2222 dev@appserver.dev.drush.in');
240
241         $this->assertRunCommandViaApplicationEquals('get:serious', 'very serious');
242         $this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1);
243
244         $this->assertRunCommandViaApplicationContains('help example:wrap',
245             [
246                 'Test word wrapping',
247                 '[default: "table"]',
248             ]
249         );
250
251         $expectedUnwrappedOutput = <<<EOT
252 -------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
253   First                                                                                                                      Second
254  -------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
255   This is a really long cell that contains a lot of data. When it is rendered, it should be wrapped across multiple lines.   This is the second column of the same table. It is also very long, and should be wrapped across multiple lines, just like the first column.
256  -------------------------------------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------------------------------------------------
257 EOT;
258         $this->application->setWidthAndHeight(0, 0);
259         $this->assertRunCommandViaApplicationEquals('example:wrap', $expectedUnwrappedOutput);
260
261         $expectedWrappedOutput = <<<EOT
262  ------------------ --------------------
263   First              Second
264  ------------------ --------------------
265   This is a really   This is the second
266   long cell that     column of the same
267   contains a lot     table. It is also
268   of data. When it   very long, and
269   is rendered, it    should be wrapped
270   should be          across multiple
271   wrapped across     lines, just like
272   multiple lines.    the first column.
273  ------------------ --------------------
274 EOT;
275         $this->application->setWidthAndHeight(42, 24);
276         $testTerminal->setWidth(42);
277         $this->assertRunCommandViaApplicationEquals('example:wrap', $expectedWrappedOutput);
278     }
279
280     function testCommandsAndHooksIncludeAllPublicMethods()
281     {
282         // First, search for commandfiles in the 'alpha'
283         // directory. Note that this same functionality
284         // is tested more thoroughly in isolation in
285         // testCommandFileDiscovery.php
286         $discovery = new CommandFileDiscovery();
287         $discovery
288           ->setSearchPattern('*CommandFile.php')
289           ->setIncludeFilesAtBase(false)
290           ->setSearchLocations(['alpha']);
291
292         chdir(__DIR__);
293         $commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
294
295         $formatter = new FormatterManager();
296         $formatter->addDefaultFormatters();
297         $formatter->addDefaultSimplifiers();
298         $hookManager = new HookManager();
299         $commandProcessor = new CommandProcessor($hookManager);
300         $commandProcessor->setFormatterManager($formatter);
301
302         // Create a new factory, and load all of the files
303         // discovered above.  The command factory class is
304         // tested in isolation in testAnnotatedCommandFactory.php,
305         // but this is the only place where
306         $factory = new AnnotatedCommandFactory();
307         $factory->setCommandProcessor($commandProcessor);
308         // $factory->addListener(...);
309
310         // Now we will once again add all commands, this time including all
311         // public methods.  The command 'withoutAnnotations' should now be found.
312         $factory->setIncludeAllPublicMethods(true);
313         $this->addDiscoveredCommands($factory, $commandFiles);
314         $this->assertTrue($this->application->has('without:annotations'));
315
316         $this->assertRunCommandViaApplicationContains('list', ['example:table', 'without:annotations'], ['alter:formatters']);
317
318         $this->assertRunCommandViaApplicationEquals('get:serious', 'very serious');
319         $this->assertRunCommandViaApplicationContains('get:lost', 'Command "get:lost" is not defined.', [], 1);
320     }
321
322     function testCommandsAndHooksWithBetaFolder()
323     {
324         // First, search for commandfiles in the 'alpha'
325         // directory. Note that this same functionality
326         // is tested more thoroughly in isolation in
327         // testCommandFileDiscovery.php
328         $discovery = new CommandFileDiscovery();
329         $discovery
330           ->setSearchPattern('*CommandFile.php')
331           ->setIncludeFilesAtBase(false)
332           ->setSearchLocations(['alpha', 'beta']);
333
334         chdir(__DIR__);
335         $commandFiles = $discovery->discover('.', '\Consolidation\TestUtils');
336
337         $formatter = new FormatterManager();
338         $formatter->addDefaultFormatters();
339         $formatter->addDefaultSimplifiers();
340         $hookManager = new HookManager();
341         $commandProcessor = new CommandProcessor($hookManager);
342         $commandProcessor->setFormatterManager($formatter);
343
344         // Create a new factory, and load all of the files
345         // discovered above.  The command factory class is
346         // tested in isolation in testAnnotatedCommandFactory.php,
347         // but this is the only place where
348         $factory = new AnnotatedCommandFactory();
349         $factory->setCommandProcessor($commandProcessor);
350         // $factory->addListener(...);
351         $factory->setIncludeAllPublicMethods(true);
352         $this->addDiscoveredCommands($factory, $commandFiles);
353
354         // A few asserts, to make sure that our hooks all get registered.
355         $allRegisteredHooks = $hookManager->getAllHooks();
356         $registeredHookNames = array_keys($allRegisteredHooks);
357         sort($registeredHookNames);
358         $this->assertEquals('*,example:table,my-event', implode(',', $registeredHookNames));
359         $allHooksForExampleTable = $allRegisteredHooks['example:table'];
360         $allHookPhasesForExampleTable = array_keys($allHooksForExampleTable);
361         sort($allHookPhasesForExampleTable);
362         $this->assertEquals('alter,option', implode(',', $allHookPhasesForExampleTable));
363
364         $this->assertContains('alterFormattersChinese', var_export($allHooksForExampleTable, true));
365
366         $alterHooksForExampleTable = $this->callProtected($hookManager, 'getHooks', [['example:table'], 'alter']);
367         $this->assertContains('alterFormattersKanji', var_export($alterHooksForExampleTable, true));
368
369         $allHooksForAnyCommand = $allRegisteredHooks['*'];
370         $allHookPhasesForAnyCommand = array_keys($allHooksForAnyCommand);
371         sort($allHookPhasesForAnyCommand);
372         $this->assertEquals('alter', implode(',', $allHookPhasesForAnyCommand));
373
374         $this->assertContains('alterFormattersKanji', var_export($allHooksForAnyCommand, true));
375
376         // Help should have the information from the hooks in the 'beta' folder
377         $this->assertRunCommandViaApplicationContains('help example:table',
378             [
379                 'Option added by @hook option example:table',
380                 'example:table --french',
381                 'Add a row with French numbers.',
382                 'chinese',
383                 'kanji',
384             ]
385         );
386
387         // Confirm that the "unavailable" command is now available
388         $this->assertTrue($this->application->has('unavailable:command'));
389
390         $expectedOutputWithChinese = <<<EOT
391  ------ ------ -------
392   I      II     III
393  ------ ------ -------
394   One    Two    Three
395   Eins   Zwei   Drei
396   Ichi   Ni     San
397   Uno    Dos    Tres
398   壹     貳     叁
399  ------ ------ -------
400 EOT;
401         $this->assertRunCommandViaApplicationEquals('example:table --chinese', $expectedOutputWithChinese);
402
403         $expectedOutputWithKanji = <<<EOT
404  ------ ------ -------
405   I      II     III
406  ------ ------ -------
407   One    Two    Three
408   Eins   Zwei   Drei
409   Ichi   Ni     San
410   Uno    Dos    Tres
411   一     二     三
412  ------ ------ -------
413 EOT;
414         $this->assertRunCommandViaApplicationEquals('example:table --kanji', $expectedOutputWithKanji);
415     }
416
417     public function addDiscoveredCommands($factory, $commandFiles) {
418         foreach ($commandFiles as $path => $commandClass) {
419             $this->assertFileExists($path);
420             if (!class_exists($commandClass)) {
421                 include $path;
422             }
423             $commandInstance = new $commandClass();
424             $commandList = $factory->createCommandsFromClass($commandInstance);
425             foreach ($commandList as $command) {
426                 $this->application->add($command);
427             }
428         }
429     }
430
431     function assertRunCommandViaApplicationEquals($cmd, $expectedOutput, $expectedStatusCode = 0)
432     {
433         $input = new StringInput($cmd);
434         $output = new BufferedOutput();
435
436         $statusCode = $this->application->run($input, $output);
437         $commandOutput = trim($output->fetch());
438
439         $expectedOutput = $this->simplifyWhitespace($expectedOutput);
440         $commandOutput = $this->simplifyWhitespace($commandOutput);
441
442         $this->assertEquals($expectedOutput, $commandOutput);
443         $this->assertEquals($expectedStatusCode, $statusCode);
444     }
445
446     function assertRunCommandViaApplicationContains($cmd, $containsList, $doesNotContainList = [], $expectedStatusCode = 0)
447     {
448         $input = new StringInput($cmd);
449         $output = new BufferedOutput();
450         $containsList = (array) $containsList;
451
452         $statusCode = $this->application->run($input, $output);
453         $commandOutput = trim($output->fetch());
454
455         $commandOutput = $this->simplifyWhitespace($commandOutput);
456
457         foreach ($containsList as $expectedToContain) {
458             $this->assertContains($this->simplifyWhitespace($expectedToContain), $commandOutput);
459         }
460         foreach ($doesNotContainList as $expectedToNotContain) {
461             $this->assertNotContains($this->simplifyWhitespace($expectedToNotContain), $commandOutput);
462         }
463         $this->assertEquals($expectedStatusCode, $statusCode);
464     }
465
466     function simplifyWhitespace($data)
467     {
468         return trim(preg_replace('#\s+$#m', '', $data));
469     }
470
471     function callProtected($object, $method, $args = [])
472     {
473         $r = new \ReflectionMethod($object, $method);
474         $r->setAccessible(true);
475         return $r->invokeArgs($object, $args);
476     }
477
478 }
479
480 class ExampleValidator implements ValidatorInterface
481 {
482     public function validate(CommandData $commandData)
483     {
484         $args = $commandData->arguments();
485         if (isset($args['one']) && ($args['one'] == 'bet')) {
486             $commandData->input()->setArgument('one', 'replaced');
487             return $args;
488         }
489     }
490 }
491
492 class ExampleResultProcessor implements ProcessResultInterface
493 {
494     public function process($result, CommandData $commandData)
495     {
496         if (is_array($result) && array_key_exists('item-list', $result)) {
497             return implode(',', $result['item-list']);
498         }
499     }
500 }
501
502 class ExampleResultAlterer implements AlterResultInterface
503 {
504     public function process($result, CommandData $commandData)
505     {
506         if (is_string($result) && ($result == 'Hello, World.')) {
507             return 'Hello, World!';
508         }
509     }
510 }
511
512 class ExampleStatusDeterminer implements StatusDeterminerInterface
513 {
514     public function determineStatusCode($result)
515     {
516         if (is_array($result) && array_key_exists('status-code', $result)) {
517             return $result['status-code'];
518         }
519     }
520 }
521
522 class ExampleOutputExtractor implements ExtractOutputInterface
523 {
524     public function extractOutput($result)
525     {
526         if (is_array($result) && array_key_exists('message', $result)) {
527             return $result['message'];
528         }
529     }
530 }