ShellDispatcherTest.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
  5. * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  6. *
  7. * Licensed under The MIT License
  8. * For full copyright and license information, please see the LICENSE.txt
  9. * Redistributions of files must retain the above copyright notice
  10. *
  11. * @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
  12. * @link https://cakephp.org CakePHP(tm) Project
  13. * @since 1.2.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Console;
  17. use Cake\Console\Shell;
  18. use Cake\Console\ShellDispatcher;
  19. use Cake\TestSuite\TestCase;
  20. /**
  21. * ShellDispatcherTest
  22. */
  23. class ShellDispatcherTest extends TestCase
  24. {
  25. /**
  26. * @var \Cake\Console\ShellDispatcher|\PHPUnit\Framework\MockObject\MockObject
  27. */
  28. protected $dispatcher;
  29. /**
  30. * setUp method
  31. */
  32. public function setUp(): void
  33. {
  34. parent::setUp();
  35. $this->loadPlugins(['TestPlugin', 'Company/TestPluginThree']);
  36. static::setAppNamespace();
  37. $this->dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  38. ->addMethods(['_stop'])
  39. ->getMock();
  40. }
  41. /**
  42. * teardown
  43. */
  44. public function tearDown(): void
  45. {
  46. parent::tearDown();
  47. ShellDispatcher::resetAliases();
  48. $this->clearPlugins();
  49. }
  50. /**
  51. * Test error on missing shell
  52. */
  53. public function testFindShellMissing(): void
  54. {
  55. $this->expectException(\Cake\Console\Exception\MissingShellException::class);
  56. $this->dispatcher->findShell('nope');
  57. }
  58. /**
  59. * Test error on missing plugin shell
  60. */
  61. public function testFindShellMissingPlugin(): void
  62. {
  63. $this->expectException(\Cake\Console\Exception\MissingShellException::class);
  64. $this->dispatcher->findShell('test_plugin.nope');
  65. }
  66. /**
  67. * Verify loading of (plugin-) shells
  68. */
  69. public function testFindShell(): void
  70. {
  71. $result = $this->dispatcher->findShell('sample');
  72. $this->assertInstanceOf('TestApp\Shell\SampleShell', $result);
  73. $result = $this->dispatcher->findShell('test_plugin.example');
  74. $this->assertInstanceOf('TestPlugin\Shell\ExampleShell', $result);
  75. $this->assertSame('TestPlugin', $result->plugin);
  76. $this->assertSame('Example', $result->name);
  77. $result = $this->dispatcher->findShell('TestPlugin.example');
  78. $this->assertInstanceOf('TestPlugin\Shell\ExampleShell', $result);
  79. $result = $this->dispatcher->findShell('TestPlugin.Example');
  80. $this->assertInstanceOf('TestPlugin\Shell\ExampleShell', $result);
  81. }
  82. /**
  83. * testAddShortPluginAlias
  84. */
  85. public function testAddShortPluginAlias(): void
  86. {
  87. $expected = [
  88. 'Company' => 'Company/TestPluginThree.company',
  89. 'Example' => 'TestPlugin.example',
  90. ];
  91. $result = $this->dispatcher->addShortPluginAliases();
  92. $this->assertSame($expected, $result, 'Should return the list of aliased plugin shells');
  93. ShellDispatcher::alias('Example', 'SomeOther.PluginsShell');
  94. $expected = [
  95. 'Company' => 'Company/TestPluginThree.company',
  96. 'Example' => 'SomeOther.PluginsShell',
  97. ];
  98. $result = $this->dispatcher->addShortPluginAliases();
  99. $this->assertSame($expected, $result, 'Should not overwrite existing aliases');
  100. }
  101. /**
  102. * Test getting shells with aliases.
  103. */
  104. public function testFindShellAliased(): void
  105. {
  106. ShellDispatcher::alias('short', 'test_plugin.example');
  107. $result = $this->dispatcher->findShell('short');
  108. $this->assertInstanceOf('TestPlugin\Shell\ExampleShell', $result);
  109. $this->assertSame('TestPlugin', $result->plugin);
  110. $this->assertSame('Example', $result->name);
  111. }
  112. /**
  113. * Test finding a shell that has a matching alias.
  114. *
  115. * Aliases should not overload concrete shells.
  116. */
  117. public function testFindShellAliasedAppShadow(): void
  118. {
  119. ShellDispatcher::alias('sample', 'test_plugin.example');
  120. $result = $this->dispatcher->findShell('sample');
  121. $this->assertInstanceOf('TestApp\Shell\SampleShell', $result);
  122. $this->assertEmpty($result->plugin);
  123. $this->assertSame('Sample', $result->name);
  124. }
  125. /**
  126. * Verify dispatch handling stop errors
  127. */
  128. public function testDispatchShellWithAbort(): void
  129. {
  130. $io = $this->getMockBuilder('Cake\Console\ConsoleIo')->getMock();
  131. $shell = $this->getMockBuilder('Cake\Console\Shell')
  132. ->addMethods(['main'])
  133. ->setConstructorArgs([$io])
  134. ->getMock();
  135. $shell->expects($this->once())
  136. ->method('main')
  137. ->will($this->returnCallback(function () use ($shell): void {
  138. $shell->abort('Bad things', 99);
  139. }));
  140. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  141. ->onlyMethods(['findShell'])
  142. ->getMock();
  143. $dispatcher->expects($this->any())
  144. ->method('findShell')
  145. ->with('aborter')
  146. ->will($this->returnValue($shell));
  147. $dispatcher->args = ['aborter'];
  148. $result = $dispatcher->dispatch();
  149. $this->assertSame(99, $result, 'Should return the exception error code.');
  150. }
  151. /**
  152. * Verify correct dispatch of Shell subclasses with a main method
  153. */
  154. public function testDispatchShellWithMain(): void
  155. {
  156. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  157. ->onlyMethods(['findShell'])
  158. ->getMock();
  159. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  160. ->disableOriginalConstructor()
  161. ->getMock();
  162. $Shell->expects($this->exactly(2))->method('initialize');
  163. $Shell->expects($this->exactly(2))
  164. ->method('runCommand')
  165. ->will($this->returnValue(null));
  166. $dispatcher->expects($this->any())
  167. ->method('findShell')
  168. ->with('mock_with_main')
  169. ->will($this->returnValue($Shell));
  170. $dispatcher->args = ['mock_with_main'];
  171. $result = $dispatcher->dispatch();
  172. $this->assertSame(Shell::CODE_SUCCESS, $result);
  173. $this->assertEquals([], $dispatcher->args);
  174. $dispatcher->args = ['mock_with_main'];
  175. $result = $dispatcher->dispatch();
  176. $this->assertSame(Shell::CODE_SUCCESS, $result);
  177. $this->assertEquals([], $dispatcher->args);
  178. }
  179. /**
  180. * Verifies correct dispatch of Shell subclasses with integer exit codes.
  181. */
  182. public function testDispatchShellWithIntegerSuccessCode(): void
  183. {
  184. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  185. ->onlyMethods(['findShell'])
  186. ->getMock();
  187. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  188. ->disableOriginalConstructor()
  189. ->getMock();
  190. $Shell->expects($this->once())->method('initialize');
  191. $Shell->expects($this->once())->method('runCommand')
  192. ->with(['initdb'])
  193. ->will($this->returnValue(Shell::CODE_SUCCESS));
  194. $dispatcher->expects($this->any())
  195. ->method('findShell')
  196. ->with('mock_without_main')
  197. ->will($this->returnValue($Shell));
  198. $dispatcher->args = ['mock_without_main', 'initdb'];
  199. $result = $dispatcher->dispatch();
  200. $this->assertSame(Shell::CODE_SUCCESS, $result);
  201. }
  202. /**
  203. * Verifies correct dispatch of Shell subclasses with custom integer exit codes.
  204. */
  205. public function testDispatchShellWithCustomIntegerCodes(): void
  206. {
  207. $customErrorCode = 3;
  208. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  209. ->onlyMethods(['findShell'])
  210. ->getMock();
  211. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  212. ->disableOriginalConstructor()
  213. ->getMock();
  214. $Shell->expects($this->once())->method('initialize');
  215. $Shell->expects($this->once())->method('runCommand')
  216. ->with(['initdb'])
  217. ->will($this->returnValue($customErrorCode));
  218. $dispatcher->expects($this->any())
  219. ->method('findShell')
  220. ->with('mock_without_main')
  221. ->will($this->returnValue($Shell));
  222. $dispatcher->args = ['mock_without_main', 'initdb'];
  223. $result = $dispatcher->dispatch();
  224. $this->assertSame($customErrorCode, $result);
  225. }
  226. /**
  227. * Verify correct dispatch of Shell subclasses without a main method
  228. */
  229. public function testDispatchShellWithoutMain(): void
  230. {
  231. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  232. ->onlyMethods(['findShell'])
  233. ->getMock();
  234. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  235. ->disableOriginalConstructor()
  236. ->getMock();
  237. $Shell->expects($this->once())->method('initialize');
  238. $Shell->expects($this->once())->method('runCommand')
  239. ->with(['initdb'])
  240. ->will($this->returnValue(true));
  241. $dispatcher->expects($this->any())
  242. ->method('findShell')
  243. ->with('mock_without_main')
  244. ->will($this->returnValue($Shell));
  245. $dispatcher->args = ['mock_without_main', 'initdb'];
  246. $result = $dispatcher->dispatch();
  247. $this->assertSame(Shell::CODE_SUCCESS, $result);
  248. }
  249. /**
  250. * Verify you can dispatch a plugin's main shell with the shell name alone
  251. */
  252. public function testDispatchShortPluginAlias(): void
  253. {
  254. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  255. ->onlyMethods(['_shellExists', '_createShell'])
  256. ->getMock();
  257. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  258. ->disableOriginalConstructor()
  259. ->getMock();
  260. $dispatcher->expects($this->exactly(2))
  261. ->method('_shellExists')
  262. ->withConsecutive(['example'], ['TestPlugin.Example'])
  263. ->will($this->onConsecutiveCalls(null, 'TestPlugin\Console\Command\TestPluginShell'));
  264. $dispatcher->expects($this->once())
  265. ->method('_createShell')
  266. ->with('TestPlugin\Console\Command\TestPluginShell', 'TestPlugin.Example')
  267. ->will($this->returnValue($Shell));
  268. $dispatcher->args = ['example'];
  269. $result = $dispatcher->dispatch();
  270. $this->assertSame(Shell::CODE_SUCCESS, $result);
  271. }
  272. /**
  273. * Ensure short plugin shell usage is case/camelized insensitive
  274. */
  275. public function testDispatchShortPluginAliasCamelized(): void
  276. {
  277. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  278. ->onlyMethods(['_shellExists', '_createShell'])
  279. ->getMock();
  280. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  281. ->disableOriginalConstructor()
  282. ->getMock();
  283. $dispatcher->expects($this->exactly(2))
  284. ->method('_shellExists')
  285. ->withConsecutive(['Example'], ['TestPlugin.Example'])
  286. ->will($this->onConsecutiveCalls(null, 'TestPlugin\Console\Command\TestPluginShell'));
  287. $dispatcher->expects($this->once())
  288. ->method('_createShell')
  289. ->with('TestPlugin\Console\Command\TestPluginShell', 'TestPlugin.Example')
  290. ->will($this->returnValue($Shell));
  291. $dispatcher->args = ['Example'];
  292. $result = $dispatcher->dispatch();
  293. $this->assertSame(Shell::CODE_SUCCESS, $result);
  294. }
  295. /**
  296. * Verify that in case of conflict, app shells take precedence in alias list
  297. */
  298. public function testDispatchShortPluginAliasConflict(): void
  299. {
  300. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  301. ->onlyMethods(['_shellExists', '_createShell'])
  302. ->getMock();
  303. $Shell = $this->getMockBuilder('Cake\Console\Shell')
  304. ->disableOriginalConstructor()
  305. ->getMock();
  306. $dispatcher->expects($this->once())
  307. ->method('_shellExists')
  308. ->with('sample')
  309. ->will($this->returnValue('App\Shell\SampleShell'));
  310. $dispatcher->expects($this->once())
  311. ->method('_createShell')
  312. ->with('App\Shell\SampleShell', 'sample')
  313. ->will($this->returnValue($Shell));
  314. $dispatcher->args = ['sample'];
  315. $result = $dispatcher->dispatch();
  316. $this->assertSame(Shell::CODE_SUCCESS, $result);
  317. }
  318. /**
  319. * Verify shifting of arguments
  320. */
  321. public function testShiftArgs(): void
  322. {
  323. $this->dispatcher->args = ['a', 'b', 'c'];
  324. $this->assertSame('a', $this->dispatcher->shiftArgs());
  325. $this->assertSame($this->dispatcher->args, ['b', 'c']);
  326. $this->dispatcher->args = ['a' => 'b', 'c', 'd'];
  327. $this->assertSame('b', $this->dispatcher->shiftArgs());
  328. $this->assertSame($this->dispatcher->args, ['c', 'd']);
  329. $this->dispatcher->args = ['a', 'b' => 'c', 'd'];
  330. $this->assertSame('a', $this->dispatcher->shiftArgs());
  331. $this->assertSame($this->dispatcher->args, ['b' => 'c', 'd']);
  332. $this->dispatcher->args = [0 => 'a', 2 => 'b', 30 => 'c'];
  333. $this->assertSame('a', $this->dispatcher->shiftArgs());
  334. $this->assertSame($this->dispatcher->args, [0 => 'b', 1 => 'c']);
  335. $this->dispatcher->args = [];
  336. $this->assertNull($this->dispatcher->shiftArgs());
  337. $this->assertSame([], $this->dispatcher->args);
  338. }
  339. /**
  340. * Test how `bin/cake --help` works.
  341. */
  342. public function testHelpOption(): void
  343. {
  344. $this->expectWarning();
  345. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  346. ->addMethods(['_stop'])
  347. ->getMock();
  348. $dispatcher->args = ['--help'];
  349. $dispatcher->dispatch();
  350. }
  351. /**
  352. * Test how `bin/cake --version` works.
  353. */
  354. public function testVersionOption(): void
  355. {
  356. $this->expectWarning();
  357. $dispatcher = $this->getMockBuilder('Cake\Console\ShellDispatcher')
  358. ->addMethods(['_stop'])
  359. ->getMock();
  360. $dispatcher->args = ['--version'];
  361. $dispatcher->dispatch();
  362. }
  363. }