ProgressBarTest.php 21 KB

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