CommandRunnerTest.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564
  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\EventManager;
  24. use Cake\Http\BaseApplication;
  25. use Cake\Routing\Router;
  26. use Cake\TestSuite\Stub\ConsoleOutput;
  27. use Cake\TestSuite\TestCase;
  28. use InvalidArgumentException;
  29. use TestApp\Command\AbortCommand;
  30. use TestApp\Command\DemoCommand;
  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 deprecated method defined in interface
  94. *
  95. * @return void
  96. */
  97. public function testEventManagerCompat()
  98. {
  99. $this->deprecated(function () {
  100. $app = $this->createMock(ConsoleApplicationInterface::class);
  101. $runner = new CommandRunner($app);
  102. $this->assertSame(EventManager::instance(), $runner->eventManager());
  103. });
  104. }
  105. /**
  106. * Test that the console hook not returning a command collection
  107. * raises an error.
  108. *
  109. * @return void
  110. */
  111. public function testRunConsoleHookFailure()
  112. {
  113. $this->expectException(\RuntimeException::class);
  114. $this->expectExceptionMessage('The application\'s `console` method did not return a CommandCollection.');
  115. $app = $this->getMockBuilder(BaseApplication::class)
  116. ->setMethods(['console', 'middleware', 'bootstrap'])
  117. ->setConstructorArgs([$this->config])
  118. ->getMock();
  119. $runner = new CommandRunner($app);
  120. $runner->run(['cake', '-h']);
  121. }
  122. /**
  123. * Test that the console hook not returning a command collection
  124. * raises an error.
  125. *
  126. * @return void
  127. */
  128. public function testRunPluginConsoleHookFailure()
  129. {
  130. $this->expectException(\RuntimeException::class);
  131. $this->expectExceptionMessage('The application\'s `pluginConsole` method did not return a CommandCollection.');
  132. $app = $this->getMockBuilder(BaseApplication::class)
  133. ->setMethods(['pluginConsole', 'middleware', 'bootstrap'])
  134. ->setConstructorArgs([$this->config])
  135. ->getMock();
  136. $runner = new CommandRunner($app);
  137. $runner->run(['cake', '-h']);
  138. }
  139. /**
  140. * Test that running with empty argv fails
  141. *
  142. * @return void
  143. */
  144. public function testRunMissingRootCommand()
  145. {
  146. $this->expectException(\RuntimeException::class);
  147. $this->expectExceptionMessage('Cannot run any commands. No arguments received.');
  148. $app = $this->getMockBuilder(BaseApplication::class)
  149. ->setMethods(['middleware', 'bootstrap', 'routes'])
  150. ->setConstructorArgs([$this->config])
  151. ->getMock();
  152. $runner = new CommandRunner($app);
  153. $runner->run([]);
  154. }
  155. /**
  156. * Test that running an unknown command raises an error.
  157. *
  158. * @return void
  159. */
  160. public function testRunInvalidCommand()
  161. {
  162. $this->expectException(\RuntimeException::class);
  163. $this->expectExceptionMessage('Unknown command `cake nope`. Run `cake --help` to get the list of valid commands.');
  164. $app = $this->getMockBuilder(BaseApplication::class)
  165. ->setMethods(['middleware', 'bootstrap', 'routes'])
  166. ->setConstructorArgs([$this->config])
  167. ->getMock();
  168. $runner = new CommandRunner($app);
  169. $runner->run(['cake', 'nope', 'nope', 'nope']);
  170. }
  171. /**
  172. * Test using `cake --help` invokes the help command
  173. *
  174. * @return void
  175. */
  176. public function testRunHelpLongOption()
  177. {
  178. $app = $this->getMockBuilder(BaseApplication::class)
  179. ->setMethods(['middleware', 'bootstrap', 'routes'])
  180. ->setConstructorArgs([$this->config])
  181. ->getMock();
  182. $output = new ConsoleOutput();
  183. $runner = new CommandRunner($app, 'cake');
  184. $result = $runner->run(['cake', '--help'], $this->getMockIo($output));
  185. $this->assertSame(0, $result);
  186. $messages = implode("\n", $output->messages());
  187. $this->assertContains('Current Paths', $messages);
  188. $this->assertContains('- i18n', $messages);
  189. $this->assertContains('Available Commands', $messages);
  190. }
  191. /**
  192. * Test using `cake -h` invokes the help command
  193. *
  194. * @return void
  195. */
  196. public function testRunHelpShortOption()
  197. {
  198. $app = $this->getMockBuilder(BaseApplication::class)
  199. ->setMethods(['middleware', 'bootstrap', 'routes'])
  200. ->setConstructorArgs([$this->config])
  201. ->getMock();
  202. $output = new ConsoleOutput();
  203. $runner = new CommandRunner($app, 'cake');
  204. $result = $runner->run(['cake', '-h'], $this->getMockIo($output));
  205. $this->assertSame(0, $result);
  206. $messages = implode("\n", $output->messages());
  207. $this->assertContains('- i18n', $messages);
  208. $this->assertContains('Available Commands', $messages);
  209. }
  210. /**
  211. * Test that no command outputs the command list
  212. *
  213. * @return void
  214. */
  215. public function testRunNoCommand()
  216. {
  217. $app = $this->getMockBuilder(BaseApplication::class)
  218. ->setMethods(['middleware', 'bootstrap', 'routes'])
  219. ->setConstructorArgs([$this->config])
  220. ->getMock();
  221. $output = new ConsoleOutput();
  222. $runner = new CommandRunner($app);
  223. $result = $runner->run(['cake'], $this->getMockIo($output));
  224. $this->assertSame(0, $result, 'help output is success.');
  225. $messages = implode("\n", $output->messages());
  226. $this->assertContains('No command provided. Choose one of the available commands', $messages);
  227. $this->assertContains('- i18n', $messages);
  228. $this->assertContains('Available Commands', $messages);
  229. }
  230. /**
  231. * Test using `cake --version` invokes the version command
  232. *
  233. * @return void
  234. */
  235. public function testRunVersionAlias()
  236. {
  237. $app = $this->getMockBuilder(BaseApplication::class)
  238. ->setMethods(['middleware', 'bootstrap', 'routes'])
  239. ->setConstructorArgs([$this->config])
  240. ->getMock();
  241. $output = new ConsoleOutput();
  242. $runner = new CommandRunner($app, 'cake');
  243. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  244. $this->assertContains(Configure::version(), $output->messages()[0]);
  245. }
  246. /**
  247. * Test running a valid command
  248. *
  249. * @return void
  250. */
  251. public function testRunValidCommand()
  252. {
  253. $app = $this->getMockBuilder(BaseApplication::class)
  254. ->setMethods(['middleware', 'bootstrap', 'routes'])
  255. ->setConstructorArgs([$this->config])
  256. ->getMock();
  257. $output = new ConsoleOutput();
  258. $runner = new CommandRunner($app, 'cake');
  259. $result = $runner->run(['cake', 'routes'], $this->getMockIo($output));
  260. $this->assertSame(Shell::CODE_SUCCESS, $result);
  261. $contents = implode("\n", $output->messages());
  262. $this->assertContains('URI template', $contents);
  263. }
  264. /**
  265. * Test running a valid command and that backwards compatible
  266. * inflection is hooked up.
  267. *
  268. * @return void
  269. */
  270. public function testRunValidCommandInflection()
  271. {
  272. $app = $this->getMockBuilder(BaseApplication::class)
  273. ->setMethods(['middleware', 'bootstrap', 'routes'])
  274. ->setConstructorArgs([$this->config])
  275. ->getMock();
  276. $output = new ConsoleOutput();
  277. $runner = new CommandRunner($app, 'cake');
  278. $result = $runner->run(['cake', 'OrmCache', 'build'], $this->getMockIo($output));
  279. $this->assertSame(Shell::CODE_SUCCESS, $result);
  280. $contents = implode("\n", $output->messages());
  281. $this->assertContains('Cache', $contents);
  282. }
  283. /**
  284. * Test running a valid raising an error
  285. *
  286. * @return void
  287. */
  288. public function testRunValidCommandWithAbort()
  289. {
  290. $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
  291. $output = new ConsoleOutput();
  292. $runner = new CommandRunner($app, 'cake');
  293. $result = $runner->run(['cake', 'failure', 'with_abort'], $this->getMockIo($output));
  294. $this->assertSame(Shell::CODE_ERROR, $result);
  295. }
  296. /**
  297. * Test returning a non-zero value
  298. *
  299. * @return void
  300. */
  301. public function testRunValidCommandReturnInteger()
  302. {
  303. $app = $this->makeAppWithCommands(['failure' => SampleShell::class]);
  304. $output = new ConsoleOutput();
  305. $runner = new CommandRunner($app, 'cake');
  306. $result = $runner->run(['cake', 'failure', 'returnValue'], $this->getMockIo($output));
  307. $this->assertSame(99, $result);
  308. }
  309. /**
  310. * Ensure that the root command name propagates to shell help
  311. *
  312. * @return void
  313. */
  314. public function testRunRootNamePropagates()
  315. {
  316. $app = $this->makeAppWithCommands(['sample' => SampleShell::class]);
  317. $output = new ConsoleOutput();
  318. $runner = new CommandRunner($app, 'widget');
  319. $runner->run(['widget', 'sample', '-h'], $this->getMockIo($output));
  320. $result = implode("\n", $output->messages());
  321. $this->assertContains('widget sample [-h]', $result);
  322. $this->assertNotContains('cake sample [-h]', $result);
  323. }
  324. /**
  325. * Test running a valid command
  326. *
  327. * @return void
  328. */
  329. public function testRunValidCommandClass()
  330. {
  331. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  332. $output = new ConsoleOutput();
  333. $runner = new CommandRunner($app, 'cake');
  334. $result = $runner->run(['cake', 'ex'], $this->getMockIo($output));
  335. $this->assertSame(Shell::CODE_SUCCESS, $result);
  336. $messages = implode("\n", $output->messages());
  337. $this->assertContains('Demo Command!', $messages);
  338. }
  339. /**
  340. * Test running a valid command with spaces in the name
  341. *
  342. * @return void
  343. */
  344. public function testRunValidCommandSubcommandName()
  345. {
  346. $app = $this->makeAppWithCommands([
  347. 'tool build' => DemoCommand::class,
  348. 'tool' => AbortCommand::class,
  349. ]);
  350. $output = new ConsoleOutput();
  351. $runner = new CommandRunner($app, 'cake');
  352. $result = $runner->run(['cake', 'tool', 'build'], $this->getMockIo($output));
  353. $this->assertSame(Shell::CODE_SUCCESS, $result);
  354. $messages = implode("\n", $output->messages());
  355. $this->assertContains('Demo Command!', $messages);
  356. }
  357. /**
  358. * Test running a valid command with spaces in the name
  359. *
  360. * @return void
  361. */
  362. public function testRunValidCommandNestedName()
  363. {
  364. $app = $this->makeAppWithCommands([
  365. 'tool build assets' => DemoCommand::class,
  366. 'tool' => AbortCommand::class,
  367. ]);
  368. $output = new ConsoleOutput();
  369. $runner = new CommandRunner($app, 'cake');
  370. $result = $runner->run(['cake', 'tool', 'build', 'assets'], $this->getMockIo($output));
  371. $this->assertSame(Shell::CODE_SUCCESS, $result);
  372. $messages = implode("\n", $output->messages());
  373. $this->assertContains('Demo Command!', $messages);
  374. }
  375. /**
  376. * Test using a custom factory
  377. *
  378. * @return void
  379. */
  380. public function testRunWithCustomFactory()
  381. {
  382. $output = new ConsoleOutput();
  383. $io = $this->getMockIo($output);
  384. $factory = $this->createMock(CommandFactoryInterface::class);
  385. $factory->expects($this->once())
  386. ->method('create')
  387. ->with(DemoCommand::class)
  388. ->willReturn(new DemoCommand());
  389. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  390. $runner = new CommandRunner($app, 'cake', $factory);
  391. $result = $runner->run(['cake', 'ex'], $io);
  392. $this->assertSame(Shell::CODE_SUCCESS, $result);
  393. $messages = implode("\n", $output->messages());
  394. $this->assertContains('Demo Command!', $messages);
  395. }
  396. /**
  397. * Test running a command class' help
  398. *
  399. * @return void
  400. */
  401. public function testRunValidCommandClassHelp()
  402. {
  403. $app = $this->makeAppWithCommands(['ex' => DemoCommand::class]);
  404. $output = new ConsoleOutput();
  405. $runner = new CommandRunner($app, 'cake');
  406. $result = $runner->run(['cake', 'ex', '-h'], $this->getMockIo($output));
  407. $this->assertSame(Shell::CODE_SUCCESS, $result);
  408. $messages = implode("\n", $output->messages());
  409. $this->assertContains("\ncake ex [-h]", $messages);
  410. $this->assertNotContains('Demo Command!', $messages);
  411. }
  412. /**
  413. * Test that run() fires off the buildCommands event.
  414. *
  415. * @return void
  416. */
  417. public function testRunTriggersBuildCommandsEvent()
  418. {
  419. $app = $this->getMockBuilder(BaseApplication::class)
  420. ->setMethods(['middleware', 'bootstrap', 'routes'])
  421. ->setConstructorArgs([$this->config])
  422. ->getMock();
  423. $output = new ConsoleOutput();
  424. $runner = new CommandRunner($app, 'cake');
  425. $runner->getEventManager()->on('Console.buildCommands', function ($event, $commands) {
  426. $this->assertInstanceOf(CommandCollection::class, $commands);
  427. $this->eventTriggered = true;
  428. });
  429. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  430. $this->assertTrue($this->eventTriggered, 'Should have triggered event.');
  431. }
  432. /**
  433. * Test that run calls plugin hook methods
  434. *
  435. * @return void
  436. */
  437. public function testRunCallsPluginHookMethods()
  438. {
  439. $app = $this->getMockBuilder(BaseApplication::class)
  440. ->setMethods([
  441. 'middleware', 'bootstrap', 'routes',
  442. 'pluginBootstrap', 'pluginConsole', 'pluginRoutes',
  443. ])
  444. ->setConstructorArgs([$this->config])
  445. ->getMock();
  446. $app->expects($this->at(0))->method('bootstrap');
  447. $app->expects($this->at(1))->method('pluginBootstrap');
  448. $commands = new CommandCollection();
  449. $app->expects($this->at(2))
  450. ->method('pluginConsole')
  451. ->with($this->isinstanceOf(CommandCollection::class))
  452. ->will($this->returnCallback(function ($commands) {
  453. return $commands;
  454. }));
  455. $app->expects($this->at(3))->method('routes');
  456. $app->expects($this->at(4))->method('pluginRoutes');
  457. $output = new ConsoleOutput();
  458. $runner = new CommandRunner($app, 'cake');
  459. $result = $runner->run(['cake', '--version'], $this->getMockIo($output));
  460. $this->assertContains(Configure::version(), $output->messages()[0]);
  461. }
  462. /**
  463. * Test that run() loads routing.
  464. *
  465. * @return void
  466. */
  467. public function testRunLoadsRoutes()
  468. {
  469. $app = $this->getMockBuilder(BaseApplication::class)
  470. ->setMethods(['middleware', 'bootstrap'])
  471. ->setConstructorArgs([TEST_APP . 'config' . DS])
  472. ->getMock();
  473. $output = new ConsoleOutput();
  474. $runner = new CommandRunner($app, 'cake');
  475. $runner->run(['cake', '--version'], $this->getMockIo($output));
  476. $this->assertGreaterThan(2, count(Router::getRouteCollection()->routes()));
  477. }
  478. protected function makeAppWithCommands($commands)
  479. {
  480. $app = $this->getMockBuilder(BaseApplication::class)
  481. ->setMethods(['middleware', 'bootstrap', 'console', 'routes'])
  482. ->setConstructorArgs([$this->config])
  483. ->getMock();
  484. $collection = new CommandCollection($commands);
  485. $app->method('console')->will($this->returnValue($collection));
  486. return $app;
  487. }
  488. protected function getMockIo($output)
  489. {
  490. $io = $this->getMockBuilder(ConsoleIo::class)
  491. ->setConstructorArgs([$output, $output, null, null])
  492. ->setMethods(['in'])
  493. ->getMock();
  494. return $io;
  495. }
  496. }