Drupal investigation

testFullStack.php 22KB

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