Files
2026-03-09 13:30:46 -07:00

154 lines
4.4 KiB
PHP

<?php
/*
* This file is part of the Predis package.
*
* (c) 2009-2020 Daniele Alessandri
* (c) 2021-2026 Till Krüss
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Predis\Retry;
use PHPUnit\Framework\TestCase;
use Predis\Connection\ConnectionException;
use Predis\Connection\NodeConnectionInterface;
use Predis\Connection\Resource\Exception\StreamInitException;
use Predis\Retry\Strategy\EqualBackoff;
use Predis\Retry\Strategy\ExponentialBackoff;
use Predis\Retry\Strategy\NoBackoff;
use Predis\Retry\Strategy\RetryStrategyInterface;
use RuntimeException;
use Throwable;
class RetryTest extends TestCase
{
/**
* @group disconnected
* @dataProvider strategyProvider
* @throws Throwable
*/
public function testCallWithRetry(
RetryStrategyInterface $backoffStrategy,
int $retries,
float $expectedExecutionTime,
float $delta
) {
$retry = new Retry($backoffStrategy, $retries);
$retriesCount = 0;
$callable = static function () use (&$retriesCount, $retries) {
if ($retriesCount >= $retries) {
return;
}
++$retriesCount;
throw new StreamInitException();
};
$startTime = microtime(true);
$retry->callWithRetry($callable);
$executionTime = microtime(true) - $startTime;
$this->assertEquals($retriesCount, $retries);
$this->assertEqualsWithDelta($expectedExecutionTime, $executionTime, $delta);
$retry->updateRetriesCount(10);
$this->assertEquals(10, $retry->getRetries());
}
/**
* @group disconnected
* @return void
*/
public function testNoRetriesOnExcludedRetryableExceptions()
{
$retry = new Retry(new NoBackoff(), 3, [ConnectionException::class]);
$retriesCount = 0;
$callCount = 0;
$doCallable = function () use (&$callCount) {
++$callCount;
if ($callCount <= 3) {
throw new RuntimeException();
} elseif ($callCount <= 7) {
throw new ConnectionException(
$this->getMockBuilder(NodeConnectionInterface::class)->getMock()
);
}
throw new StreamInitException();
};
$failCallable = static function () use (&$retriesCount) {
++$retriesCount;
};
// Ensures that no retries happens on excluded exception.
while ($callCount < 3) {
try {
$retry->callWithRetry($doCallable, $failCallable);
} catch (Throwable $e) {
$this->assertInstanceOf(RuntimeException::class, $e);
$this->assertEquals(0, $retriesCount);
}
}
// Ensures that retries happens on specified exception.
try {
$retry->callWithRetry($doCallable, $failCallable);
} catch (Throwable $e) {
$this->assertInstanceOf(ConnectionException::class, $e);
$this->assertEquals(3, $retriesCount);
}
$retry->updateCatchableExceptions([StreamInitException::class]);
// Ensures that retries happens on updated catchable exceptions.
try {
$retry->callWithRetry($doCallable, $failCallable);
} catch (Throwable $e) {
$this->assertInstanceOf(StreamInitException::class, $e);
$this->assertEquals(6, $retriesCount);
}
$this->assertEquals(11, $callCount);
}
public function strategyProvider(): array
{
return [
'NoBackoff' => [
new NoBackoff(),
3,
1,
1,
],
'EqualBackoff' => [
new EqualBackoff(0.3 * 1000000),
3,
0.9,
0.1,
],
'ExponentialBackoff - no jitter' => [
new ExponentialBackoff(),
3,
0.112,
0.08,
],
'ExponentialBackoff - with jitter' => [
new ExponentialBackoff(
RetryStrategyInterface::DEFAULT_BASE,
RetryStrategyInterface::DEFAULT_CAP,
true
),
3,
0.112,
0.112, // Theoretically, jitter==0 might happen sequentially 3 times
],
];
}
}