From aec881af792b3d09fc927d9cb5d35405f307496f Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 08:13:40 +0200 Subject: [PATCH 01/14] Added Timebox support class --- src/Illuminate/Auth/SessionGuard.php | 15 +++++++---- src/Illuminate/Support/Timebox.php | 40 ++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 5 deletions(-) create mode 100644 src/Illuminate/Support/Timebox.php diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 652835f..8e0a9c9 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -20,6 +20,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; +use Illuminate\Support\Timebox; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; use RuntimeException; @@ -423,13 +424,17 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe */ protected function hasValidCredentials($user, $credentials) { - $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); + return (new Timebox)(function ($timebox) use ($user, $credentials) { + $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); - if ($validated) { - $this->fireValidatedEvent($user); - } + if ($validated) { + $timebox->returnEarly(); + + $this->fireValidatedEvent($user); + } - return $validated; + return $validated; + }, 200000); } /** diff --git a/src/Illuminate/Support/Timebox.php b/src/Illuminate/Support/Timebox.php new file mode 100644 index 0000000..faff146 --- /dev/null +++ b/src/Illuminate/Support/Timebox.php @@ -0,0 +1,40 @@ +earlyReturn && microtime(true) - $start < $microseconds / 1000000) { + usleep(5); + } + + return $result; + } + + public function returnEarly(): self + { + $this->earlyReturn = true; + + return $this; + } + + public function dontReturnEarly(): self + { + $this->earlyReturn = false; + + return $this; + } +} From 581d4c079cd578f5e84294ea5624165b7b4da44a Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 11:23:47 +0200 Subject: [PATCH 02/14] Refactored timebox from invokeable to facade --- src/Illuminate/Auth/SessionGuard.php | 4 ++-- src/Illuminate/Support/Facades/Timebox.php | 22 ++++++++++++++++++++++ src/Illuminate/Support/Timebox.php | 10 ++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) create mode 100755 src/Illuminate/Support/Facades/Timebox.php diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 8e0a9c9..65bc47c 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -20,7 +20,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; -use Illuminate\Support\Timebox; +use Illuminate\Support\Facades\Timebox; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; use RuntimeException; @@ -424,7 +424,7 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe */ protected function hasValidCredentials($user, $credentials) { - return (new Timebox)(function ($timebox) use ($user, $credentials) { + return Timebox::make(function ($timebox) use ($user, $credentials) { $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); if ($validated) { diff --git a/src/Illuminate/Support/Facades/Timebox.php b/src/Illuminate/Support/Facades/Timebox.php new file mode 100755 index 0000000..3123916 --- /dev/null +++ b/src/Illuminate/Support/Facades/Timebox.php @@ -0,0 +1,22 @@ +earlyReturn && microtime(true) - $start < $microseconds / 1000000) { - usleep(5); + $remainder = $microseconds - ((microtime(true) - $start) * 1000000); + + if (! $this->earlyReturn && $remainder > 0) { + usleep($remainder); } return $result; From 99d3e6ac978e5b0f05fb638215ba314206d41983 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 16:31:33 +0200 Subject: [PATCH 03/14] Added mock to AuthGuardTest --- tests/Auth/AuthGuardTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index f062cbc..e53e88c 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -16,6 +16,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Session\Session; use Illuminate\Cookie\CookieJar; +use Illuminate\Support\Facades\Timebox; use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; @@ -84,6 +85,8 @@ public function testBasicWithExtraArrayConditions() $guard = m::mock(SessionGuard::class.'[check,attempt]', ['default', $provider, $session]); $guard->shouldReceive('check')->once()->andReturn(false); $guard->shouldReceive('attempt')->once()->with(['email' => 'foo@bar.com', 'password' => 'secret', 'active' => 1, 'type' => [1, 2, 3]])->andReturn(true); + $timebox = m::mock(Timebox::class); + $timebox->shouldReceive('make')->once()->andReturnUsing(fn($callback) => $callback()); $request = Request::create('/', 'GET', [], [], [], ['PHP_AUTH_USER' => 'foo@bar.com', 'PHP_AUTH_PW' => 'secret']); $guard->setRequest($request); From 02c3edaa7e8144959bad49c51ef625089d75ee3f Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 16:37:40 +0200 Subject: [PATCH 04/14] Added mock to AuthGuardTest --- src/Illuminate/Auth/GuardHelpers.php | 7 +++++++ src/Illuminate/Auth/SessionGuard.php | 4 +++- tests/Auth/AuthGuardTest.php | 3 +-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Auth/GuardHelpers.php b/src/Illuminate/Auth/GuardHelpers.php index aa9ebf9..9ad54ae 100644 --- a/src/Illuminate/Auth/GuardHelpers.php +++ b/src/Illuminate/Auth/GuardHelpers.php @@ -24,6 +24,13 @@ trait GuardHelpers */ protected $provider; + /** + * The timebox implementation. + * + * @var \Illuminate\Support\Timebox + */ + protected $timebox; + /** * Determine if the current user is authenticated. If not, throw an exception. * diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index 65bc47c..b52e8f2 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -115,12 +115,14 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth public function __construct($name, UserProvider $provider, Session $session, - Request $request = null) + Request $request = null, + Timebox $timebox = null) { $this->name = $name; $this->session = $session; $this->request = $request; $this->provider = $provider; + $this->timebox = $timebox; } /** diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index e53e88c..4c7d9a3 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -85,8 +85,6 @@ public function testBasicWithExtraArrayConditions() $guard = m::mock(SessionGuard::class.'[check,attempt]', ['default', $provider, $session]); $guard->shouldReceive('check')->once()->andReturn(false); $guard->shouldReceive('attempt')->once()->with(['email' => 'foo@bar.com', 'password' => 'secret', 'active' => 1, 'type' => [1, 2, 3]])->andReturn(true); - $timebox = m::mock(Timebox::class); - $timebox->shouldReceive('make')->once()->andReturnUsing(fn($callback) => $callback()); $request = Request::create('/', 'GET', [], [], [], ['PHP_AUTH_USER' => 'foo@bar.com', 'PHP_AUTH_PW' => 'secret']); $guard->setRequest($request); @@ -97,6 +95,7 @@ public function testAttemptCallsRetrieveByCredentials() { $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); + Timebox::shouldReceive('make')->once()->andReturnUsing(fn($callback) => $callback()); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); From aca16f3363f79749899c36a3c92d2c9736031685 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 16:45:06 +0200 Subject: [PATCH 05/14] Added mock to AuthGuardTest --- tests/Auth/AuthGuardTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index 4c7d9a3..c12fb03 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -95,7 +95,9 @@ public function testAttemptCallsRetrieveByCredentials() { $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); - Timebox::shouldReceive('make')->once()->andReturnUsing(fn($callback) => $callback()); + Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { + return $callback(); + }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); From 65a2948a72b249a3ebc47fe2f59332ef9dd81ff6 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 16:51:04 +0200 Subject: [PATCH 06/14] Added mock to AuthGuardTest --- tests/Auth/AuthGuardTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index c12fb03..27aed64 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -96,7 +96,7 @@ public function testAttemptCallsRetrieveByCredentials() $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { - return $callback(); + return $callback(Timebox::shouldReceive('earlyReturn')->once()->andReturnSelf()); }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); From 176e9cdac8e58b4e4583b92d201f7f74438a5a77 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 17:23:30 +0200 Subject: [PATCH 07/14] Added mocks --- tests/Auth/AuthGuardTest.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index 27aed64..2278f07 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -96,7 +96,7 @@ public function testAttemptCallsRetrieveByCredentials() $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { - return $callback(Timebox::shouldReceive('earlyReturn')->once()->andReturnSelf()); + return $callback(new Timebox); }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); @@ -110,6 +110,9 @@ public function testAttemptReturnsUserInterface() [$session, $provider, $request, $cookie] = $this->getMocks(); $guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login'])->setConstructorArgs(['default', $provider, $session, $request])->getMock(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); + Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { + return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); + }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Validated::class)); $user = $this->createMock(Authenticatable::class); @@ -148,6 +151,9 @@ public function testAttemptAndWithCallbacks() $mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user); $mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue(); $mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse(); + Timebox::shouldReceive('make')->andReturnUsing(function ($callback, $microseconds) { + return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); + }); $this->assertTrue($mock->attemptWhen(['foo'], function ($user, $guard) { static::assertInstanceOf(Authenticatable::class, $user); @@ -551,6 +557,9 @@ public function testLoginOnceSetsUser() [$session, $provider, $request, $cookie] = $this->getMocks(); $guard = m::mock(SessionGuard::class, ['default', $provider, $session])->makePartial(); $user = m::mock(Authenticatable::class); + Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { + return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); + }); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn($user); $guard->getProvider()->shouldReceive('validateCredentials')->once()->with($user, ['foo'])->andReturn(true); $guard->shouldReceive('setUser')->once()->with($user); From e9d325c93681c0080bbfc947f73bf58a3746e70e Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 2 Aug 2022 22:16:39 +0200 Subject: [PATCH 08/14] Dependency injection for timebox --- src/Illuminate/Auth/AuthManager.php | 3 +- src/Illuminate/Auth/GuardHelpers.php | 7 --- src/Illuminate/Auth/SessionGuard.php | 22 +++++++++- src/Illuminate/Support/Facades/Facade.php | 1 + tests/Auth/AuthGuardTest.php | 53 ++++++++++++++--------- 5 files changed, 56 insertions(+), 30 deletions(-) diff --git a/src/Illuminate/Auth/AuthManager.php b/src/Illuminate/Auth/AuthManager.php index cc23eb8..139f66f 100755 --- a/src/Illuminate/Auth/AuthManager.php +++ b/src/Illuminate/Auth/AuthManager.php @@ -4,6 +4,7 @@ use Closure; use Illuminate\Contracts\Auth\Factory as FactoryContract; +use Illuminate\Support\Timebox; use InvalidArgumentException; class AuthManager implements FactoryContract @@ -120,7 +121,7 @@ public function createSessionDriver($name, $config) { $provider = $this->createUserProvider($config['provider'] ?? null); - $guard = new SessionGuard($name, $provider, $this->app['session.store']); + $guard = new SessionGuard($name, $provider, $this->app['session.store'], null, new Timebox); // When using the remember me functionality of the authentication services we // will need to be set the encryption instance of the guard, which allows diff --git a/src/Illuminate/Auth/GuardHelpers.php b/src/Illuminate/Auth/GuardHelpers.php index 9ad54ae..aa9ebf9 100644 --- a/src/Illuminate/Auth/GuardHelpers.php +++ b/src/Illuminate/Auth/GuardHelpers.php @@ -24,13 +24,6 @@ trait GuardHelpers */ protected $provider; - /** - * The timebox implementation. - * - * @var \Illuminate\Support\Timebox - */ - protected $timebox; - /** * Determine if the current user is authenticated. If not, throw an exception. * diff --git a/src/Illuminate/Auth/SessionGuard.php b/src/Illuminate/Auth/SessionGuard.php index b52e8f2..a63e187 100644 --- a/src/Illuminate/Auth/SessionGuard.php +++ b/src/Illuminate/Auth/SessionGuard.php @@ -20,7 +20,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Str; -use Illuminate\Support\Facades\Timebox; +use Illuminate\Support\Timebox; use Illuminate\Support\Traits\Macroable; use InvalidArgumentException; use RuntimeException; @@ -89,6 +89,13 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth */ protected $events; + /** + * The timebox implementation. + * + * @var \Illuminate\Support\Timebox + */ + protected $timebox; + /** * Indicates if the logout method has been called. * @@ -110,6 +117,7 @@ class SessionGuard implements StatefulGuard, SupportsBasicAuth * @param \Illuminate\Contracts\Auth\UserProvider $provider * @param \Illuminate\Contracts\Session\Session $session * @param \Symfony\Component\HttpFoundation\Request|null $request + * @param \Illuminate\Support\Timebox|null $timebox * @return void */ public function __construct($name, @@ -426,7 +434,7 @@ public function attemptWhen(array $credentials = [], $callbacks = null, $remembe */ protected function hasValidCredentials($user, $credentials) { - return Timebox::make(function ($timebox) use ($user, $credentials) { + return $this->timebox->make(function ($timebox) use ($user, $credentials) { $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials); if ($validated) { @@ -889,6 +897,16 @@ public function getSession() return $this->session; } + /** + * Get the timebox instance used by the guard. + * + * @return \Illuminate\Support\Timebox + */ + public function getTimebox() + { + return $this->timebox; + } + /** * Return the currently cached user. * diff --git a/src/Illuminate/Support/Facades/Facade.php b/src/Illuminate/Support/Facades/Facade.php index 219e599..798457d 100755 --- a/src/Illuminate/Support/Facades/Facade.php +++ b/src/Illuminate/Support/Facades/Facade.php @@ -290,6 +290,7 @@ public static function defaultAliases() 'Session' => Session::class, 'Storage' => Storage::class, 'Str' => Str::class, + 'Timebox' => Timebox::class, 'URL' => URL::class, 'Validator' => Validator::class, 'View' => View::class, diff --git a/tests/Auth/AuthGuardTest.php b/tests/Auth/AuthGuardTest.php index 2278f07..8f30fb7 100755 --- a/tests/Auth/AuthGuardTest.php +++ b/tests/Auth/AuthGuardTest.php @@ -16,7 +16,7 @@ use Illuminate\Contracts\Events\Dispatcher; use Illuminate\Contracts\Session\Session; use Illuminate\Cookie\CookieJar; -use Illuminate\Support\Facades\Timebox; +use Illuminate\Support\Timebox; use Mockery as m; use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Cookie; @@ -95,8 +95,9 @@ public function testAttemptCallsRetrieveByCredentials() { $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); - Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { - return $callback(new Timebox); + $timebox = $guard->getTimebox(); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback) use ($timebox) { + return $callback($timebox); }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); @@ -107,11 +108,11 @@ public function testAttemptCallsRetrieveByCredentials() public function testAttemptReturnsUserInterface() { - [$session, $provider, $request, $cookie] = $this->getMocks(); - $guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login'])->setConstructorArgs(['default', $provider, $session, $request])->getMock(); + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $guard = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['login'])->setConstructorArgs(['default', $provider, $session, $request, $timebox])->getMock(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); - Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { - return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) { + return $callback($timebox->shouldReceive('returnEarly')->once()->getMock()); }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Validated::class)); @@ -126,6 +127,10 @@ public function testAttemptReturnsFalseIfUserNotGiven() { $mock = $this->getGuard(); $mock->setDispatcher($events = m::mock(Dispatcher::class)); + $timebox = $mock->getTimebox(); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) { + return $callback($timebox); + }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); @@ -135,9 +140,12 @@ public function testAttemptReturnsFalseIfUserNotGiven() public function testAttemptAndWithCallbacks() { - [$session, $provider, $request, $cookie] = $this->getMocks(); - $mock = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['getName'])->setConstructorArgs(['default', $provider, $session, $request])->getMock(); + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $mock = $this->getMockBuilder(SessionGuard::class)->onlyMethods(['getName'])->setConstructorArgs(['default', $provider, $session, $request, $timebox])->getMock(); $mock->setDispatcher($events = m::mock(Dispatcher::class)); + $timebox->shouldReceive('make')->andReturnUsing(function ($callback) use ($timebox) { + return $callback($timebox->shouldReceive('returnEarly')->getMock()); + }); $user = m::mock(Authenticatable::class); $events->shouldReceive('dispatch')->times(3)->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Login::class)); @@ -151,9 +159,6 @@ public function testAttemptAndWithCallbacks() $mock->getProvider()->shouldReceive('retrieveByCredentials')->times(3)->with(['foo'])->andReturn($user); $mock->getProvider()->shouldReceive('validateCredentials')->twice()->andReturnTrue(); $mock->getProvider()->shouldReceive('validateCredentials')->once()->andReturnFalse(); - Timebox::shouldReceive('make')->andReturnUsing(function ($callback, $microseconds) { - return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); - }); $this->assertTrue($mock->attemptWhen(['foo'], function ($user, $guard) { static::assertInstanceOf(Authenticatable::class, $user); @@ -222,6 +227,10 @@ public function testFailedAttemptFiresFailedEvent() { $guard = $this->getGuard(); $guard->setDispatcher($events = m::mock(Dispatcher::class)); + $timebox = $guard->getTimebox(); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) use ($timebox) { + return $callback($timebox); + }); $events->shouldReceive('dispatch')->once()->with(m::type(Attempting::class)); $events->shouldReceive('dispatch')->once()->with(m::type(Failed::class)); $events->shouldNotReceive('dispatch')->with(m::type(Validated::class)); @@ -554,11 +563,11 @@ public function testUserUsesRememberCookieIfItExists() public function testLoginOnceSetsUser() { - [$session, $provider, $request, $cookie] = $this->getMocks(); - $guard = m::mock(SessionGuard::class, ['default', $provider, $session])->makePartial(); + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $guard = m::mock(SessionGuard::class, ['default', $provider, $session, $request, $timebox])->makePartial(); $user = m::mock(Authenticatable::class); - Timebox::shouldReceive('make')->once()->andReturnUsing(function ($callback, $microseconds) { - return $callback(Timebox::shouldReceive('returnEarly')->once()->getMock()); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback) use ($timebox) { + return $callback($timebox->shouldReceive('returnEarly')->once()->getMock()); }); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn($user); $guard->getProvider()->shouldReceive('validateCredentials')->once()->with($user, ['foo'])->andReturn(true); @@ -568,9 +577,12 @@ public function testLoginOnceSetsUser() public function testLoginOnceFailure() { - [$session, $provider, $request, $cookie] = $this->getMocks(); - $guard = m::mock(SessionGuard::class, ['default', $provider, $session])->makePartial(); + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); + $guard = m::mock(SessionGuard::class, ['default', $provider, $session, $request, $timebox])->makePartial(); $user = m::mock(Authenticatable::class); + $timebox->shouldReceive('make')->once()->andReturnUsing(function ($callback) use ($timebox) { + return $callback($timebox); + }); $guard->getProvider()->shouldReceive('retrieveByCredentials')->once()->with(['foo'])->andReturn($user); $guard->getProvider()->shouldReceive('validateCredentials')->once()->with($user, ['foo'])->andReturn(false); $this->assertFalse($guard->once(['foo'])); @@ -578,9 +590,9 @@ public function testLoginOnceFailure() protected function getGuard() { - [$session, $provider, $request, $cookie] = $this->getMocks(); + [$session, $provider, $request, $cookie, $timebox] = $this->getMocks(); - return new SessionGuard('default', $provider, $session, $request); + return new SessionGuard('default', $provider, $session, $request, $timebox); } protected function getMocks() @@ -590,6 +602,7 @@ protected function getMocks() m::mock(UserProvider::class), Request::create('/', 'GET'), m::mock(CookieJar::class), + m::mock(Timebox::class), ]; } From 8a48916e06d54d681b8c4501ca9dbbfce8ede317 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Wed, 3 Aug 2022 07:16:36 +0200 Subject: [PATCH 09/14] Made Timebox PHP 7.3 compatible --- src/Illuminate/Support/Timebox.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Illuminate/Support/Timebox.php b/src/Illuminate/Support/Timebox.php index 0da6d80..6aa1f6b 100644 --- a/src/Illuminate/Support/Timebox.php +++ b/src/Illuminate/Support/Timebox.php @@ -7,11 +7,16 @@ class Timebox /** * Is the timebox allowed to do an early return * - * @var boolean + * @var bool */ - public bool $earlyReturn = false; + public $earlyReturn = false; - public function make(callable $callback, int $microseconds): mixed + /** + * @param callable $callback + * @param int $microseconds + * @return mixed + */ + public function make(callable $callback, int $microseconds) { $start = microtime(true); From aa93ee2bc08ed41f556c3555eee3b3c8c93f49ca Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Sat, 6 Aug 2022 12:18:57 +0200 Subject: [PATCH 10/14] Added unittests for Timebox support class --- src/Illuminate/Support/Timebox.php | 11 +++++- tests/Support/SupportTimeboxTest.php | 52 ++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100755 tests/Support/SupportTimeboxTest.php diff --git a/src/Illuminate/Support/Timebox.php b/src/Illuminate/Support/Timebox.php index 6aa1f6b..6c7c838 100644 --- a/src/Illuminate/Support/Timebox.php +++ b/src/Illuminate/Support/Timebox.php @@ -25,7 +25,7 @@ public function make(callable $callback, int $microseconds) $remainder = $microseconds - ((microtime(true) - $start) * 1000000); if (! $this->earlyReturn && $remainder > 0) { - usleep($remainder); + $this->usleep($remainder); } return $result; @@ -44,4 +44,13 @@ public function dontReturnEarly(): self return $this; } + + /** + * @param $microseconds + * @return void + */ + protected function usleep($microseconds) + { + usleep($microseconds); + } } diff --git a/tests/Support/SupportTimeboxTest.php b/tests/Support/SupportTimeboxTest.php new file mode 100755 index 0000000..b11b46e --- /dev/null +++ b/tests/Support/SupportTimeboxTest.php @@ -0,0 +1,52 @@ +assertTrue(true); + }; + + (new Timebox)->make($callback, 0); + } + + public function testMakeWaitsForMicroseconds() + { + $mock = m::spy(Timebox::class)->shouldAllowMockingProtectedMethods()->makePartial(); + $mock->shouldReceive('usleep')->once(); + + $mock->make(function () {}, 10000); + + $mock->shouldHaveReceived('usleep')->once(); + } + + public function testMakeShouldNotSleepWhenEarlyReturnHasBeenFlagged() + { + $mock = m::spy(Timebox::class)->shouldAllowMockingProtectedMethods()->makePartial(); + $mock->make(function ($timebox) { + $timebox->returnEarly(); + }, 10000); + + $mock->shouldNotHaveReceived('usleep'); + } + + public function testMakeShouldSleepWhenDontEarlyReturnHasBeenFlagged() + { + $mock = m::spy(Timebox::class)->shouldAllowMockingProtectedMethods()->makePartial(); + $mock->shouldReceive('usleep')->once(); + + $mock->make(function ($timebox) { + $timebox->returnEarly(); + $timebox->dontReturnEarly(); + }, 10000); + + $mock->shouldHaveReceived('usleep')->once(); + } +} From cb271e9fe600e492b7f15f7df459b99dbcd222f5 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Mon, 12 Sep 2022 20:28:19 +0200 Subject: [PATCH 11/14] Added higher delta because usleep() on windows is unrealiable --- tests/Support/SupportHelpersTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 2f75cca..042462c 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -585,7 +585,7 @@ public function testRetry() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.03); } public function testRetryWithPassingSleepCallback() @@ -608,7 +608,7 @@ public function testRetryWithPassingSleepCallback() $this->assertEquals(3, $attempts); // Make sure we waited 300ms for the first two attempts - $this->assertEqualsWithDelta(0.3, microtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.3, microtime(true) - $startTime, 0.03); } public function testRetryWithPassingWhenCallback() @@ -629,7 +629,7 @@ public function testRetryWithPassingWhenCallback() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.03); } public function testRetryWithFailingWhenCallback() @@ -661,7 +661,7 @@ public function testRetryWithBackoff() // Make sure we made four attempts $this->assertEquals(4, $attempts); - $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, microtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, microtime(true) - $startTime, 0.03); } public function testTransform() From b658ba8a2d30650d36202c67d4e260ba106ba35f Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Mon, 12 Sep 2022 20:36:49 +0200 Subject: [PATCH 12/14] Added higher delta because usleep() on windows is unrealiable --- tests/Support/SupportHelpersTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 042462c..a70cfa4 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -661,7 +661,7 @@ public function testRetryWithBackoff() // Make sure we made four attempts $this->assertEquals(4, $attempts); - $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, microtime(true) - $startTime, 0.03); + $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, microtime(true) - $startTime, 0.05); } public function testTransform() From edbadc0dc9b4fe628f9a8f04599a734ff4470260 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 13 Sep 2022 16:47:34 +0200 Subject: [PATCH 13/14] Changing to hrtime instead of microtime in time sensitive tests --- tests/Support/SupportHelpersTest.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index a70cfa4..3db6cb9 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -571,7 +571,7 @@ public function something() public function testRetry() { - $startTime = microtime(true); + $startTime = hrtime(true); $attempts = retry(2, function ($attempts) { if ($attempts > 1) { @@ -585,12 +585,12 @@ public function testRetry() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.03); + $this->assertEqualsWithDelta(0.1, hrtime(true) - $startTime, 0.02); } public function testRetryWithPassingSleepCallback() { - $startTime = microtime(true); + $startTime = hrtime(true); $attempts = retry(3, function ($attempts) { if ($attempts > 2) { @@ -608,12 +608,12 @@ public function testRetryWithPassingSleepCallback() $this->assertEquals(3, $attempts); // Make sure we waited 300ms for the first two attempts - $this->assertEqualsWithDelta(0.3, microtime(true) - $startTime, 0.03); + $this->assertEqualsWithDelta(0.3, hrtime(true) - $startTime, 0.02); } public function testRetryWithPassingWhenCallback() { - $startTime = microtime(true); + $startTime = hrtime(true); $attempts = retry(2, function ($attempts) { if ($attempts > 1) { @@ -629,7 +629,7 @@ public function testRetryWithPassingWhenCallback() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, microtime(true) - $startTime, 0.03); + $this->assertEqualsWithDelta(0.1, hrtime(true) - $startTime, 0.02); } public function testRetryWithFailingWhenCallback() @@ -649,7 +649,7 @@ public function testRetryWithFailingWhenCallback() public function testRetryWithBackoff() { - $startTime = microtime(true); + $startTime = hrtime(true); $attempts = retry([50, 100, 200], function ($attempts) { if ($attempts > 3) { return $attempts; @@ -661,7 +661,7 @@ public function testRetryWithBackoff() // Make sure we made four attempts $this->assertEquals(4, $attempts); - $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, microtime(true) - $startTime, 0.05); + $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, hrtime(true) - $startTime, 0.02); } public function testTransform() From a45361cc81a89c9915515664a9814fc9148e2a73 Mon Sep 17 00:00:00 2001 From: Jens Just Iversen Date: Tue, 13 Sep 2022 17:04:08 +0200 Subject: [PATCH 14/14] Changing to hrtime instead of microtime in time sensitive tests --- tests/Support/SupportHelpersTest.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Support/SupportHelpersTest.php b/tests/Support/SupportHelpersTest.php index 3db6cb9..ed33181 100755 --- a/tests/Support/SupportHelpersTest.php +++ b/tests/Support/SupportHelpersTest.php @@ -585,7 +585,7 @@ public function testRetry() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, hrtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.1, (hrtime(true) - $startTime) / 1000000000, 0.02); } public function testRetryWithPassingSleepCallback() @@ -608,7 +608,7 @@ public function testRetryWithPassingSleepCallback() $this->assertEquals(3, $attempts); // Make sure we waited 300ms for the first two attempts - $this->assertEqualsWithDelta(0.3, hrtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.3, (hrtime(true) - $startTime) / 1000000000, 0.02); } public function testRetryWithPassingWhenCallback() @@ -629,7 +629,7 @@ public function testRetryWithPassingWhenCallback() $this->assertEquals(2, $attempts); // Make sure we waited 100ms for the first attempt - $this->assertEqualsWithDelta(0.1, hrtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.1, (hrtime(true) - $startTime) / 1000000000, 0.02); } public function testRetryWithFailingWhenCallback() @@ -661,7 +661,7 @@ public function testRetryWithBackoff() // Make sure we made four attempts $this->assertEquals(4, $attempts); - $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, hrtime(true) - $startTime, 0.02); + $this->assertEqualsWithDelta(0.05 + 0.1 + 0.2, (hrtime(true) - $startTime) / 1000000000, 0.02); } public function testTransform()