ConsoleIoTest.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743
  1. <?php
  2. declare(strict_types=1);
  3. /**
  4. * CakePHP : 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 Project
  13. * @since 3.0.0
  14. * @license https://opensource.org/licenses/mit-license.php MIT License
  15. */
  16. namespace Cake\Test\TestCase\Console;
  17. use Cake\Console\ConsoleIo;
  18. use Cake\Console\Exception\StopException;
  19. use Cake\Filesystem\Filesystem;
  20. use Cake\Log\Log;
  21. use Cake\TestSuite\TestCase;
  22. /**
  23. * ConsoleIo test.
  24. */
  25. class ConsoleIoTest extends TestCase
  26. {
  27. /**
  28. * @var \Cake\Console\ConsoleIo
  29. */
  30. protected $io;
  31. /**
  32. * @var \Cake\Console\ConsoleOutput|\PHPUnit\Framework\MockObject\MockObject
  33. */
  34. protected $out;
  35. /**
  36. * @var \Cake\Console\ConsoleOutput|\PHPUnit\Framework\MockObject\MockObject
  37. */
  38. protected $err;
  39. /**
  40. * @var \Cake\Console\ConsoleInput|\PHPUnit\Framework\MockObject\MockObject
  41. */
  42. protected $in;
  43. /**
  44. * setUp method
  45. */
  46. public function setUp(): void
  47. {
  48. parent::setUp();
  49. static::setAppNamespace();
  50. $this->out = $this->getMockBuilder('Cake\Console\ConsoleOutput')
  51. ->disableOriginalConstructor()
  52. ->getMock();
  53. $this->err = $this->getMockBuilder('Cake\Console\ConsoleOutput')
  54. ->disableOriginalConstructor()
  55. ->getMock();
  56. $this->in = $this->getMockBuilder('Cake\Console\ConsoleInput')
  57. ->disableOriginalConstructor()
  58. ->getMock();
  59. $this->io = new ConsoleIo($this->out, $this->err, $this->in);
  60. }
  61. /**
  62. * teardown method
  63. */
  64. public function tearDown(): void
  65. {
  66. parent::tearDown();
  67. if (is_dir(TMP . 'shell_test')) {
  68. $fs = new Filesystem();
  69. $fs->deleteDir(TMP . 'shell_test');
  70. }
  71. }
  72. /**
  73. * Provider for testing choice types.
  74. *
  75. * @return array
  76. */
  77. public function choiceProvider(): array
  78. {
  79. return [
  80. [['y', 'n']],
  81. ['y,n'],
  82. ['y/n'],
  83. ['y'],
  84. ];
  85. }
  86. /**
  87. * test ask choices method
  88. *
  89. * @dataProvider choiceProvider
  90. * @param array|string $choices
  91. */
  92. public function testAskChoices($choices): void
  93. {
  94. $this->in->expects($this->once())
  95. ->method('read')
  96. ->will($this->returnValue('y'));
  97. $result = $this->io->askChoice('Just a test?', $choices);
  98. $this->assertSame('y', $result);
  99. }
  100. /**
  101. * test ask choices method
  102. *
  103. * @dataProvider choiceProvider
  104. * @param array|string $choices
  105. */
  106. public function testAskChoicesInsensitive($choices): void
  107. {
  108. $this->in->expects($this->once())
  109. ->method('read')
  110. ->will($this->returnValue('Y'));
  111. $result = $this->io->askChoice('Just a test?', $choices);
  112. $this->assertSame('Y', $result);
  113. }
  114. /**
  115. * Test ask method
  116. */
  117. public function testAsk(): void
  118. {
  119. $this->out->expects($this->once())
  120. ->method('write')
  121. ->with("<question>Just a test?</question>\n> ");
  122. $this->in->expects($this->once())
  123. ->method('read')
  124. ->will($this->returnValue('y'));
  125. $result = $this->io->ask('Just a test?');
  126. $this->assertSame('y', $result);
  127. }
  128. /**
  129. * Test ask method
  130. */
  131. public function testAskDefaultValue(): void
  132. {
  133. $this->out->expects($this->once())
  134. ->method('write')
  135. ->with("<question>Just a test?</question>\n[n] > ");
  136. $this->in->expects($this->once())
  137. ->method('read')
  138. ->will($this->returnValue(''));
  139. $result = $this->io->ask('Just a test?', 'n');
  140. $this->assertSame('n', $result);
  141. }
  142. /**
  143. * testOut method
  144. */
  145. public function testOut(): void
  146. {
  147. $this->out->expects($this->exactly(4))
  148. ->method('write')
  149. ->withConsecutive(
  150. ['Just a test', 1],
  151. [['Just', 'a', 'test'], 1],
  152. [['Just', 'a', 'test'], 2],
  153. ['', 1]
  154. );
  155. $this->io->out('Just a test');
  156. $this->io->out(['Just', 'a', 'test']);
  157. $this->io->out(['Just', 'a', 'test'], 2);
  158. $this->io->out();
  159. }
  160. /**
  161. * test that verbose and quiet output levels work
  162. */
  163. public function testVerboseOut(): void
  164. {
  165. $this->out->expects($this->exactly(3))
  166. ->method('write')
  167. ->withConsecutive(
  168. ['Verbose', 1],
  169. ['Normal', 1],
  170. ['Quiet', 1]
  171. );
  172. $this->io->level(ConsoleIo::VERBOSE);
  173. $this->io->out('Verbose', 1, ConsoleIo::VERBOSE);
  174. $this->io->out('Normal', 1, ConsoleIo::NORMAL);
  175. $this->io->out('Quiet', 1, ConsoleIo::QUIET);
  176. }
  177. /**
  178. * test that verbose and quiet output levels work
  179. */
  180. public function testVerboseOutput(): void
  181. {
  182. $this->out->expects($this->exactly(3))
  183. ->method('write')
  184. ->withConsecutive(
  185. ['Verbose', 1],
  186. ['Normal', 1],
  187. ['Quiet', 1]
  188. );
  189. $this->io->level(ConsoleIo::VERBOSE);
  190. $this->io->verbose('Verbose');
  191. $this->io->out('Normal');
  192. $this->io->quiet('Quiet');
  193. }
  194. /**
  195. * test that verbose and quiet output levels work
  196. */
  197. public function testQuietOutput(): void
  198. {
  199. $this->out->expects($this->exactly(2))
  200. ->method('write')
  201. ->withConsecutive(
  202. ['Quiet', 1],
  203. ['Quiet', 1]
  204. );
  205. $this->io->level(ConsoleIo::QUIET);
  206. $this->io->out('Verbose', 1, ConsoleIo::VERBOSE);
  207. $this->io->out('Normal', 1, ConsoleIo::NORMAL);
  208. $this->io->out('Quiet', 1, ConsoleIo::QUIET);
  209. $this->io->verbose('Verbose');
  210. $this->io->quiet('Quiet');
  211. }
  212. /**
  213. * testErr method
  214. */
  215. public function testErr(): void
  216. {
  217. $this->err->expects($this->exactly(4))
  218. ->method('write')
  219. ->withConsecutive(
  220. ['Just a test', 1],
  221. [['Just', 'a', 'test'], 1],
  222. [['Just', 'a', 'test'], 2],
  223. ['', 1]
  224. );
  225. $this->io->err('Just a test');
  226. $this->io->err(['Just', 'a', 'test']);
  227. $this->io->err(['Just', 'a', 'test'], 2);
  228. $this->io->err();
  229. }
  230. /**
  231. * Tests abort() wrapper.
  232. */
  233. public function testAbort(): void
  234. {
  235. $this->expectException(StopException::class);
  236. $this->expectExceptionMessage('Some error');
  237. $this->expectExceptionCode(1);
  238. $this->err->expects($this->once())
  239. ->method('write')
  240. ->with('<error>Some error</error>', 1);
  241. $this->expectException(StopException::class);
  242. $this->expectExceptionCode(1);
  243. $this->expectExceptionMessage('Some error');
  244. $this->io->abort('Some error');
  245. }
  246. /**
  247. * Tests abort() wrapper.
  248. */
  249. public function testAbortCustomCode(): void
  250. {
  251. $this->expectException(StopException::class);
  252. $this->expectExceptionMessage('Some error');
  253. $this->expectExceptionCode(99);
  254. $this->err->expects($this->once())
  255. ->method('write')
  256. ->with('<error>Some error</error>', 1);
  257. $this->expectException(StopException::class);
  258. $this->expectExceptionCode(99);
  259. $this->expectExceptionMessage('Some error');
  260. $this->io->abort('Some error', 99);
  261. }
  262. /**
  263. * testNl
  264. */
  265. public function testNl(): void
  266. {
  267. $newLine = "\n";
  268. if (DS === '\\') {
  269. $newLine = "\r\n";
  270. }
  271. $this->assertSame($this->io->nl(), $newLine);
  272. $this->assertSame($this->io->nl(2), $newLine . $newLine);
  273. $this->assertSame($this->io->nl(1), $newLine);
  274. }
  275. /**
  276. * testHr
  277. */
  278. public function testHr(): void
  279. {
  280. $bar = str_repeat('-', 79);
  281. $this->out->expects($this->exactly(6))
  282. ->method('write')
  283. ->withConsecutive(
  284. ['', 0],
  285. [$bar, 1],
  286. ['', 0],
  287. ['', true],
  288. [$bar, 1],
  289. ['', true]
  290. );
  291. $this->io->hr();
  292. $this->io->hr(2);
  293. }
  294. /**
  295. * Test overwriting.
  296. */
  297. public function testOverwrite(): void
  298. {
  299. $number = strlen('Some text I want to overwrite');
  300. $this->out->expects($this->atLeast(4))
  301. ->method('write')
  302. ->withConsecutive(
  303. ['Some <info>text</info> I want to overwrite', 0],
  304. [str_repeat("\x08", $number), 0],
  305. ['Less text', 0],
  306. [str_repeat(' ', $number - 9), 0],
  307. [PHP_EOL, 0]
  308. )
  309. ->will($this->onConsecutiveCalls(
  310. $number,
  311. 9,
  312. 9,
  313. 1,
  314. 0
  315. ));
  316. $this->io->out('Some <info>text</info> I want to overwrite', 0);
  317. $this->io->overwrite('Less text');
  318. }
  319. /**
  320. * Test overwriting content with shorter content
  321. */
  322. public function testOverwriteWithShorterContent(): void
  323. {
  324. $length = strlen('12345');
  325. $this->out->expects($this->exactly(7))
  326. ->method('write')
  327. ->withConsecutive(
  328. ['12345'],
  329. // Backspaces
  330. [str_repeat("\x08", $length), 0],
  331. ['123', 0],
  332. // 2 spaces output to pad up to 5 bytes
  333. [str_repeat(' ', $length - 3), 0],
  334. // Backspaces
  335. [str_repeat("\x08", $length), 0],
  336. ['12', 0],
  337. [str_repeat(' ', $length - 2), 0]
  338. )
  339. ->will($this->onConsecutiveCalls(
  340. $length,
  341. $length,
  342. 3,
  343. $length - 3,
  344. $length,
  345. 2,
  346. $length - 2
  347. ));
  348. $this->io->out('12345');
  349. $this->io->overwrite('123', 0);
  350. $this->io->overwrite('12', 0);
  351. }
  352. /**
  353. * Test overwriting content with longer content
  354. */
  355. public function testOverwriteWithLongerContent(): void
  356. {
  357. $this->out->expects($this->exactly(5))
  358. ->method('write')
  359. ->withConsecutive(
  360. ['1'],
  361. // Backspaces
  362. [str_repeat("\x08", 1), 0],
  363. ['123', 0],
  364. // Backspaces
  365. [str_repeat("\x08", 3), 0],
  366. ['12345', 0]
  367. )
  368. ->will($this->onConsecutiveCalls(
  369. 1,
  370. 1,
  371. 3,
  372. 3,
  373. 5
  374. ));
  375. $this->io->out('1');
  376. $this->io->overwrite('123', 0);
  377. $this->io->overwrite('12345', 0);
  378. }
  379. /**
  380. * Tests that setLoggers works properly
  381. */
  382. public function testSetLoggers(): void
  383. {
  384. Log::drop('stdout');
  385. Log::drop('stderr');
  386. $this->io->setLoggers(true);
  387. $this->assertNotEmpty(Log::engine('stdout'));
  388. $this->assertNotEmpty(Log::engine('stderr'));
  389. $this->io->setLoggers(false);
  390. $this->assertNull(Log::engine('stdout'));
  391. $this->assertNull(Log::engine('stderr'));
  392. }
  393. /**
  394. * Tests that setLoggers works properly with quiet
  395. */
  396. public function testSetLoggersQuiet(): void
  397. {
  398. Log::drop('stdout');
  399. Log::drop('stderr');
  400. $this->io->setLoggers(ConsoleIo::QUIET);
  401. $this->assertEmpty(Log::engine('stdout'));
  402. $this->assertNotEmpty(Log::engine('stderr'));
  403. }
  404. /**
  405. * Tests that setLoggers works properly with verbose
  406. */
  407. public function testSetLoggersVerbose(): void
  408. {
  409. Log::drop('stdout');
  410. Log::drop('stderr');
  411. $this->io->setLoggers(ConsoleIo::VERBOSE);
  412. $this->assertNotEmpty(Log::engine('stderr'));
  413. $engine = Log::engine('stdout');
  414. $this->assertEquals(['notice', 'info', 'debug'], $engine->getConfig('levels'));
  415. }
  416. /**
  417. * Ensure that setStyle() just proxies to stdout.
  418. */
  419. public function testSetStyle(): void
  420. {
  421. $this->out->expects($this->once())
  422. ->method('setStyle')
  423. ->with('name', ['props']);
  424. $this->io->setStyle('name', ['props']);
  425. }
  426. /**
  427. * Ensure that getStyle() just proxies to stdout.
  428. */
  429. public function testGetStyle(): void
  430. {
  431. $this->out->expects($this->once())
  432. ->method('getStyle')
  433. ->with('name');
  434. $this->io->getStyle('name');
  435. }
  436. /**
  437. * Ensure that styles() just proxies to stdout.
  438. */
  439. public function testStyles(): void
  440. {
  441. $this->out->expects($this->once())
  442. ->method('styles');
  443. $this->io->styles();
  444. }
  445. /**
  446. * Test the helper method.
  447. */
  448. public function testHelper(): void
  449. {
  450. $this->out->expects($this->once())
  451. ->method('write')
  452. ->with('It works!well ish');
  453. $helper = $this->io->helper('simple');
  454. $this->assertInstanceOf('Cake\Console\Helper', $helper);
  455. $helper->output(['well', 'ish']);
  456. }
  457. /**
  458. * Provider for output helpers
  459. *
  460. * @return array
  461. */
  462. public function outHelperProvider(): array
  463. {
  464. return [['info'], ['success'], ['comment']];
  465. }
  466. /**
  467. * Provider for err helpers
  468. *
  469. * @return array
  470. */
  471. public function errHelperProvider(): array
  472. {
  473. return [['warning'], ['error']];
  474. }
  475. /**
  476. * test out helper methods
  477. *
  478. * @dataProvider outHelperProvider
  479. */
  480. public function testOutHelpers(string $method): void
  481. {
  482. $this->out->expects($this->exactly(2))
  483. ->method('write')
  484. ->withConsecutive(
  485. [ "<{$method}>Just a test</{$method}>", 1],
  486. [["<{$method}>Just</{$method}>", "<{$method}>a test</{$method}>"], 1]
  487. );
  488. $this->io->{$method}('Just a test');
  489. $this->io->{$method}(['Just', 'a test']);
  490. }
  491. /**
  492. * test err helper methods
  493. *
  494. * @dataProvider errHelperProvider
  495. */
  496. public function testErrHelpers(string $method): void
  497. {
  498. $this->err->expects($this->exactly(2))
  499. ->method('write')
  500. ->withConsecutive(
  501. [ "<{$method}>Just a test</{$method}>", 1],
  502. [["<{$method}>Just</{$method}>", "<{$method}>a test</{$method}>"], 1]
  503. );
  504. $this->io->{$method}('Just a test');
  505. $this->io->{$method}(['Just', 'a test']);
  506. }
  507. /**
  508. * Test that createFile
  509. */
  510. public function testCreateFileSuccess(): void
  511. {
  512. $this->err->expects($this->never())
  513. ->method('write');
  514. $path = TMP . 'shell_test';
  515. mkdir($path);
  516. $file = $path . DS . 'file1.php';
  517. $contents = 'some content';
  518. $result = $this->io->createFile($file, $contents);
  519. $this->assertTrue($result);
  520. $this->assertFileExists($file);
  521. $this->assertStringEqualsFile($file, $contents);
  522. }
  523. public function testCreateFileEmptySuccess(): void
  524. {
  525. $this->err->expects($this->never())
  526. ->method('write');
  527. $path = TMP . 'shell_test';
  528. mkdir($path);
  529. $file = $path . DS . 'file_empty.php';
  530. $contents = '';
  531. $result = $this->io->createFile($file, $contents);
  532. $this->assertTrue($result);
  533. $this->assertFileExists($file);
  534. $this->assertStringEqualsFile($file, $contents);
  535. }
  536. public function testCreateFileDirectoryCreation(): void
  537. {
  538. $this->err->expects($this->never())
  539. ->method('write');
  540. $directory = TMP . 'shell_test';
  541. $this->assertFileDoesNotExist($directory, 'Directory should not exist before createFile');
  542. $path = $directory . DS . 'create.txt';
  543. $contents = 'some content';
  544. $result = $this->io->createFile($path, $contents);
  545. $this->assertTrue($result, 'File should create');
  546. $this->assertFileExists($path);
  547. $this->assertStringEqualsFile($path, $contents);
  548. }
  549. /**
  550. * Test that createFile with permissions error.
  551. */
  552. public function testCreateFilePermissionsError(): void
  553. {
  554. $this->skipIf(DS === '\\', 'Cant perform operations using permissions on windows.');
  555. $path = TMP . 'shell_test';
  556. $file = $path . DS . 'no_perms';
  557. if (!is_dir($path)) {
  558. mkdir($path);
  559. }
  560. chmod($path, 0444);
  561. $this->io->createFile($file, 'testing');
  562. $this->assertFileDoesNotExist($file);
  563. chmod($path, 0744);
  564. rmdir($path);
  565. }
  566. /**
  567. * Test that `q` raises an error.
  568. */
  569. public function testCreateFileOverwriteQuit(): void
  570. {
  571. $path = TMP . 'shell_test';
  572. mkdir($path);
  573. $file = $path . DS . 'file1.php';
  574. touch($file);
  575. $this->expectException(StopException::class);
  576. $this->in->expects($this->once())
  577. ->method('read')
  578. ->will($this->returnValue('q'));
  579. $this->io->createFile($file, 'some content');
  580. }
  581. /**
  582. * Test that `n` raises an error.
  583. */
  584. public function testCreateFileOverwriteNo(): void
  585. {
  586. $path = TMP . 'shell_test';
  587. mkdir($path);
  588. $file = $path . DS . 'file1.php';
  589. file_put_contents($file, 'original');
  590. touch($file);
  591. $this->in->expects($this->once())
  592. ->method('read')
  593. ->will($this->returnValue('n'));
  594. $contents = 'new content';
  595. $result = $this->io->createFile($file, $contents);
  596. $this->assertFalse($result);
  597. $this->assertFileExists($file);
  598. $this->assertStringEqualsFile($file, 'original');
  599. }
  600. /**
  601. * Test the forceOverwrite parameter
  602. */
  603. public function testCreateFileOverwriteParam(): void
  604. {
  605. $path = TMP . 'shell_test';
  606. mkdir($path);
  607. $file = $path . DS . 'file1.php';
  608. file_put_contents($file, 'original');
  609. touch($file);
  610. $contents = 'new content';
  611. $result = $this->io->createFile($file, $contents, true);
  612. $this->assertTrue($result);
  613. $this->assertFileExists($file);
  614. $this->assertStringEqualsFile($file, $contents);
  615. }
  616. /**
  617. * Test the `a` response
  618. */
  619. public function testCreateFileOverwriteAll(): void
  620. {
  621. $path = TMP . 'shell_test';
  622. mkdir($path);
  623. $file = $path . DS . 'file1.php';
  624. file_put_contents($file, 'original');
  625. touch($file);
  626. $this->in->expects($this->once())
  627. ->method('read')
  628. ->will($this->returnValue('a'));
  629. $this->io->createFile($file, 'new content');
  630. $this->assertStringEqualsFile($file, 'new content');
  631. $this->io->createFile($file, 'newer content');
  632. $this->assertStringEqualsFile($file, 'newer content');
  633. $this->io->createFile($file, 'newest content', false);
  634. $this->assertStringEqualsFile(
  635. $file,
  636. 'newest content',
  637. 'overwrite state replaces parameter'
  638. );
  639. }
  640. }