Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[ProgressBar] Empty iterable throws Exception on "maximum number of steps is not set" #47259

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 14 additions & 6 deletions src/Symfony/Component/Console/Helper/ProgressBar.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ final class ProgressBar
private OutputInterface $output;
private int $step = 0;
private int $startingStep = 0;
private ?int $max = null;
private int $max = 0;
private bool $isMaxKnown = false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this new variable? I suppose 0 is not a valid max, so having max at 0 probably means that it is not known?

Copy link
Contributor Author

@freimair freimair Aug 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the whole point of this PR and the issue I found with Progessbars is that max = 0 IS a valid max value but it is treated like it is not.

we stumbled over that behavior because we need to do a lot of import/sync operations, nasty but necessary. Once in a while, one of our imports/sync iterables is empty and thus max value = 0. foreach([] as ...) does not throw errors in vanilla PHP. Using the progressbar with foreach($bar->iterate([]) as ...) does.

private int $startTime;
private int $stepWidth;
private float $percent = 0.0;
Expand Down Expand Up @@ -195,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
Expand All @@ -209,7 +210,7 @@ public function getEstimated(): float

public function getRemaining(): float
{
if (!$this->step) {
if (!$this->step || $this->step === $this->startingStep) {
return 0;
}

Expand All @@ -233,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)
Expand Down Expand Up @@ -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) {
Expand All @@ -308,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;
Expand Down Expand Up @@ -352,6 +357,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
Expand All @@ -377,6 +384,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;
}

/**
Expand Down Expand Up @@ -519,14 +527,14 @@ private static function initPlaceholderFormatters(): array
return Helper::formatTime(time() - $bar->getStartTime());
},
'remaining' => function (self $bar) {
if (!$bar->getMaxSteps()) {
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 (!$bar->getMaxSteps()) {
if (!$bar->isMaxKnown && !$bar->getMaxSteps()) {
throw new LogicException('Unable to display the estimated time if the maximum number of steps is not set.');
}

Expand Down
24 changes: 24 additions & 0 deletions src/Symfony/Component/Console/Tests/Helper/ProgressBarTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1037,6 +1038,29 @@ public function testIterateUncountable()
);
}

public function testEmptyInputWithDebugFormat()
{
try {
$bar = new ProgressBar($output = $this->getOutputStream());
$bar->setFormat('%current%/%max% [%bar%] %percent:3s%% %elapsed:6s%/%estimated:-6s%');

$input = [];
foreach ($bar->iterate($input) as $item) {
var_dump($item);
}

// succeed the test if we reach this code (no exception happened)
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();
}
}

protected function getOutputStream($decorated = true, $verbosity = StreamOutput::VERBOSITY_NORMAL)
{
return new StreamOutput(fopen('php://memory', 'r+', false), $verbosity, $decorated);
Expand Down