From 05e370c56cd3009e971724aa5d9f0d09291f22d3 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 16:07:36 -0600 Subject: [PATCH 01/11] Add side effects --- src/ActivityStub.php | 29 +---- src/Traits/AwaitWithTimeouts.php | 53 +++++++++ src/Traits/Awaits.php | 51 +++++++++ src/Traits/SideEffects.php | 44 ++++++++ src/Traits/Timers.php | 83 ++++++++++++++ src/WorkflowStub.php | 128 ++-------------------- tests/Feature/SideEffectWorkflowTest.php | 35 ++++++ tests/Fixtures/TestSideEffectWorkflow.php | 44 ++++++++ tests/Unit/WorkflowStubTest.php | 9 +- 9 files changed, 327 insertions(+), 149 deletions(-) create mode 100644 src/Traits/AwaitWithTimeouts.php create mode 100644 src/Traits/Awaits.php create mode 100644 src/Traits/SideEffects.php create mode 100644 src/Traits/Timers.php create mode 100644 tests/Feature/SideEffectWorkflowTest.php create mode 100644 tests/Fixtures/TestSideEffectWorkflow.php diff --git a/src/ActivityStub.php b/src/ActivityStub.php index 0f5b850..b0f65eb 100644 --- a/src/ActivityStub.php +++ b/src/ActivityStub.php @@ -12,15 +12,6 @@ final class ActivityStub { - private $arguments; - - private function __construct( - protected $activity, - ...$arguments - ) { - $this->arguments = $arguments; - } - public static function all(iterable $promises): PromiseInterface { return all([...$promises]); @@ -39,30 +30,12 @@ public static function make($activity, ...$arguments): PromiseInterface WorkflowStub::setContext($context); return resolve(Y::unserialize($log->result)); } - $current = new self($activity, ...$arguments); - $current->activity()::dispatch( - $context->index, - $context->now, - $context->storedWorkflow, - ...$current->arguments() - ); + $activity::dispatch($context->index, $context->now, $context->storedWorkflow, ...$arguments); ++$context->index; WorkflowStub::setContext($context); - $deferred = new Deferred(); - return $deferred->promise(); } - - public function activity() - { - return $this->activity; - } - - public function arguments() - { - return $this->arguments; - } } diff --git a/src/Traits/AwaitWithTimeouts.php b/src/Traits/AwaitWithTimeouts.php new file mode 100644 index 0000000..eb50add --- /dev/null +++ b/src/Traits/AwaitWithTimeouts.php @@ -0,0 +1,53 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + if (is_string($seconds)) { + $seconds = CarbonInterval::fromString($seconds)->totalSeconds; + } + + $result = $condition(); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + // already logged + } + } + ++self::$context->index; + return resolve($result); + } + + return self::timer($seconds)->then(static fn ($completed): bool => ! $completed); + } +} diff --git a/src/Traits/Awaits.php b/src/Traits/Awaits.php new file mode 100644 index 0000000..671a4db --- /dev/null +++ b/src/Traits/Awaits.php @@ -0,0 +1,51 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $result = $condition(); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + // already logged + } + } + ++self::$context->index; + return resolve($result); + } + + ++self::$context->index; + $deferred = new Deferred(); + return $deferred->promise(); + } +} diff --git a/src/Traits/SideEffects.php b/src/Traits/SideEffects.php new file mode 100644 index 0000000..a1a2b0b --- /dev/null +++ b/src/Traits/SideEffects.php @@ -0,0 +1,44 @@ +storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $result = $callable(); + + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => self::$context->storedWorkflow->class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + // already logged + } + } + + ++self::$context->index; + return resolve($result); + } +} diff --git a/src/Traits/Timers.php b/src/Traits/Timers.php new file mode 100644 index 0000000..ca4e83d --- /dev/null +++ b/src/Traits/Timers.php @@ -0,0 +1,83 @@ +totalSeconds; + } + + if ($seconds <= 0) { + ++self::$context->index; + return resolve(true); + } + + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } + + $timer = self::$context->storedWorkflow->timers() + ->whereIndex(self::$context->index) + ->first(); + + if ($timer === null) { + $when = self::$context->now->copy() + ->addSeconds($seconds); + + if (! self::$context->replaying) { + $timer = self::$context->storedWorkflow->timers() + ->create([ + 'index' => self::$context->index, + 'stop_at' => $when, + ]); + } + } + + $result = $timer->stop_at + ->lessThanOrEqualTo(self::$context->now); + + if ($result === true) { + if (! self::$context->replaying) { + try { + self::$context->storedWorkflow->logs() + ->create([ + 'index' => self::$context->index, + 'now' => self::$context->now, + 'class' => Signal::class, + 'result' => Y::serialize($result), + ]); + } catch (QueryException $exception) { + // already logged + } + } + ++self::$context->index; + return resolve($result); + } + + if (! self::$context->replaying) { + Signal::dispatch(self::$context->storedWorkflow)->delay($timer->stop_at); + } + + ++self::$context->index; + $deferred = new Deferred(); + return $deferred->promise(); + } +} diff --git a/src/WorkflowStub.php b/src/WorkflowStub.php index 93050b2..77117af 100644 --- a/src/WorkflowStub.php +++ b/src/WorkflowStub.php @@ -4,21 +4,26 @@ namespace Workflow; -use Carbon\CarbonInterval; use Illuminate\Database\QueryException; use Illuminate\Support\Carbon; -use React\Promise\Deferred; -use React\Promise\PromiseInterface; -use function React\Promise\resolve; use ReflectionClass; use Workflow\Models\StoredWorkflow; use Workflow\Serializers\Y; use Workflow\States\WorkflowCompletedStatus; use Workflow\States\WorkflowFailedStatus; use Workflow\States\WorkflowPendingStatus; +use Workflow\Traits\Awaits; +use Workflow\Traits\AwaitWithTimeouts; +use Workflow\Traits\SideEffects; +use Workflow\Traits\Timers; final class WorkflowStub { + use Awaits; + use AwaitWithTimeouts; + use SideEffects; + use Timers; + private static ?\stdClass $context = null; private function __construct( @@ -94,121 +99,6 @@ public static function setContext($context): void self::$context = (object) $context; } - public static function await($condition): PromiseInterface - { - $result = $condition(); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve(true); - } - - ++self::$context->index; - $deferred = new Deferred(); - return $deferred->promise(); - } - - public static function awaitWithTimeout($seconds, $condition): PromiseInterface - { - if (is_string($seconds)) { - $seconds = CarbonInterval::fromString($seconds)->totalSeconds; - } - - $result = $condition(); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve($result); - } - - ++self::$context->index; - return self::timer($seconds)->then(static fn ($completed): bool => ! $completed); - } - - public static function timer($seconds): PromiseInterface - { - if (is_string($seconds)) { - $seconds = CarbonInterval::fromString($seconds)->totalSeconds; - } - - if ($seconds <= 0) { - ++self::$context->index; - return resolve(true); - } - - $timer = self::$context->storedWorkflow->timers() - ->whereIndex(self::$context->index) - ->first(); - - if ($timer === null) { - $when = self::$context->now->copy() - ->addSeconds($seconds); - - if (! self::$context->replaying) { - $timer = self::$context->storedWorkflow->timers() - ->create([ - 'index' => self::$context->index, - 'stop_at' => $when, - ]); - } - } - - $result = $timer->stop_at - ->lessThanOrEqualTo(self::$context->now); - - if ($result === true) { - if (! self::$context->replaying) { - try { - self::$context->storedWorkflow->logs() - ->create([ - 'index' => self::$context->index, - 'now' => self::$context->now, - 'class' => Signal::class, - 'result' => Y::serialize($result), - ]); - } catch (QueryException $exception) { - // already logged - } - } - ++self::$context->index; - return resolve($result); - } - - if (! self::$context->replaying) { - Signal::dispatch(self::$context->storedWorkflow)->delay($timer->stop_at); - } - - ++self::$context->index; - $deferred = new Deferred(); - return $deferred->promise(); - } - public static function now() { return self::getContext()->now; diff --git a/tests/Feature/SideEffectWorkflowTest.php b/tests/Feature/SideEffectWorkflowTest.php new file mode 100644 index 0000000..2e7b3ad --- /dev/null +++ b/tests/Feature/SideEffectWorkflowTest.php @@ -0,0 +1,35 @@ +start(); + + while ($workflow->running()); + + $this->assertSame(WorkflowCompletedStatus::class, $workflow->status()); + $this->assertSame('workflow', $workflow->output()); + $this->assertSame( + [TestActivity::class, TestOtherActivity::class, TestOtherActivity::class, TestSideEffectWorkflow::class], + $workflow->logs() + ->pluck('class') + ->sort() + ->values() + ->toArray() + ); + } +} diff --git a/tests/Fixtures/TestSideEffectWorkflow.php b/tests/Fixtures/TestSideEffectWorkflow.php new file mode 100644 index 0000000..97903c6 --- /dev/null +++ b/tests/Fixtures/TestSideEffectWorkflow.php @@ -0,0 +1,44 @@ + random_int(PHP_INT_MIN, PHP_INT_MAX)); + + $badSideEffect = random_int(PHP_INT_MIN, PHP_INT_MAX); + + $result = yield ActivityStub::make(TestActivity::class); + + $otherResult1 = yield ActivityStub::make(TestOtherActivity::class, $sideEffect); + + $otherResult2 = yield ActivityStub::make(TestOtherActivity::class, $badSideEffect); + + if ($sideEffect !== $otherResult1) { + throw new Exception( + 'These side effects should match because it was properly wrapped in WorkflowStub::sideEffect().' + ); + } + + if ($badSideEffect === $otherResult2) { + throw new Exception( + 'These side effects should not match because it was not wrapped in WorkflowStub::sideEffect().' + ); + } + + return 'workflow'; + } +} diff --git a/tests/Unit/WorkflowStubTest.php b/tests/Unit/WorkflowStubTest.php index b9fe254..630a12a 100644 --- a/tests/Unit/WorkflowStubTest.php +++ b/tests/Unit/WorkflowStubTest.php @@ -151,9 +151,13 @@ public function testAwaitWithTimeoutTimedout(): void $workflow->cancel(); while (! $workflow->isCanceled()); - $workflow = WorkflowStub::load($workflow->id()); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertSame(1, WorkflowStub::getContext()->index); - $this->assertSame(0, WorkflowStub::getContext()->index); + $workflow = WorkflowStub::load($workflow->id()); + $context = WorkflowStub::getContext(); + $context->index = 1; + WorkflowStub::setContext($context); $promise = WorkflowStub::awaitWithTimeout('1 minute', static fn () => false); @@ -162,6 +166,7 @@ public function testAwaitWithTimeoutTimedout(): void $workflow = WorkflowStub::load($workflow->id()); $context = WorkflowStub::getContext(); + $context->index = 1; $context->now = $context->now->addMinute(); WorkflowStub::setContext($context); From efdf95fc8081a05c4f887e5005507cc1ae321f51 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 16:43:42 -0600 Subject: [PATCH 02/11] Add test --- tests/Unit/Traits/SideEffectsTest.php | 58 +++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 tests/Unit/Traits/SideEffectsTest.php diff --git a/tests/Unit/Traits/SideEffectsTest.php b/tests/Unit/Traits/SideEffectsTest.php new file mode 100644 index 0000000..0227d9d --- /dev/null +++ b/tests/Unit/Traits/SideEffectsTest.php @@ -0,0 +1,58 @@ +id()); + + $promise = WorkflowStub::sideEffect(static fn () => 'test'); + + $promise->then(fn($result) => $this->assertSame('test', $result)); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + + $promise = WorkflowStub::sideEffect(static fn () => ''); + + $promise->then(fn($result) => $this->assertSame('test', $result)); + $this->assertSame(1, $workflow->logs()->count()); + + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + $promise = WorkflowStub::sideEffect(static function() use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs()->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + return 'test'; + }); + + $promise->then(fn($result) => $this->assertSame('test', $result)); + $this->assertSame(1, $workflow->logs()->count()); + } +} From 4f0ced4903175aadb95cb4e9d1415956488bf797 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 16:45:07 -0600 Subject: [PATCH 03/11] Add test --- tests/Unit/Traits/SideEffectsTest.php | 28 +++++++++++---------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/tests/Unit/Traits/SideEffectsTest.php b/tests/Unit/Traits/SideEffectsTest.php index 0227d9d..8f7da8b 100644 --- a/tests/Unit/Traits/SideEffectsTest.php +++ b/tests/Unit/Traits/SideEffectsTest.php @@ -4,17 +4,10 @@ namespace Tests\Unit\Traits; -use Exception; -use Illuminate\Support\Carbon; -use Tests\Fixtures\TestAwaitWorkflow; use Tests\Fixtures\TestWorkflow; use Tests\TestCase; use Workflow\Models\StoredWorkflow; use Workflow\Serializers\Y; -use Workflow\Signal; -use Workflow\States\WorkflowCompletedStatus; -use Workflow\States\WorkflowFailedStatus; -use Workflow\States\WorkflowPendingStatus; use Workflow\WorkflowStub; final class SideEffectsTest extends TestCase @@ -25,7 +18,7 @@ public function testTrait(): void $promise = WorkflowStub::sideEffect(static fn () => 'test'); - $promise->then(fn($result) => $this->assertSame('test', $result)); + $promise->then(fn ($result) => $this->assertSame('test', $result)); $this->assertSame(1, $workflow->logs()->count()); $this->assertDatabaseHas('workflow_logs', [ 'stored_workflow_id' => $workflow->id(), @@ -36,23 +29,24 @@ public function testTrait(): void $promise = WorkflowStub::sideEffect(static fn () => ''); - $promise->then(fn($result) => $this->assertSame('test', $result)); + $promise->then(fn ($result) => $this->assertSame('test', $result)); $this->assertSame(1, $workflow->logs()->count()); $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); - $promise = WorkflowStub::sideEffect(static function() use ($workflow) { + $promise = WorkflowStub::sideEffect(static function () use ($workflow) { $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); - $storedWorkflow->logs()->create([ - 'index' => 0, - 'now' => WorkflowStub::now(), - 'class' => TestWorkflow::class, - 'result' => Y::serialize('test'), - ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); return 'test'; }); - $promise->then(fn($result) => $this->assertSame('test', $result)); + $promise->then(fn ($result) => $this->assertSame('test', $result)); $this->assertSame(1, $workflow->logs()->count()); } } From 8a4ba45f33a1f79a3bc51e322ea053d1b60abb4d Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 17:13:10 -0600 Subject: [PATCH 04/11] Add tests --- src/Traits/SideEffects.php | 9 ++++- tests/Unit/Traits/SideEffectsTest.php | 55 ++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 10 deletions(-) diff --git a/src/Traits/SideEffects.php b/src/Traits/SideEffects.php index a1a2b0b..8dced83 100644 --- a/src/Traits/SideEffects.php +++ b/src/Traits/SideEffects.php @@ -34,7 +34,14 @@ public static function sideEffect($callable): PromiseInterface 'result' => Y::serialize($result), ]); } catch (QueryException $exception) { - // already logged + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } } } diff --git a/tests/Unit/Traits/SideEffectsTest.php b/tests/Unit/Traits/SideEffectsTest.php index 8f7da8b..e1794ea 100644 --- a/tests/Unit/Traits/SideEffectsTest.php +++ b/tests/Unit/Traits/SideEffectsTest.php @@ -12,13 +12,16 @@ final class SideEffectsTest extends TestCase { - public function testTrait(): void + public function testStoresResult(): void { $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); - $promise = WorkflowStub::sideEffect(static fn () => 'test'); + WorkflowStub::sideEffect(static fn () => 'test') + ->then(static function ($value) use (&$result) { + $result = $value; + }); - $promise->then(fn ($result) => $this->assertSame('test', $result)); + $this->assertSame('test', $result); $this->assertSame(1, $workflow->logs()->count()); $this->assertDatabaseHas('workflow_logs', [ 'stored_workflow_id' => $workflow->id(), @@ -26,15 +29,40 @@ public function testTrait(): void 'class' => TestWorkflow::class, 'result' => Y::serialize('test'), ]); + } - $promise = WorkflowStub::sideEffect(static fn () => ''); + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); - $promise->then(fn ($result) => $this->assertSame('test', $result)); + WorkflowStub::sideEffect(static fn () => '') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); + } + public function testResolvesConflictingResult(): void + { $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); - $promise = WorkflowStub::sideEffect(static function () use ($workflow) { + WorkflowStub::sideEffect(static function () use ($workflow) { $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); $storedWorkflow->logs() ->create([ @@ -43,10 +71,19 @@ public function testTrait(): void 'class' => TestWorkflow::class, 'result' => Y::serialize('test'), ]); - return 'test'; - }); + return ''; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); - $promise->then(fn ($result) => $this->assertSame('test', $result)); + $this->assertSame('test', $result); $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestWorkflow::class, + 'result' => Y::serialize('test'), + ]); } } From fe0d11406943c3fe5f09132d68b73480b2c62e16 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 17:41:17 -0600 Subject: [PATCH 05/11] Add more tests --- src/Traits/Awaits.php | 9 ++- tests/Unit/Traits/AwaitsTest.php | 103 +++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Traits/AwaitsTest.php diff --git a/src/Traits/Awaits.php b/src/Traits/Awaits.php index 671a4db..4ff5e73 100644 --- a/src/Traits/Awaits.php +++ b/src/Traits/Awaits.php @@ -37,7 +37,14 @@ public static function await($condition): PromiseInterface 'result' => Y::serialize($result), ]); } catch (QueryException $exception) { - // already logged + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } } } ++self::$context->index; diff --git a/tests/Unit/Traits/AwaitsTest.php b/tests/Unit/Traits/AwaitsTest.php new file mode 100644 index 0000000..a1da364 --- /dev/null +++ b/tests/Unit/Traits/AwaitsTest.php @@ -0,0 +1,103 @@ +id()); + + WorkflowStub::await(static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::await(static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::await(static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testResolvesConflictingResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::await(static function () use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + return true; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(false, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + } +} From 0e08e8292514b911e7b8edb6279d79e756cdab64 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 18:01:15 -0600 Subject: [PATCH 06/11] Add more tests --- src/Traits/AwaitWithTimeouts.php | 9 +- tests/Unit/Traits/AwaitWithTimeoutsTest.php | 117 ++++++++++++++++++++ 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 tests/Unit/Traits/AwaitWithTimeoutsTest.php diff --git a/src/Traits/AwaitWithTimeouts.php b/src/Traits/AwaitWithTimeouts.php index eb50add..64a4b20 100644 --- a/src/Traits/AwaitWithTimeouts.php +++ b/src/Traits/AwaitWithTimeouts.php @@ -41,7 +41,14 @@ public static function awaitWithTimeout($seconds, $condition): PromiseInterface 'result' => Y::serialize($result), ]); } catch (QueryException $exception) { - // already logged + $log = self::$context->storedWorkflow->logs() + ->whereIndex(self::$context->index) + ->first(); + + if ($log) { + ++self::$context->index; + return resolve(Y::unserialize($log->result)); + } } } ++self::$context->index; diff --git a/tests/Unit/Traits/AwaitWithTimeoutsTest.php b/tests/Unit/Traits/AwaitWithTimeoutsTest.php new file mode 100644 index 0000000..e4c5b95 --- /dev/null +++ b/tests/Unit/Traits/AwaitWithTimeoutsTest.php @@ -0,0 +1,117 @@ +id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + WorkflowStub::awaitWithTimeout(60, static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testResolvesConflictingResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + + WorkflowStub::awaitWithTimeout('1 minute', static function () use ($workflow) { + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + return true; + }) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(false, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(false), + ]); + } +} From d16618c2a6e331f6d2f1708989ed9f93f1722d1d Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 18:47:26 -0600 Subject: [PATCH 07/11] Add more tests --- src/Traits/Timers.php | 4 +- tests/Unit/Traits/TimersTest.php | 142 +++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+), 2 deletions(-) create mode 100644 tests/Unit/Traits/TimersTest.php diff --git a/src/Traits/Timers.php b/src/Traits/Timers.php index ca4e83d..c3948d8 100644 --- a/src/Traits/Timers.php +++ b/src/Traits/Timers.php @@ -62,14 +62,14 @@ public static function timer($seconds): PromiseInterface 'index' => self::$context->index, 'now' => self::$context->now, 'class' => Signal::class, - 'result' => Y::serialize($result), + 'result' => Y::serialize(true), ]); } catch (QueryException $exception) { // already logged } } ++self::$context->index; - return resolve($result); + return resolve(true); } if (! self::$context->replaying) { diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php new file mode 100644 index 0000000..acd80b7 --- /dev/null +++ b/tests/Unit/Traits/TimersTest.php @@ -0,0 +1,142 @@ +id()); + + WorkflowStub::timer(0) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertTrue($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testCreatesTimer(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + WorkflowStub::timer('1 minute') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_timers', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'stop_at' => WorkflowStub::now()->addMinute(), + ]); + } + + public function testDefersIfNotElapsed(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now()->addHour(), + ]); + + WorkflowStub::timer(60) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => false) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(0, $workflow->logs()->count()); + } + + public function testStoresResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + + WorkflowStub::timer('1 minute') + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->timers() + ->create([ + 'index' => 0, + 'stop_at' => now(), + ]); + + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => now(), + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + + WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(true, $result); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => Signal::class, + 'result' => Y::serialize(true), + ]); + } +} From fa6ab762935c914a0faf67d4c7c6e9ca4456b6a3 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 18:48:17 -0600 Subject: [PATCH 08/11] Add more tests --- tests/Unit/Traits/TimersTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php index acd80b7..fb48fe8 100644 --- a/tests/Unit/Traits/TimersTest.php +++ b/tests/Unit/Traits/TimersTest.php @@ -62,7 +62,8 @@ public function testDefersIfNotElapsed(): void $storedWorkflow->timers() ->create([ 'index' => 0, - 'stop_at' => now()->addHour(), + 'stop_at' => now() + ->addHour(), ]); WorkflowStub::timer(60) From ba7859e3d8ea07067840519fa479676e6a748736 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 18:57:49 -0600 Subject: [PATCH 09/11] Add more tests --- tests/Unit/Traits/TimersTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php index fb48fe8..64fe312 100644 --- a/tests/Unit/Traits/TimersTest.php +++ b/tests/Unit/Traits/TimersTest.php @@ -74,7 +74,7 @@ public function testDefersIfNotElapsed(): void $this->assertNull($result); $this->assertSame(0, $workflow->logs()->count()); - WorkflowStub::awaitWithTimeout('1 minute', static fn () => false) + WorkflowStub::timer('1 minute', static fn () => false) ->then(static function ($value) use (&$result) { $result = $value; }); @@ -126,7 +126,7 @@ public function testLoadsStoredResult(): void 'result' => Y::serialize(true), ]); - WorkflowStub::awaitWithTimeout('1 minute', static fn () => true) + WorkflowStub::timer('1 minute', static fn () => true) ->then(static function ($value) use (&$result) { $result = $value; }); From a6514ca10a5bc524a7961488b43bc0b45536aa85 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 19:12:56 -0600 Subject: [PATCH 10/11] Add more tests --- tests/Unit/Traits/TimersTest.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/Unit/Traits/TimersTest.php b/tests/Unit/Traits/TimersTest.php index 64fe312..8c4571b 100644 --- a/tests/Unit/Traits/TimersTest.php +++ b/tests/Unit/Traits/TimersTest.php @@ -58,7 +58,6 @@ public function testDefersIfNotElapsed(): void 'arguments' => Y::serialize([]), 'status' => WorkflowPendingStatus::$name, ]); - $storedWorkflow->timers() ->create([ 'index' => 0, @@ -117,7 +116,6 @@ public function testLoadsStoredResult(): void 'index' => 0, 'stop_at' => now(), ]); - $storedWorkflow->logs() ->create([ 'index' => 0, From 4da4fa92a65d49c2e25906dc7e95557014275f84 Mon Sep 17 00:00:00 2001 From: Richard McDaniel Date: Sat, 24 Dec 2022 19:13:03 -0600 Subject: [PATCH 11/11] Add more tests --- tests/Unit/ActivityStubTest.php | 91 +++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 tests/Unit/ActivityStubTest.php diff --git a/tests/Unit/ActivityStubTest.php b/tests/Unit/ActivityStubTest.php new file mode 100644 index 0000000..54bc8fc --- /dev/null +++ b/tests/Unit/ActivityStubTest.php @@ -0,0 +1,91 @@ +id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + + ActivityStub::make(TestActivity::class) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertNull($result); + $this->assertSame(WorkflowPendingStatus::class, $workflow->status()); + $this->assertNull($workflow->output()); + $this->assertSame(1, $workflow->logs()->count()); + $this->assertDatabaseHas('workflow_logs', [ + 'stored_workflow_id' => $workflow->id(), + 'index' => 0, + 'class' => TestActivity::class, + 'result' => Y::serialize('activity'), + ]); + } + + public function testLoadsStoredResult(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestActivity::class, + 'result' => Y::serialize('test'), + ]); + + ActivityStub::make(TestActivity::class) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame('test', $result); + } + + public function testAll(): void + { + $workflow = WorkflowStub::load(WorkflowStub::make(TestWorkflow::class)->id()); + $storedWorkflow = StoredWorkflow::findOrFail($workflow->id()); + $storedWorkflow->update([ + 'arguments' => Y::serialize([]), + 'status' => WorkflowPendingStatus::$name, + ]); + $storedWorkflow->logs() + ->create([ + 'index' => 0, + 'now' => WorkflowStub::now(), + 'class' => TestActivity::class, + 'result' => Y::serialize('test'), + ]); + + ActivityStub::all([ActivityStub::make(TestActivity::class)]) + ->then(static function ($value) use (&$result) { + $result = $value; + }); + + $this->assertSame(['test'], $result); + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy