diff --git a/src/ExponentialBackoff.php b/src/ExponentialBackoff.php index f6dff09..fcdb5b4 100644 --- a/src/ExponentialBackoff.php +++ b/src/ExponentialBackoff.php @@ -33,6 +33,8 @@ */ class ExponentialBackoff { + public const DEFAULT_MAX_ATTEMPTS = 4; + public const TYPE_MICROSECONDS = 1; public const TYPE_SECONDS = 2; @@ -54,7 +56,7 @@ class ExponentialBackoff /** * @var int */ - protected $maxAttempts = 4; + protected $maxAttempts = self::DEFAULT_MAX_ATTEMPTS; /** * @var int @@ -176,17 +178,17 @@ public function setRetryCondition(AbstractRetryCondition $retryCondition): self */ protected function retry($result, ?\Exception $e): bool { - if ($this->getCurrentAttempts() < $this->getMaxAttempts()) { - if ($this->getRetryCondition()->met($result, $e)) { - return false; - } - - $this->sleep(); + if ($this->getRetryCondition()->met($result, $e)) { + return false; + } - return true; + if ($this->getCurrentAttempts() >= $this->getMaxAttempts()) { + return false; } - return false; + $this->sleep(); + + return true; } /** diff --git a/tests/unit/CustomizedConditionTest.php b/tests/unit/CustomizedConditionTest.php index 67cb320..e0d77a7 100644 --- a/tests/unit/CustomizedConditionTest.php +++ b/tests/unit/CustomizedConditionTest.php @@ -32,32 +32,40 @@ */ class CustomizedConditionTest extends TestCase { + protected const MAX_ATTEMPTS = ExponentialBackoff::DEFAULT_MAX_ATTEMPTS; + + public function dataBackoff(): array + { + return [ + [ + 'maxAttempts' => 1, + 'message' => 'Maximum # of attempts is 1 (exponential backoff disabled)', + ], + [ + 'maxAttempts' => 2, + 'message' => 'Maximum # of attempts is 2', + ], + [ + 'maxAttempts' => self::MAX_ATTEMPTS, + 'message' => 'Maximum # of attempts is 4', + ], + ]; + } + /** * The $backoff object in this test is the same as the one in the next method self::testUnthrowableException(), * except that method $backoff->throwable() returns TRUE. * + * @dataProvider dataBackoff * @covers \CrowdStar\Backoff\AbstractRetryCondition::throwable() * @covers \CrowdStar\Backoff\ExponentialBackoff::run() */ - public function testThrowableException() + public function testThrowableException(int $maxAttempts) { - $helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(4); - $backoff = (new ExponentialBackoff( - new class extends AbstractRetryCondition { - public function throwable(): bool - { - // This tells the caller to throw out the exception when finally failed. - return true; - } - public function met($result, ?Exception $e): bool - { - return (empty($e) || (!($e instanceof Exception))); - } - } - )); + $helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(self::MAX_ATTEMPTS); $this->expectException(Exception::class); // Next function call will through out an exception. - $backoff->run( + $this->getBackoff($maxAttempts, false)->run( function () use ($helper) { return $helper->getValueAfterExpectedNumberOfFailedAttemptsWithExceptionsThrownOut(); } @@ -68,31 +76,52 @@ function () use ($helper) { * The $backoff object in this test is the same as the one in the previous method self::testThrowableException(), * except that method $backoff->throwable() returns FALSE. * + * @dataProvider dataBackoff * @covers \CrowdStar\Backoff\AbstractRetryCondition::throwable() * @covers \CrowdStar\Backoff\ExponentialBackoff::run() */ - public function testUnthrowableException() + public function testUnthrowableException(int $maxAttempts) + { + $helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(self::MAX_ATTEMPTS); + $this->getBackoff($maxAttempts, true)->run( + function () use ($helper) { + return $helper->getValueAfterExpectedNumberOfFailedAttemptsWithExceptionsThrownOut(); + } + ); + + $this->addToAssertionCount(1); // Since there is no assertions in this test, we manually add the count by 1. + } + + /** + * @param bool $silenceWhenFailed To hide or throw out the exception when finally failed. + */ + protected function getBackoff(int $maxAttempts, bool $silenceWhenFailed): ExponentialBackoff { - $helper = (new Helper())->setException(Exception::class)->setExpectedFailedAttempts(4); $backoff = (new ExponentialBackoff( - new class extends AbstractRetryCondition { + new class($silenceWhenFailed) extends AbstractRetryCondition { + protected $silenceWhenFailed; + protected $throwable = true; + public function __construct(bool $silenceWhenFailed) + { + $this->silenceWhenFailed = $silenceWhenFailed; + } public function throwable(): bool { - // This tells the caller NOT to throw out the exception when finally failed. - return false; + // This tells the caller to hide or throw out the exception when finally failed. + return $this->throwable; } public function met($result, ?Exception $e): bool { - return (empty($e) || (!($e instanceof Exception))); + if (empty($e) || (!($e instanceof Exception))) { + return true; + } + $this->throwable = !$this->silenceWhenFailed; + return false; } } )); + $backoff->setMaxAttempts($maxAttempts); - $this->addToAssertionCount(1); // Since there is no assertions in this test, we manually add the count by 1. - $backoff->run( - function () use ($helper) { - return $helper->getValueAfterExpectedNumberOfFailedAttemptsWithExceptionsThrownOut(); - } - ); + return $backoff; } }