123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- <?php
- namespace STS\Backoff;
- use Exception;
- use PHPUnit\Framework\TestCase;
- use STS\Backoff\Strategies\ConstantStrategy;
- use STS\Backoff\Strategies\ExponentialStrategy;
- use STS\Backoff\Strategies\LinearStrategy;
- use STS\Backoff\Strategies\PolynomialStrategy;
- class BackoffTest extends TestCase
- {
- public function testDefaults()
- {
- $b = new Backoff();
- $this->assertEquals(5, $b->getMaxAttempts());
- $this->assertInstanceOf(PolynomialStrategy::class, $b->getStrategy());
- $this->assertFalse($b->jitterEnabled());
- }
- public function testFluidApi()
- {
- $b = new Backoff();
- $result = $b
- ->setStrategy('constant')
- ->setMaxAttempts(10)
- ->setWaitCap(5)
- ->enableJitter();
- $this->assertEquals(10, $b->getMaxAttempts());
- $this->assertEquals(5, $b->getWaitCap());
- $this->assertTrue($b->jitterEnabled());
- $this->assertInstanceOf(ConstantStrategy::class, $b->getStrategy());
- }
- public function testChangingStaticDefaults()
- {
- Backoff::$defaultMaxAttempts = 15;
- Backoff::$defaultStrategy = "constant";
- Backoff::$defaultJitterEnabled = true;
- $b = new Backoff();
- $this->assertEquals(15, $b->getMaxAttempts());
- $this->assertInstanceOf(ConstantStrategy::class, $b->getStrategy());
- $this->assertTrue($b->jitterEnabled());
- Backoff::$defaultStrategy = new LinearStrategy(250);
- $b = new Backoff();
- $this->assertInstanceOf(LinearStrategy::class, $b->getStrategy());
- // Put them back!
- Backoff::$defaultMaxAttempts = 5;
- Backoff::$defaultStrategy = "polynomial";
- Backoff::$defaultJitterEnabled = false;
- }
- public function testConstructorParams()
- {
- $b = new Backoff(10, "linear");
- $this->assertEquals(10, $b->getMaxAttempts());
- $this->assertInstanceOf(LinearStrategy::class, $b->getStrategy());
- }
- public function testStrategyKeys()
- {
- $b = new Backoff();
- $b->setStrategy("constant");
- $this->assertInstanceOf(ConstantStrategy::class, $b->getStrategy());
- $b->setStrategy("linear");
- $this->assertInstanceOf(LinearStrategy::class, $b->getStrategy());
- $b->setStrategy("polynomial");
- $this->assertInstanceOf(PolynomialStrategy::class, $b->getStrategy());
- $b->setStrategy("exponential");
- $this->assertInstanceOf(ExponentialStrategy::class, $b->getStrategy());
- }
- public function testStrategyInstances()
- {
- $b = new Backoff();
- $b->setStrategy(new ConstantStrategy());
- $this->assertInstanceOf(ConstantStrategy::class, $b->getStrategy());
- $b->setStrategy(new LinearStrategy());
- $this->assertInstanceOf(LinearStrategy::class, $b->getStrategy());
- $b->setStrategy(new PolynomialStrategy());
- $this->assertInstanceOf(PolynomialStrategy::class, $b->getStrategy());
- $b->setStrategy(new ExponentialStrategy());
- $this->assertInstanceOf(ExponentialStrategy::class, $b->getStrategy());
- }
- public function testClosureStrategy()
- {
- $b = new Backoff();
- $strategy = function () {
- return "hi there";
- };
- $b->setStrategy($strategy);
- $this->assertEquals("hi there", call_user_func($b->getStrategy()));
- }
- public function testIntegerReturnsConstantStrategy()
- {
- $b = new Backoff();
- $b->setStrategy(500);
- $this->assertInstanceOf(ConstantStrategy::class, $b->getStrategy());
- }
- public function testInvalidStrategy()
- {
- $b = new Backoff();
- $this->expectException(\InvalidArgumentException::class);
- $b->setStrategy("foo");
- }
- public function testWaitTimes()
- {
- $b = new Backoff(1, "linear");
- $this->assertEquals(100, $b->getStrategy()->getBase());
- $this->assertEquals(100, $b->getWaitTime(1));
- $this->assertEquals(200, $b->getWaitTime(2));
- }
- public function testWaitCap()
- {
- $b = new Backoff(1, new LinearStrategy(5000));
- $this->assertEquals(10000, $b->getWaitTime(2));
- $b->setWaitCap(5000);
- $this->assertEquals(5000, $b->getWaitTime(2));
- }
- public function testWait()
- {
- $b = new Backoff(1, new LinearStrategy(50));
- $start = microtime(true);
- $b->wait(2);
- $end = microtime(true);
- $elapsedMS = ($end - $start) * 1000;
- // We expect that this took just barely over the 100ms we asked for
- $this->assertTrue($elapsedMS > 90 && $elapsedMS < 150,
- sprintf("Expected elapsedMS between 100 & 110, got: $elapsedMS\n"));
- }
- public function testSuccessfulWork()
- {
- $b = new Backoff();
- $result = $b->run(function () {
- return "done";
- });
- $this->assertEquals("done", $result);
- }
- public function testFirstAttemptDoesNotCallStrategy()
- {
- $b = new Backoff();
- $b->setStrategy(function () {
- throw new \Exception("We shouldn't be here");
- });
- $result = $b->run(function () {
- return "done";
- });
- $this->assertEquals("done", $result);
- }
- public function testFailedWorkReThrowsException()
- {
- $b = new Backoff(2, new ConstantStrategy(0));
- $this->expectException(\Exception::class);
- $this->expectExceptionMessage("failure");
- $b->run(function () {
- throw new \Exception("failure");
- });
- }
- public function testHandleErrorsPhp7()
- {
- $b = new Backoff(2, new ConstantStrategy(0));
- $this->expectException(\Exception::class);
- $this->expectExceptionMessage("Modulo by zero");
- $b->run(function () {
- if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
- return 1 % 0;
- } else {
- // Handle version < 7
- throw new Exception("Modulo by zero");
- }
- });
- }
- public function testAttempts()
- {
- $b = new Backoff(10, new ConstantStrategy(0));
- $attempt = 0;
- $result = $b->run(function () use (&$attempt) {
- $attempt++;
- if ($attempt < 5) {
- throw new \Exception("failure");
- }
- return "success";
- });
- $this->assertEquals(5, $attempt);
- $this->assertEquals("success", $result);
- }
- public function testCustomDeciderAttempts()
- {
- $b = new Backoff(10, new ConstantStrategy(0));
- $b->setDecider(
- function ($retry, $maxAttempts, $result = null, $exception = null) {
- if ($retry >= $maxAttempts || $result == "success") {
- return false;
- }
- return true;
- }
- );
- $attempt = 0;
- $result = $b->run(function () use (&$attempt) {
- $attempt++;
- if ($attempt < 5) {
- throw new \Exception("failure");
- }
- if ($attempt < 7) {
- return 'not yet';
- }
- return "success";
- });
- $this->assertEquals(7, $attempt);
- $this->assertEquals("success", $result);
- }
- public function testErrorHandler()
- {
- $log = [];
- $b = new Backoff(10, new ConstantStrategy(0));
- $b->setErrorHandler(function($exception, $attempt, $maxAttempts) use(&$log) {
- $log[] = "Attempt $attempt of $maxAttempts: " . $exception->getMessage();
- });
- $attempt = 0;
- $result = $b->run(function () use (&$attempt) {
- $attempt++;
- if ($attempt < 5) {
- throw new \Exception("failure");
- }
- return "success";
- });
- $this->assertEquals(4, count($log));
- $this->assertEquals("Attempt 4 of 10: failure", array_pop($log));
- $this->assertEquals("success", $result);
- }
- public function testJitter()
- {
- $b = new Backoff(10, new ConstantStrategy(1000));
- // First without jitter
- $this->assertEquals(1000, $b->getWaitTime(1));
- // Now with jitter
- $b->enableJitter();
- // Because it's still possible that I could get 1000 back even with jitter, I'm going to generate two
- $waitTime1 = $b->getWaitTime(1);
- $waitTime2 = $b->getWaitTime(1);
- // And I'm banking that I didn't hit the _extremely_ rare chance that both were randomly chosen to be 1000 still
- $this->assertTrue($waitTime1 < 1000 || $waitTime2 < 1000);
- }
- }
|