Skip to content

Feature: specify flaky exceptions #5

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

3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
},
"require-dev": {
"mockery/mockery": "^1.3.3",
"phpunit/phpunit": ">=8.5.23|^9"
"phpunit/phpunit": ">=8.5.23|^9",
"orchestra/testbench": "^6.0|^7.0|^8.0"
},
"autoload": {
"psr-4": {
Expand Down
12 changes: 2 additions & 10 deletions src/Arbiter.php
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,8 @@ public function __construct($id)
};
}

public function handle($exception, $bypassProtections = false)
public function handle($exception)
{
$failed = !is_null($exception);

if ($failed && $bypassProtections) {
$this->callHandler($exception);

return;
}

$this->deadline = $this->deadline ?? $this->freshDeadline();

if ($exception) {
Expand All @@ -66,7 +58,7 @@ public function handle($exception, $bypassProtections = false)

$this->updateCachedStats($exception);

if ($failed && $this->outOfBounds()) {
if (!is_null($exception) && $this->outOfBounds()) {
$this->callHandler($exception);
}
}
Expand Down
30 changes: 29 additions & 1 deletion src/Flaky.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace Hammerstone\Flaky;

use Exception;
use Illuminate\Support\Traits\Macroable;
use Throwable;

Expand All @@ -20,6 +21,8 @@ class Flaky

protected $flakyProtectionDisabled = false;

protected $flakyExceptions;

public static function make($id)
{
return new static($id);
Expand Down Expand Up @@ -52,7 +55,11 @@ public function run(callable $callable)
$exception = $e;
}

$this->arbiter->handle($exception, $this->protectionsBypassed());
if ($this->shouldThrowImmediately($exception)) {
throw $exception;
}

$this->arbiter->handle($exception);

return new Result($value, $exception);
}
Expand Down Expand Up @@ -173,6 +180,13 @@ public function allowTotalFailures($failures)
return $this;
}

public function forExceptions(array $exceptions)
{
$this->flakyExceptions = $exceptions;

return $this;
}

protected function protectionsBypassed()
{
return static::$disabledGlobally || $this->flakyProtectionDisabled;
Expand Down Expand Up @@ -200,4 +214,18 @@ protected function normalizeRetryWhen($when = null)

return $when;
}

protected function shouldThrowImmediately(Exception $exception = null)
{
if (is_null($exception)) {
return false;
}

return $this->protectionsBypassed() || !$this->exceptionIsFlaky($exception);
}

protected function exceptionIsFlaky(Exception $exception = null)
{
return is_null($this->flakyExceptions) || in_array(get_class($exception), $this->flakyExceptions, true);
}
}
56 changes: 56 additions & 0 deletions tests/Unit/BasicTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,44 @@ public function report(Throwable $e)
$this->assertInstanceOf(Exception::class, $handler->reported);
}

/** @test */
public function throws_for_unset_specific_exceptions()
{
$this->expectException(Exception::class);

Carbon::setTestNow();

// We've specified a flaky exception, but we will throw another, so it should throw.
$flaky = Flaky::make(__FUNCTION__)->forExceptions([SpecificException::class])->allowFailuresForSeconds(60);

$result = $flaky->run(function () {
throw new Exception();
});
}

/** @test */
public function does_not_throws_for_specific_exceptions()
{
Carbon::setTestNow();

$flaky = Flaky::make(__FUNCTION__)->forExceptions([SpecificException::class])->allowFailuresForSeconds(60);

// Should not throw, since it is the first occurrence of a defined flaky exception.
$result = $flaky->run(function () {
throw new SpecificException();
});

$this->assertTrue($result->failed);

Carbon::setTestNow(now()->addSeconds(61));

$this->expectException(SpecificException::class);

$flaky->run(function () {
throw new SpecificException();
});
}

/** @test */
public function can_disable()
{
Expand Down Expand Up @@ -185,4 +223,22 @@ public function can_pass_in_our_own_exception()

$this->assertInstanceOf(Result::class, $result);
}

/** @test */
public function it_does_not_throw_for_non_exceptions_when_protections_are_bypassed()
{
$result = Flaky::make(__FUNCTION__)
->allowFailuresForADay()
->disableFlakyProtection()
->run(function () {
return 1;
});

$this->assertEquals(1, $result->value);
}
}

class SpecificException extends \Exception
{

}