From 010af2aa1a1d9776bd47393e50636d254c420e73 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 12 Aug 2022 09:58:36 +0200 Subject: [PATCH 1/6] Fix: ProgressBar does not fail on emtpy Iterable --- .../Component/Console/Helper/ProgressBar.php | 4 ++-- .../Console/Tests/Helper/ProgressBarTest.php | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 4c883ac04bc9d..8b5d77c7746ce 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -519,14 +519,14 @@ private static function initPlaceholderFormatters(): array return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (!isset($bar->max)) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining()); }, 'estimated' => function (self $bar) { - if (!$bar->getMaxSteps()) { + if (!isset($bar->max)) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index f9a74ae761879..c2cce0aaab9b0 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -12,6 +12,7 @@ namespace Symfony\Component\Console\Tests\Helper; use PHPUnit\Framework\TestCase; +use Symfony\Component\Config\Definition\Exception\Exception; use Symfony\Component\Console\Formatter\OutputFormatter; use Symfony\Component\Console\Helper\Helper; use Symfony\Component\Console\Helper\ProgressBar; @@ -1037,6 +1038,23 @@ public function testIterateUncountable() ); } + public function testEmptyInputWithDebugFormat() + { + try { + $bar = new ProgressBar($output = $this->getOutputStream()); + $bar->setFormat("%elapsed%/%estimated%"); + + $input = []; + foreach ($bar->iterate($input) as $item) + var_dump($item); + + // succeed the test if we reach this code (no exception happened) + $this->assertTrue(true); + } catch(Exception $e) { + $this->fail(); + } + } + protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL) { return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated); From 50d6cd18c7fba0b7f619eb5808308e6bc5d74755 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Fri, 12 Aug 2022 09:59:10 +0200 Subject: [PATCH 2/6] Fix: divide by Zero chance in ProgressBar --- src/Symfony/Component/Console/Helper/ProgressBar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 8b5d77c7746ce..a5b917da540ca 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -209,7 +209,7 @@ public function getEstimated(): float public function getRemaining(): float { - if (!$this->step) { + if (!$this->step || $this->step === $this->startingStep) { return 0; } From eac82fc22a299ee22defe44beb03e614c674065d Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 23 Aug 2022 09:42:08 +0200 Subject: [PATCH 3/6] Set max to int since it cannot be null `max` value has been prepared to contain a `null` value, however, the constructor with its default value of `max = 0` prevents `$max` to ever be `null`. Hence, declare `$max` as `int` instead of `?int`. --- src/Symfony/Component/Console/Helper/ProgressBar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index a5b917da540ca..846b8e157049b 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -50,7 +50,7 @@ final class ProgressBar private OutputInterface $output; private int $step = 0; private int $startingStep = 0; - private ?int $max = null; + private int $max = 0; private int $startTime; private int $stepWidth; private float $percent = 0.0; From 97018638878754814320dd926aaadba7c5f9714e Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 23 Aug 2022 09:42:35 +0200 Subject: [PATCH 4/6] Introduce isMaxKnown variable Up until now, it has been very hard to determin whether a progressbar has a known maximum value or not, because the property `max` is an integer. Up until now, `max == 0` has been treated like we do _not_ know the `max` value. However, `max == 0` _is_ a valid `max` value when iterating over an empty but countable array for example. We cannot simply keep `max == null` if unknown, because that would break rendering mechanics and fixing those would require interface changes. Hence, enter `isMaxKnown` - a property which keeps track of whether the `max` value is known or not. --- src/Symfony/Component/Console/Helper/ProgressBar.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 846b8e157049b..f936655352e10 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -51,6 +51,7 @@ final class ProgressBar private int $step = 0; private int $startingStep = 0; private int $max = 0; + private bool $isMaxKnown = false; private int $startTime; private int $stepWidth; private float $percent = 0.0; @@ -289,6 +290,8 @@ public function maxSecondsBetweenRedraws(float $seconds): void */ public function iterate(iterable $iterable, int $max = null): iterable { + $this->isMaxKnown = is_countable($iterable); + $this->start($max ?? (is_countable($iterable) ? \count($iterable) : 0)); foreach ($iterable as $key => $value) { @@ -377,6 +380,7 @@ public function setMaxSteps(int $max) $this->format = null; $this->max = max(0, $max); $this->stepWidth = $this->max ? Helper::width((string) $this->max) : 4; + $this->isMaxKnown = $this->isMaxKnown || 0 < $this->max; } /** @@ -519,14 +523,14 @@ private static function initPlaceholderFormatters(): array return Helper::formatTime(time() - $bar->getStartTime()); }, 'remaining' => function (self $bar) { - if (!isset($bar->max)) { + if (!$bar->isMaxKnown && !$bar->getMaxSteps()) { throw new LogicException('Unable to display the remaining time if the maximum number of steps is not set.'); } return Helper::formatTime($bar->getRemaining()); }, 'estimated' => function (self $bar) { - if (!isset($bar->max)) { + if (!$bar->isMaxKnown && !$bar->getMaxSteps()) { throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.'); } From d0cc3bd682744dd0ad710a74b4aae98d49047323 Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Tue, 23 Aug 2022 09:54:26 +0200 Subject: [PATCH 5/6] Ui work --- .../Component/Console/Helper/ProgressBar.php | 6 ++++-- .../Console/Tests/Helper/ProgressBarTest.php | 14 ++++++++++---- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index f936655352e10..30238709c9ee0 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -196,7 +196,7 @@ public function getProgressPercent(): float public function getBarOffset(): float { - return floor($this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); + return floor($this->isMaxKnown || $this->max ? $this->percent * $this->barWidth : (null === $this->redrawFreq ? (int) (min(5, $this->barWidth / 15) * $this->writeCount) : $this->step) % $this->barWidth); } public function getEstimated(): float @@ -234,7 +234,7 @@ public function setBarCharacter(string $char) public function getBarCharacter(): string { - return $this->barChar ?? ($this->max ? '=' : $this->emptyBarChar); + return $this->barChar ?? ($this->isMaxKnown || $this->max ? '=' : $this->emptyBarChar); } public function setEmptyBarCharacter(string $char) @@ -355,6 +355,8 @@ public function setProgress(int $step) $currPeriod = (int) ($step / $redrawFreq); $this->step = $step; $this->percent = $this->max ? (float) $this->step / $this->max : 0; + if ($this->isMaxKnown && 0 == $this->max) + $this->percent = 1.0; $timeInterval = microtime(true) - $this->lastWriteTime; // Draw regardless of other limits diff --git a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php index c2cce0aaab9b0..dea640ffd22a8 100644 --- a/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php +++ b/src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php @@ -1042,15 +1042,21 @@ public function testEmptyInputWithDebugFormat() { try { $bar = new ProgressBar($output = $this->getOutputStream()); - $bar->setFormat("%elapsed%/%estimated%"); + $bar->setFormat('%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%'); $input = []; - foreach ($bar->iterate($input) as $item) + foreach ($bar->iterate($input) as $item) { var_dump($item); + } // succeed the test if we reach this code (no exception happened) - $this->assertTrue(true); - } catch(Exception $e) { + rewind($output->getStream()); + $this->assertEquals( + ' 0/0 [>---------------------------] 0% < 1 sec/< 1 sec' . + $this->generateOutput(' 0/0 [============================] 100% < 1 sec/< 1 sec'), + stream_get_contents($output->getStream()) + ); + } catch (Exception $e) { $this->fail(); } } From 42a088492ae3d3032c6bea385af71603b89104ea Mon Sep 17 00:00:00 2001 From: Florian Reimair Date: Mon, 23 Oct 2023 16:12:07 +0200 Subject: [PATCH 6/6] Set maxKnown also in start function --- src/Symfony/Component/Console/Helper/ProgressBar.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Symfony/Component/Console/Helper/ProgressBar.php b/src/Symfony/Component/Console/Helper/ProgressBar.php index 30238709c9ee0..f6b3323d59f4d 100644 --- a/src/Symfony/Component/Console/Helper/ProgressBar.php +++ b/src/Symfony/Component/Console/Helper/ProgressBar.php @@ -311,6 +311,8 @@ public function iterate(iterable $iterable, int $max = null): iterable */ public function start(int $max = null, int $startAt = 0): void { + $this->isMaxKnown = !is_null($max); + $this->startTime = time(); $this->step = $startAt; $this->startingStep = $startAt;