CommandRunnerTest.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. <?php
  2. /**
  3. * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
  4. * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  5. *
  6. * Licensed under The MIT License
  7. * For full copyright and license information, please see the LICENSE.txt
  8. * Redistributions of files must retain the above copyright notice.
  9. *
  10. * @copyright Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
  11. * @link http://cakephp.org CakePHP(tm) Project
  12. * @since 3.5.0
  13. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  14. */
  15. namespace Cake\Test\Console;
  16. use Cake\Console\CommandCollection;
  17. use Cake\Console\CommandFactoryInterface;
  18. use Cake\Console\CommandRunner;
  19. use Cake\Console\ConsoleIo;
  20. use Cake\Console\Shell;
  21. use Cake\Core\Configure;
  22. use Cake\Core\ConsoleApplicationInterface;
  23. use Cake\Event\EventList;
  24. use Cake\Event\EventManager;
  25. use Cake\Http\BaseApplication;
  26. use Cake\TestSuite\Stub\ConsoleOutput;
  27. use Cake\TestSuite\TestCase;
  28. use InvalidArgumentException;
  29. use TestApp\Command\DemoCommand;
  30. use TestApp\Http\EventApplication;
  31. use TestApp\Shell\SampleShell;
  32. /**
  33. * Test case for the CommandCollection
  34. */
  35. class CommandRunnerTest extends TestCase
  36. {
  37. /**
  38. * Tracking property for event triggering
  39. *
  40. * @var bool
  41. */
  42. protected $eventTriggered = false;
  43. /**
  44. * setup
  45. *
  46. * @return void
  47. */
  48. public function setUp()
  49. {
  50. parent::setUp();
  51. Configure::write('App.namespace', 'TestApp');
  52. $this->config = dirname(dirname(__DIR__));
  53. }
  54. /**
  55. * test event manager proxies to the application.
  56. *
  57. * @return void
  58. */
  59. public function testEventManagerProxies()
  60. {
  61. $app = $this->getMockForAbstractClass(
  62. BaseApplication::class,
  63. [$this->config]
  64. );
  65. $runner = new CommandRunner($app);
  66. $this->assertSame($app->getEventManager(), $runner->getEventManager());
  67. }
  68. /**
  69. * test event manager cannot be set on applications without events.
  70. *
  71. * @return void
  72. */
  73. public function testGetEventManagerNonEventedApplication()
  74. {
  75. $app = $this->createMock(ConsoleApplicationInterface::class);
  76. $runner = new CommandRunner($app);
  77. $this->assertSame(EventManager::instance(), $runner->getEventManager());
  78. }
  79. /**
  80. * test event manager cannot be set on applications without events.
  81. *
  82. * @return void
  83. */
  84. public function testSetEventManagerNonEventedApplication()
  85. {
  86. $this->expectException(InvalidArgumentException::class);
  87. $app = $this->createMock(ConsoleApplicationInterface::class);
  88. $events = new EventManager();
  89. $runner = new CommandRunner($app);
  90. $runner->setEventManager($events);
  91. }
  92. /**
  93. * Test that the console hook not returning a command collection
  94. * raises an error.
  95. *
  96. * @return void
  97. */
  98. public function testRunConsoleHookFailure()
  99. {
  100. $this->expectException(\RuntimeException::class);
  101. $this->expectExceptionMessage('The application\'s `console` method did not return a CommandCollection.');
  102. $app = $this->getMockBuilder(BaseApplication::class)
  103. ->setMethods(['console', 'middleware', 'bootstrap'])
  104. ->setConstructorArgs([$this->config])
  105. ->getMock();
  106. $runner = new CommandRunner($app);
  107. $runner->run(['cake', '-h']);
  108. }
  109. /**
  110. * Test that the console hook not returning a command collection
  111. * raises an error.
  112. *
  113. * @return void
  114. */
  115. public function testRunPluginConsoleHookFailure()
  116. {
  117. $this->expectException(\RuntimeException::class);
  118. $this->expectExceptionMessage('The application\'s `pluginConsole` method did not return a CommandCollection.');
  119. $app = $this->getMockBuilder(BaseApplication::class)
  120. ->setMethods(['pluginConsole', 'middleware', 'bootstrap'])
  121. ->setConstructorArgs([$this->config])
  122. ->getMock();
  123. $runner = new CommandRunner($app);
  124. $runner->run(['cake', '-h']);
  125. }
  126. /**
  127. * Test that running with empty argv fails
  128. *
  129. * @return void
  130. */
  131. public function testRunMissingRootCommand()
  132. {
  133. $this->expectException(\RuntimeException::class);
  134. $this->expectExceptionMessage('Cannot run any commands. No arguments received.');
  135. $app = $this->getMockBuilder(BaseApplication::class)
  136. ->setMethods(['middleware', 'bootstrap'])
  137. ->setConstructorArgs([$this->config])
  138. ->getMock();
  139. $runner = new CommandRunner($app);
  140. $runner->run([]);
  141. }
  142. /**
  143. * Test that running an unknown command raises an error.
  144. *
  145. * @return void
  146. */
  147. public function testRunInvalidCommand()
  148. {
  149. $this->expectException(\RuntimeException::class);
  150. $this->expectExceptionMessage('Unknown command `cake nope`. Run `cake --help` to get the list of valid commands.');
  151. $app = $this->getMockBuilder(BaseApplication::class)
  152. ->setMethods(['middleware', 'bootstrap'])
  153. ->setConstructorArgs([$this->config])
  154. ->getMock();
  155. $runner = new CommandRunner($app);
  156. $runner->run(['cake', 'nope', 'nope', 'nope']);
  157. }
  158. /**
  159. * Test using `cake --help` invokes the help command
  160. *
  161. * @return void
  162. */
  163. public function testRunHelpLongOption()
  164. {
  165. $app = $this->getMockBuilder(BaseApplication::class)
  166. ->setMethods(['middleware', 'bootstrap'])
  167. ->setConstructorArgs([$this->config])
  168. ->getMock();
  169. $output = new ConsoleOutput();
  170. $runner = new CommandRunner($app, 'cake');
  171. $result = $runner->run(['cake', '--help'], $this->getMockIo($output));
  172. $this->assertSame(0, $result);
  173. $messages = implode("\n", $output->messages());
  174. $this->assertContains('Current Paths', $messages);
  175. $this->assertContains('- i18n', $messages);
  176. $this->assertContains('Available Commands', $messages);
  177. }
  178. /**
  179. * Test using `cake -h` invokes the help command
  180. *
  181. * @return void
  182. */
  183. public function testRunHelpShortOption()
  184. {
  185. $app = $this->getMockBuilder(BaseApplication::class)
  186. ->setMethods(['middleware', 'bootstrap'])
  187. ->setConstructorArgs([$this->config])
  188. ->getMock();
  189. $output = new ConsoleOutput();
  190. $runner = new CommandRunner($app, 'cake');
  191. $result = $runner->run(['cake', '-h'], $this->getMockIo($output));
  192. $this->assertSame(0, $result);
  193. $messages = implode("\n", $output->messages());
  194. $this->assertContains('- i18n', $messages);
  195. $this->assertContains('Available Commands', $messages);
  196. }
  197. /**
  198. * Test that no command outputs the command list
  199. *
  200. * @return void
  201. */
  202. public function testRunNoCommand()
  203. {
  204. $app = $this->getMockBuilder(BaseApplication::class)
  205. ->setMethods(['middleware', 'bootstrap'])
  206. ->setConstructorArgs([$this->config])
  207. ->getMock();
  208. $output = new ConsoleOutput();
  209. $runner = new CommandRunner($app);
  210. $result = $runner->run(['cake'], $this->getMockIo($output));
  211. $this->assertSame(0, $result, 'help output is success.');
  212. $messages = implode("\n", $output->messages());
  213. $this->assertContains('No command provided. Choose one of the available commands', $messages);
  214. $this->assertContains('- i18n', $messages);
  215. $this->assertContains('Available Commands', $messages);
  216. }
  217. /**
  218. * Test using `cake --verson` invokes the version command
  219. *
  220. * @return void
  221. */
  222. public function testRunVersionAlias()
  223. {
  224. $app = $this->getMockBuilder(BaseApplication::class)
  225. ->setMethods(['middleware', 'bootstrap'])
  226. ->setConstructorArgs([$this->config])
  227. ->getMock();
  228. $output = new ConsoleOutput();
  229. $runner = new CommandRunner($app, 'cake');
  230. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  231. $this->assertContains(Configure::version(), $output->messages()[0]);
  232. }
  233. /**
  234. * Test running a valid command
  235. *
  236. * @return void
  237. */
  238. public function testRunValidCommand()
  239. {
  240. $app = $this->getMockBuilder(BaseApplication::class)
  241. ->setMethods(['middleware', 'bootstrap'])
  242. ->setConstructorArgs([$this->config])
  243. ->getMock();
  244. $output = new ConsoleOutput();
  245. $runner = new CommandRunner($app, 'cake');
  246. $result = $runner->run(['cake', 'routes'], $this->getMockIo($output));
  247. $this->assertSame(Shell::CODE_SUCCESS, $result);
  248. $contents = implode("\n", $output->messages());
  249. $this->assertContains('URI template', $contents);
  250. }
  251. /**
  252. * Test running a valid command and that backwards compatible
  253. * inflection is hooked up.
  254. *
  255. * @return void
  256. */
  257. public function testRunValidCommandInflection()
  258. {
  259. $app = $this->getMockBuilder(BaseApplication::class)
  260. ->setMethods(['middleware', 'bootstrap'])
  261. ->setConstructorArgs([$this->config])
  262. ->getMock();
  263. $output = new ConsoleOutput();
  264. $runner = new CommandRunner($app, 'cake');
  265. $result = $runner->run(['cake', 'OrmCache', 'build'], $this->getMockIo($output));
  266. $this->assertSame(Shell::CODE_SUCCESS, $result);
  267. $contents = implode("\n", $output->messages());
  268. $this->assertContains('Cache', $contents);
  269. }
  270. /**
  271. * Test running a valid raising an error
  272. *
  273. * @return void
  274. */
  275. public function testRunValidCommandWithAbort()
  276. {
  277. $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
  278. $output = new ConsoleOutput();
  279. $runner = new CommandRunner($app, 'cake');
  280. $result = $runner->run(['cake', 'failure', 'with_abort'], $this->getMockIo($output));
  281. $this->assertSame(Shell::CODE_ERROR, $result);
  282. }
  283. /**
  284. * Test returning a non-zero value
  285. *
  286. * @return void
  287. */
  288. public function testRunValidCommandReturnInteger()
  289. {
  290. $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
  291. $output = new ConsoleOutput();
  292. $runner = new CommandRunner($app, 'cake');
  293. $result = $runner->run(['cake', 'failure', 'returnValue'], $this->getMockIo($output));
  294. $this->assertSame(99, $result);
  295. }
  296. /**
  297. * Ensure that the root command name propagates to shell help
  298. *
  299. * @return void
  300. */
  301. public function testRunRootNamePropagates()
  302. {
  303. $app = $this->makeAppWithCommands(['sample' => SampleShell::class]);
  304. $output = new ConsoleOutput();
  305. $runner = new CommandRunner($app, 'widget');
  306. $runner->run(['widget', 'sample', '-h'], $this->getMockIo($output));
  307. $result = implode("\n", $output->messages());
  308. $this->assertContains('widget sample [-h]', $result);
  309. $this->assertNotContains('cake sample [-h]', $result);
  310. }
  311. /**
  312. * Test running a valid command
  313. *
  314. * @return void
  315. */
  316. public function testRunValidCommandClass()
  317. {
  318. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  319. $output = new ConsoleOutput();
  320. $runner = new CommandRunner($app, 'cake');
  321. $result = $runner->run(['cake', 'ex'], $this->getMockIo($output));
  322. $this->assertSame(Shell::CODE_SUCCESS, $result);
  323. $messages = implode("\n", $output->messages());
  324. $this->assertContains('Demo Command!', $messages);
  325. }
  326. /**
  327. * Test using a custom factory
  328. *
  329. * @return void
  330. */
  331. public function testRunWithCustomFactory()
  332. {
  333. $output = new ConsoleOutput();
  334. $io = $this->getMockIo($output);
  335. $factory = $this->createMock(CommandFactoryInterface::class);
  336. $factory->expects($this->once())
  337. ->method('create')
  338. ->with(DemoCommand::class)
  339. ->willReturn(new DemoCommand());
  340. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  341. $runner = new CommandRunner($app, 'cake', $factory);
  342. $result = $runner->run(['cake', 'ex'], $io);
  343. $this->assertSame(Shell::CODE_SUCCESS, $result);
  344. $messages = implode("\n", $output->messages());
  345. $this->assertContains('Demo Command!', $messages);
  346. }
  347. /**
  348. * Test running a command class' help
  349. *
  350. * @return void
  351. */
  352. public function testRunValidCommandClassHelp()
  353. {
  354. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  355. $output = new ConsoleOutput();
  356. $runner = new CommandRunner($app, 'cake');
  357. $result = $runner->run(['cake', 'ex', '-h'], $this->getMockIo($output));
  358. $this->assertSame(Shell::CODE_SUCCESS, $result);
  359. $messages = implode("\n", $output->messages());
  360. $this->assertContains("\ncake ex [-h]", $messages);
  361. $this->assertNotContains('Demo Command!', $messages);
  362. }
  363. /**
  364. * Test that run() fires off the buildCommands event.
  365. *
  366. * @return void
  367. */
  368. public function testRunTriggersBuildCommandsEvent()
  369. {
  370. $app = $this->getMockBuilder(BaseApplication::class)
  371. ->setMethods(['middleware', 'bootstrap'])
  372. ->setConstructorArgs([$this->config])
  373. ->getMock();
  374. $output = new ConsoleOutput();
  375. $runner = new CommandRunner($app, 'cake');
  376. $runner->getEventManager()->on('Console.buildCommands', function ($event, $commands) {
  377. $this->assertInstanceOf(CommandCollection::class, $commands);
  378. $this->eventTriggered = true;
  379. });
  380. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  381. $this->assertTrue($this->eventTriggered, 'Should have triggered event.');
  382. }
  383. /**
  384. * Test that run calls plugin hook methods
  385. *
  386. * @return void
  387. */
  388. public function testRunCallsPluginHookMethods()
  389. {
  390. $app = $this->getMockBuilder(BaseApplication::class)
  391. ->setMethods(['middleware', 'bootstrap', 'pluginBootstrap', 'pluginEvents', 'pluginConsole'])
  392. ->setConstructorArgs([$this->config])
  393. ->getMock();
  394. $app->expects($this->at(0))->method('bootstrap');
  395. $app->expects($this->at(1))->method('pluginBootstrap');
  396. $commands = new CommandCollection();
  397. $app->expects($this->at(2))
  398. ->method('pluginConsole')
  399. ->with($this->isinstanceOf(CommandCollection::class))
  400. ->will($this->returnCallback(function ($commands) {
  401. return $commands;
  402. }));
  403. $output = new ConsoleOutput();
  404. $runner = new CommandRunner($app, 'cake');
  405. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  406. $this->assertContains(Configure::version(), $output->messages()[0]);
  407. }
  408. protected function makeAppWithCommands($commands)
  409. {
  410. $app = $this->getMockBuilder(BaseApplication::class)
  411. ->setMethods(['middleware', 'bootstrap', 'console'])
  412. ->setConstructorArgs([$this->config])
  413. ->getMock();
  414. $collection = new CommandCollection($commands);
  415. $app->method('console')->will($this->returnValue($collection));
  416. return $app;
  417. }
  418. protected function getMockIo($output)
  419. {
  420. $io = $this->getMockBuilder(ConsoleIo::class)
  421. ->setConstructorArgs([$output, $output, null, null])
  422. ->setMethods(['in'])
  423. ->getMock();
  424. return $io;
  425. }
  426. }