ProgressBarTest.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\Console\Tests\Helper;
  11. use Symfony\Component\Console\Helper\ProgressBar;
  12. use Symfony\Component\Console\Helper\Helper;
  13. use Symfony\Component\Console\Output\StreamOutput;
  14. /**
  15. * @group time-sensitive
  16. */
  17. class ProgressBarTest extends \PHPUnit_Framework_TestCase
  18. {
  19. public function testMultipleStart()
  20. {
  21. $bar = new ProgressBar($output = $this->getOutputStream());
  22. $bar->start();
  23. $bar->advance();
  24. $bar->start();
  25. rewind($output->getStream());
  26. $this->assertEquals(
  27. ' 0 [>---------------------------]'.
  28. $this->generateOutput(' 1 [->--------------------------]').
  29. $this->generateOutput(' 0 [>---------------------------]'),
  30. stream_get_contents($output->getStream())
  31. );
  32. }
  33. public function testAdvance()
  34. {
  35. $bar = new ProgressBar($output = $this->getOutputStream());
  36. $bar->start();
  37. $bar->advance();
  38. rewind($output->getStream());
  39. $this->assertEquals(
  40. ' 0 [>---------------------------]'.
  41. $this->generateOutput(' 1 [->--------------------------]'),
  42. stream_get_contents($output->getStream())
  43. );
  44. }
  45. public function testAdvanceWithStep()
  46. {
  47. $bar = new ProgressBar($output = $this->getOutputStream());
  48. $bar->start();
  49. $bar->advance(5);
  50. rewind($output->getStream());
  51. $this->assertEquals(
  52. ' 0 [>---------------------------]'.
  53. $this->generateOutput(' 5 [----->----------------------]'),
  54. stream_get_contents($output->getStream())
  55. );
  56. }
  57. public function testAdvanceMultipleTimes()
  58. {
  59. $bar = new ProgressBar($output = $this->getOutputStream());
  60. $bar->start();
  61. $bar->advance(3);
  62. $bar->advance(2);
  63. rewind($output->getStream());
  64. $this->assertEquals(
  65. ' 0 [>---------------------------]'.
  66. $this->generateOutput(' 3 [--->------------------------]').
  67. $this->generateOutput(' 5 [----->----------------------]'),
  68. stream_get_contents($output->getStream())
  69. );
  70. }
  71. public function testAdvanceOverMax()
  72. {
  73. $bar = new ProgressBar($output = $this->getOutputStream(), 10);
  74. $bar->setProgress(9);
  75. $bar->advance();
  76. $bar->advance();
  77. rewind($output->getStream());
  78. $this->assertEquals(
  79. ' 9/10 [=========================>--] 90%'.
  80. $this->generateOutput(' 10/10 [============================] 100%').
  81. $this->generateOutput(' 11/11 [============================] 100%'),
  82. stream_get_contents($output->getStream())
  83. );
  84. }
  85. public function testFormat()
  86. {
  87. $expected =
  88. ' 0/10 [>---------------------------] 0%'.
  89. $this->generateOutput(' 10/10 [============================] 100%').
  90. $this->generateOutput(' 10/10 [============================] 100%')
  91. ;
  92. // max in construct, no format
  93. $bar = new ProgressBar($output = $this->getOutputStream(), 10);
  94. $bar->start();
  95. $bar->advance(10);
  96. $bar->finish();
  97. rewind($output->getStream());
  98. $this->assertEquals($expected, stream_get_contents($output->getStream()));
  99. // max in start, no format
  100. $bar = new ProgressBar($output = $this->getOutputStream());
  101. $bar->start(10);
  102. $bar->advance(10);
  103. $bar->finish();
  104. rewind($output->getStream());
  105. $this->assertEquals($expected, stream_get_contents($output->getStream()));
  106. // max in construct, explicit format before
  107. $bar = new ProgressBar($output = $this->getOutputStream(), 10);
  108. $bar->setFormat('normal');
  109. $bar->start();
  110. $bar->advance(10);
  111. $bar->finish();
  112. rewind($output->getStream());
  113. $this->assertEquals($expected, stream_get_contents($output->getStream()));
  114. // max in start, explicit format before
  115. $bar = new ProgressBar($output = $this->getOutputStream());
  116. $bar->setFormat('normal');
  117. $bar->start(10);
  118. $bar->advance(10);
  119. $bar->finish();
  120. rewind($output->getStream());
  121. $this->assertEquals($expected, stream_get_contents($output->getStream()));
  122. }
  123. public function testCustomizations()
  124. {
  125. $bar = new ProgressBar($output = $this->getOutputStream(), 10);
  126. $bar->setBarWidth(10);
  127. $bar->setBarCharacter('_');
  128. $bar->setEmptyBarCharacter(' ');
  129. $bar->setProgressCharacter('/');
  130. $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%');
  131. $bar->start();
  132. $bar->advance();
  133. rewind($output->getStream());
  134. $this->assertEquals(
  135. ' 0/10 [/ ] 0%'.
  136. $this->generateOutput(' 1/10 [_/ ] 10%'),
  137. stream_get_contents($output->getStream())
  138. );
  139. }
  140. public function testDisplayWithoutStart()
  141. {
  142. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  143. $bar->display();
  144. rewind($output->getStream());
  145. $this->assertEquals(
  146. ' 0/50 [>---------------------------] 0%',
  147. stream_get_contents($output->getStream())
  148. );
  149. }
  150. public function testDisplayWithQuietVerbosity()
  151. {
  152. $bar = new ProgressBar($output = $this->getOutputStream(true, StreamOutput::VERBOSITY_QUIET), 50);
  153. $bar->display();
  154. rewind($output->getStream());
  155. $this->assertEquals(
  156. '',
  157. stream_get_contents($output->getStream())
  158. );
  159. }
  160. public function testFinishWithoutStart()
  161. {
  162. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  163. $bar->finish();
  164. rewind($output->getStream());
  165. $this->assertEquals(
  166. ' 50/50 [============================] 100%',
  167. stream_get_contents($output->getStream())
  168. );
  169. }
  170. public function testPercent()
  171. {
  172. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  173. $bar->start();
  174. $bar->display();
  175. $bar->advance();
  176. $bar->advance();
  177. rewind($output->getStream());
  178. $this->assertEquals(
  179. ' 0/50 [>---------------------------] 0%'.
  180. $this->generateOutput(' 0/50 [>---------------------------] 0%').
  181. $this->generateOutput(' 1/50 [>---------------------------] 2%').
  182. $this->generateOutput(' 2/50 [=>--------------------------] 4%'),
  183. stream_get_contents($output->getStream())
  184. );
  185. }
  186. public function testOverwriteWithShorterLine()
  187. {
  188. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  189. $bar->setFormat(' %current%/%max% [%bar%] %percent:3s%%');
  190. $bar->start();
  191. $bar->display();
  192. $bar->advance();
  193. // set shorter format
  194. $bar->setFormat(' %current%/%max% [%bar%]');
  195. $bar->advance();
  196. rewind($output->getStream());
  197. $this->assertEquals(
  198. ' 0/50 [>---------------------------] 0%'.
  199. $this->generateOutput(' 0/50 [>---------------------------] 0%').
  200. $this->generateOutput(' 1/50 [>---------------------------] 2%').
  201. $this->generateOutput(' 2/50 [=>--------------------------]'),
  202. stream_get_contents($output->getStream())
  203. );
  204. }
  205. public function testStartWithMax()
  206. {
  207. $bar = new ProgressBar($output = $this->getOutputStream());
  208. $bar->setFormat('%current%/%max% [%bar%]');
  209. $bar->start(50);
  210. $bar->advance();
  211. rewind($output->getStream());
  212. $this->assertEquals(
  213. ' 0/50 [>---------------------------]'.
  214. $this->generateOutput(' 1/50 [>---------------------------]'),
  215. stream_get_contents($output->getStream())
  216. );
  217. }
  218. public function testSetCurrentProgress()
  219. {
  220. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  221. $bar->start();
  222. $bar->display();
  223. $bar->advance();
  224. $bar->setProgress(15);
  225. $bar->setProgress(25);
  226. rewind($output->getStream());
  227. $this->assertEquals(
  228. ' 0/50 [>---------------------------] 0%'.
  229. $this->generateOutput(' 0/50 [>---------------------------] 0%').
  230. $this->generateOutput(' 1/50 [>---------------------------] 2%').
  231. $this->generateOutput(' 15/50 [========>-------------------] 30%').
  232. $this->generateOutput(' 25/50 [==============>-------------] 50%'),
  233. stream_get_contents($output->getStream())
  234. );
  235. }
  236. /**
  237. */
  238. public function testSetCurrentBeforeStarting()
  239. {
  240. $bar = new ProgressBar($this->getOutputStream());
  241. $bar->setProgress(15);
  242. $this->assertNotNull($bar->getStartTime());
  243. }
  244. /**
  245. * @expectedException \LogicException
  246. * @expectedExceptionMessage You can't regress the progress bar
  247. */
  248. public function testRegressProgress()
  249. {
  250. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  251. $bar->start();
  252. $bar->setProgress(15);
  253. $bar->setProgress(10);
  254. }
  255. public function testRedrawFrequency()
  256. {
  257. $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream(), 6));
  258. $bar->expects($this->exactly(4))->method('display');
  259. $bar->setRedrawFrequency(2);
  260. $bar->start();
  261. $bar->setProgress(1);
  262. $bar->advance(2);
  263. $bar->advance(2);
  264. $bar->advance(1);
  265. }
  266. public function testRedrawFrequencyIsAtLeastOneIfZeroGiven()
  267. {
  268. $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream()));
  269. $bar->expects($this->exactly(2))->method('display');
  270. $bar->setRedrawFrequency(0);
  271. $bar->start();
  272. $bar->advance();
  273. }
  274. public function testRedrawFrequencyIsAtLeastOneIfSmallerOneGiven()
  275. {
  276. $bar = $this->getMock('Symfony\Component\Console\Helper\ProgressBar', array('display'), array($this->getOutputStream()));
  277. $bar->expects($this->exactly(2))->method('display');
  278. $bar->setRedrawFrequency(0.9);
  279. $bar->start();
  280. $bar->advance();
  281. }
  282. public function testMultiByteSupport()
  283. {
  284. $bar = new ProgressBar($output = $this->getOutputStream());
  285. $bar->start();
  286. $bar->setBarCharacter('■');
  287. $bar->advance(3);
  288. rewind($output->getStream());
  289. $this->assertEquals(
  290. ' 0 [>---------------------------]'.
  291. $this->generateOutput(' 3 [■■■>------------------------]'),
  292. stream_get_contents($output->getStream())
  293. );
  294. }
  295. public function testClear()
  296. {
  297. $bar = new ProgressBar($output = $this->getOutputStream(), 50);
  298. $bar->start();
  299. $bar->setProgress(25);
  300. $bar->clear();
  301. rewind($output->getStream());
  302. $this->assertEquals(
  303. ' 0/50 [>---------------------------] 0%'.
  304. $this->generateOutput(' 25/50 [==============>-------------] 50%').
  305. $this->generateOutput(''),
  306. stream_get_contents($output->getStream())
  307. );
  308. }
  309. public function testPercentNotHundredBeforeComplete()
  310. {
  311. $bar = new ProgressBar($output = $this->getOutputStream(), 200);
  312. $bar->start();
  313. $bar->display();
  314. $bar->advance(199);
  315. $bar->advance();
  316. rewind($output->getStream());
  317. $this->assertEquals(
  318. ' 0/200 [>---------------------------] 0%'.
  319. $this->generateOutput(' 0/200 [>---------------------------] 0%').
  320. $this->generateOutput(' 199/200 [===========================>] 99%').
  321. $this->generateOutput(' 200/200 [============================] 100%'),
  322. stream_get_contents($output->getStream())
  323. );
  324. }
  325. public function testNonDecoratedOutput()
  326. {
  327. $bar = new ProgressBar($output = $this->getOutputStream(false), 200);
  328. $bar->start();
  329. for ($i = 0; $i < 200; ++$i) {
  330. $bar->advance();
  331. }
  332. $bar->finish();
  333. rewind($output->getStream());
  334. $this->assertEquals(
  335. ' 0/200 [>---------------------------] 0%'.PHP_EOL.
  336. ' 20/200 [==>-------------------------] 10%'.PHP_EOL.
  337. ' 40/200 [=====>----------------------] 20%'.PHP_EOL.
  338. ' 60/200 [========>-------------------] 30%'.PHP_EOL.
  339. ' 80/200 [===========>----------------] 40%'.PHP_EOL.
  340. ' 100/200 [==============>-------------] 50%'.PHP_EOL.
  341. ' 120/200 [================>-----------] 60%'.PHP_EOL.
  342. ' 140/200 [===================>--------] 70%'.PHP_EOL.
  343. ' 160/200 [======================>-----] 80%'.PHP_EOL.
  344. ' 180/200 [=========================>--] 90%'.PHP_EOL.
  345. ' 200/200 [============================] 100%',
  346. stream_get_contents($output->getStream())
  347. );
  348. }
  349. public function testNonDecoratedOutputWithClear()
  350. {
  351. $bar = new ProgressBar($output = $this->getOutputStream(false), 50);
  352. $bar->start();
  353. $bar->setProgress(25);
  354. $bar->clear();
  355. $bar->setProgress(50);
  356. $bar->finish();
  357. rewind($output->getStream());
  358. $this->assertEquals(
  359. ' 0/50 [>---------------------------] 0%'.PHP_EOL.
  360. ' 25/50 [==============>-------------] 50%'.PHP_EOL.
  361. ' 50/50 [============================] 100%',
  362. stream_get_contents($output->getStream())
  363. );
  364. }
  365. public function testNonDecoratedOutputWithoutMax()
  366. {
  367. $bar = new ProgressBar($output = $this->getOutputStream(false));
  368. $bar->start();
  369. $bar->advance();
  370. rewind($output->getStream());
  371. $this->assertEquals(
  372. ' 0 [>---------------------------]'.PHP_EOL.
  373. ' 1 [->--------------------------]',
  374. stream_get_contents($output->getStream())
  375. );
  376. }
  377. public function testParallelBars()
  378. {
  379. $output = $this->getOutputStream();
  380. $bar1 = new ProgressBar($output, 2);
  381. $bar2 = new ProgressBar($output, 3);
  382. $bar2->setProgressCharacter('#');
  383. $bar3 = new ProgressBar($output);
  384. $bar1->start();
  385. $output->write("\n");
  386. $bar2->start();
  387. $output->write("\n");
  388. $bar3->start();
  389. for ($i = 1; $i <= 3; ++$i) {
  390. // up two lines
  391. $output->write("\033[2A");
  392. if ($i <= 2) {
  393. $bar1->advance();
  394. }
  395. $output->write("\n");
  396. $bar2->advance();
  397. $output->write("\n");
  398. $bar3->advance();
  399. }
  400. $output->write("\033[2A");
  401. $output->write("\n");
  402. $output->write("\n");
  403. $bar3->finish();
  404. rewind($output->getStream());
  405. $this->assertEquals(
  406. ' 0/2 [>---------------------------] 0%'."\n".
  407. ' 0/3 [#---------------------------] 0%'."\n".
  408. rtrim(' 0 [>---------------------------]').
  409. "\033[2A".
  410. $this->generateOutput(' 1/2 [==============>-------------] 50%')."\n".
  411. $this->generateOutput(' 1/3 [=========#------------------] 33%')."\n".
  412. rtrim($this->generateOutput(' 1 [->--------------------------]')).
  413. "\033[2A".
  414. $this->generateOutput(' 2/2 [============================] 100%')."\n".
  415. $this->generateOutput(' 2/3 [==================#---------] 66%')."\n".
  416. rtrim($this->generateOutput(' 2 [-->-------------------------]')).
  417. "\033[2A".
  418. "\n".
  419. $this->generateOutput(' 3/3 [============================] 100%')."\n".
  420. rtrim($this->generateOutput(' 3 [--->------------------------]')).
  421. "\033[2A".
  422. "\n".
  423. "\n".
  424. rtrim($this->generateOutput(' 3 [============================]')),
  425. stream_get_contents($output->getStream())
  426. );
  427. }
  428. public function testWithoutMax()
  429. {
  430. $output = $this->getOutputStream();
  431. $bar = new ProgressBar($output);
  432. $bar->start();
  433. $bar->advance();
  434. $bar->advance();
  435. $bar->advance();
  436. $bar->finish();
  437. rewind($output->getStream());
  438. $this->assertEquals(
  439. rtrim(' 0 [>---------------------------]').
  440. rtrim($this->generateOutput(' 1 [->--------------------------]')).
  441. rtrim($this->generateOutput(' 2 [-->-------------------------]')).
  442. rtrim($this->generateOutput(' 3 [--->------------------------]')).
  443. rtrim($this->generateOutput(' 3 [============================]')),
  444. stream_get_contents($output->getStream())
  445. );
  446. }
  447. public function testAddingPlaceholderFormatter()
  448. {
  449. ProgressBar::setPlaceholderFormatterDefinition('remaining_steps', function (ProgressBar $bar) {
  450. return $bar->getMaxSteps() - $bar->getProgress();
  451. });
  452. $bar = new ProgressBar($output = $this->getOutputStream(), 3);
  453. $bar->setFormat(' %remaining_steps% [%bar%]');
  454. $bar->start();
  455. $bar->advance();
  456. $bar->finish();
  457. rewind($output->getStream());
  458. $this->assertEquals(
  459. ' 3 [>---------------------------]'.
  460. $this->generateOutput(' 2 [=========>------------------]').
  461. $this->generateOutput(' 0 [============================]'),
  462. stream_get_contents($output->getStream())
  463. );
  464. }
  465. public function testMultilineFormat()
  466. {
  467. $bar = new ProgressBar($output = $this->getOutputStream(), 3);
  468. $bar->setFormat("%bar%\nfoobar");
  469. $bar->start();
  470. $bar->advance();
  471. $bar->clear();
  472. $bar->finish();
  473. rewind($output->getStream());
  474. $this->assertEquals(
  475. ">---------------------------\nfoobar".
  476. $this->generateOutput("=========>------------------\nfoobar").
  477. "\x0D\x1B[2K\x1B[1A\x1B[2K".
  478. $this->generateOutput("============================\nfoobar"),
  479. stream_get_contents($output->getStream())
  480. );
  481. }
  482. public function testAnsiColorsAndEmojis()
  483. {
  484. $bar = new ProgressBar($output = $this->getOutputStream(), 15);
  485. ProgressBar::setPlaceholderFormatterDefinition('memory', function (ProgressBar $bar) {
  486. static $i = 0;
  487. $mem = 100000 * $i;
  488. $colors = $i++ ? '41;37' : '44;37';
  489. return "\033[".$colors.'m '.Helper::formatMemory($mem)." \033[0m";
  490. });
  491. $bar->setFormat(" \033[44;37m %title:-37s% \033[0m\n %current%/%max% %bar% %percent:3s%%\n 🏁 %remaining:-10s% %memory:37s%");
  492. $bar->setBarCharacter($done = "\033[32m●\033[0m");
  493. $bar->setEmptyBarCharacter($empty = "\033[31m●\033[0m");
  494. $bar->setProgressCharacter($progress = "\033[32m➤ \033[0m");
  495. $bar->setMessage('Starting the demo... fingers crossed', 'title');
  496. $bar->start();
  497. $bar->setMessage('Looks good to me...', 'title');
  498. $bar->advance(4);
  499. $bar->setMessage('Thanks, bye', 'title');
  500. $bar->finish();
  501. rewind($output->getStream());
  502. $this->assertEquals(
  503. " \033[44;37m Starting the demo... fingers crossed \033[0m\n".
  504. ' 0/15 '.$progress.str_repeat($empty, 26)." 0%\n".
  505. " \xf0\x9f\x8f\x81 < 1 sec \033[44;37m 0 B \033[0m"
  506. .
  507. $this->generateOutput(
  508. " \033[44;37m Looks good to me... \033[0m\n".
  509. ' 4/15 '.str_repeat($done, 7).$progress.str_repeat($empty, 19)." 26%\n".
  510. " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 97 KiB \033[0m"
  511. ).
  512. $this->generateOutput(
  513. " \033[44;37m Thanks, bye \033[0m\n".
  514. ' 15/15 '.str_repeat($done, 28)." 100%\n".
  515. " \xf0\x9f\x8f\x81 < 1 sec \033[41;37m 195 KiB \033[0m"
  516. ),
  517. stream_get_contents($output->getStream())
  518. );
  519. }
  520. public function testSetFormat()
  521. {
  522. $bar = new ProgressBar($output = $this->getOutputStream());
  523. $bar->setFormat('normal');
  524. $bar->start();
  525. rewind($output->getStream());
  526. $this->assertEquals(
  527. ' 0 [>---------------------------]',
  528. stream_get_contents($output->getStream())
  529. );
  530. $bar = new ProgressBar($output = $this->getOutputStream(), 10);
  531. $bar->setFormat('normal');
  532. $bar->start();
  533. rewind($output->getStream());
  534. $this->assertEquals(
  535. ' 0/10 [>---------------------------] 0%',
  536. stream_get_contents($output->getStream())
  537. );
  538. }
  539. /**
  540. * @dataProvider provideFormat
  541. */
  542. public function testFormatsWithoutMax($format)
  543. {
  544. $bar = new ProgressBar($output = $this->getOutputStream());
  545. $bar->setFormat($format);
  546. $bar->start();
  547. rewind($output->getStream());
  548. $this->assertNotEmpty(stream_get_contents($output->getStream()));
  549. }
  550. /**
  551. * Provides each defined format.
  552. *
  553. * @return array
  554. */
  555. public function provideFormat()
  556. {
  557. return array(
  558. array('normal'),
  559. array('verbose'),
  560. array('very_verbose'),
  561. array('debug'),
  562. );
  563. }
  564. protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL)
  565. {
  566. return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated);
  567. }
  568. protected function generateOutput($expected)
  569. {
  570. $count = substr_count($expected, "\n");
  571. return "\x0D\x1B[2K".($count ? str_repeat("\x1B[1A\x1B[2K", $count) : '').$expected;
  572. }
  573. }