Skip to content
Open
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
29 changes: 29 additions & 0 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ final class Config
*/
private static bool $preventStrayRequests = false;

/**
* Custom sleep handler used instead of usleep()
*
* @var callable|null
*/
private static mixed $sleepHandler = null;

/**
* Write a custom sender resolver
*/
Expand Down Expand Up @@ -107,4 +114,26 @@ public static function allowStrayRequests(): void
{
self::$preventStrayRequests = false;
}

/**
* Write a custom sleep handler
*
* The handler receives the number of microseconds to sleep for. Useful
* for skipping or observing sleeps in tests. Pass null to restore the
* default usleep() behaviour.
*/
public static function sleepUsing(?callable $sleepHandler): void
{
self::$sleepHandler = $sleepHandler;
}

/**
* Sleep for the given number of microseconds
*/
public static function sleep(int $microseconds): void
{
$sleepHandler = self::$sleepHandler;

is_callable($sleepHandler) ? $sleepHandler($microseconds) : usleep($microseconds);
}
}
5 changes: 4 additions & 1 deletion src/Http/Middleware/DelayMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\Http\Middleware;

use Saloon\Config;
use Saloon\Http\PendingRequest;
use Saloon\Contracts\RequestMiddleware;

Expand All @@ -16,6 +17,8 @@ public function __invoke(PendingRequest $pendingRequest): void
{
$delay = $pendingRequest->delay()->get() ?? 0;

usleep($delay * 1000);
if ($delay > 0) {
Config::sleep($delay * 1000);
}
}
}
3 changes: 2 additions & 1 deletion src/Traits/Connector/SendsRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Saloon\Traits\Connector;

use Saloon\Config;
use LogicException;
use Saloon\Http\Pool;
use Saloon\Http\Request;
Expand Down Expand Up @@ -58,7 +59,7 @@ public function send(Request $request, ?MockClient $mockClient = null, ?callable
? $retryInterval * (2 ** ($attempts - 2)) * 1000
: $retryInterval * 1000;

usleep($sleepTime);
Config::sleep($sleepTime);
}

try {
Expand Down
26 changes: 26 additions & 0 deletions tests/Feature/DelayRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

declare(strict_types=1);

use Saloon\Config;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
use Saloon\Tests\Fixtures\Requests\UserRequest;

afterEach(function () {
Config::sleepUsing(null);
});

test('async request delay works', function () {
$request = new UserRequest;
$request->delay()->set(1000);
Expand Down Expand Up @@ -96,3 +101,24 @@
$connector->send($request);
expect(round(microtime(true) - $start))->toBeGreaterThanOrEqual(1);
});

test('the delay is sent through the custom sleep handler when one is defined', function () {
$microseconds = [];

Config::sleepUsing(function (int $duration) use (&$microseconds) {
$microseconds[] = $duration;
});

$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam']),
]);

$request = new UserRequest;
$request->delay()->set(1000);

$start = microtime(true);
connector()->send($request, $mockClient);

expect(microtime(true) - $start)->toBeLessThan(1);
expect($microseconds)->toEqual([1_000_000]);
});
28 changes: 28 additions & 0 deletions tests/Feature/RetryRequestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

declare(strict_types=1);

use Saloon\Config;
use Saloon\Http\Request;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;
Expand All @@ -13,6 +14,10 @@
use Saloon\Tests\Fixtures\Requests\HeaderErrorRetryRequest;
use Saloon\Exceptions\Request\Statuses\InternalServerErrorException;

afterEach(function () {
Config::sleepUsing(null);
});

test('a failed request can be retried', function () {
$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam'], 500),
Expand Down Expand Up @@ -108,6 +113,29 @@
expect(round(microtime(true) - $start))->toBeGreaterThanOrEqual(2);
});

test('the interval between attempts is sent through the custom sleep handler when one is defined', function () {
$microseconds = [];

Config::sleepUsing(function (int $duration) use (&$microseconds) {
$microseconds[] = $duration;
});

$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam'], 500),
MockResponse::make(['name' => 'Gareth'], 500),
MockResponse::make(['name' => 'Teodor'], 200),
]);

$connector = new TestConnector;
$connector->withMockClient($mockClient);

$connector->send(new RetryUserRequest(3, 200));

expect($microseconds)->toEqual([200_000, 200_000]);

$mockClient->assertSentCount(3);
});

test('an exception other than a request exception will not be retried', function () {
$mockClient = new MockClient([
MockResponse::make(['name' => 'Sam'], 500),
Expand Down
13 changes: 13 additions & 0 deletions tests/Unit/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
afterEach(function () {
Config::clearGlobalMiddleware();
Config::$defaultSender = GuzzleSender::class;
Config::sleepUsing(null);
});

test('the config can specify global middleware', function () {
Expand Down Expand Up @@ -100,3 +101,15 @@

Config::clearGlobalMiddleware();
});

test('the config can specify a custom sleep handler', function () {
$microseconds = [];

Config::sleepUsing(function (int $duration) use (&$microseconds) {
$microseconds[] = $duration;
});

Config::sleep(50_000);

expect($microseconds)->toEqual([50_000]);
});