Skip to content

Commit

Permalink
Add unit tests for DeferredGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Dec 16, 2024
1 parent 7f8e2e5 commit 589242f
Show file tree
Hide file tree
Showing 2 changed files with 254 additions and 33 deletions.
62 changes: 29 additions & 33 deletions src/Internal/Workflow/Process/DeferredGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,6 @@ final class DeferredGenerator implements \Iterator
{
private bool $started = false;
private bool $finished = false;
private mixed $key = null;
private mixed $value = null;
private mixed $result = null;
private \Generator $generator;

/** @var array<\Closure(\Throwable): mixed> */
Expand Down Expand Up @@ -50,7 +47,6 @@ public static function fromGenerator(\Generator $generator): self
$self = new self();
$self->generator = $generator;
$self->started = true;
$self->fill();
return $self;
}

Expand All @@ -67,10 +63,8 @@ public function throw(\Throwable $exception): void
);
try {
$this->generator->throw($exception);
$this->fill();
} catch (\Throwable $e) {
$this->handleException($e);
throw $e;
}
}

Expand All @@ -81,15 +75,12 @@ public function throw(\Throwable $exception): void
*/
public function send(mixed $value): mixed
{
$this->started or throw new \LogicException('Cannot send value to a generator that was not started.');
$this->start();
$this->finished and throw new \LogicException('Cannot send value to a generator that was already finished.');
try {
$result = $this->generator->send($value);
$this->fill();
return $result;
return $this->generator->send($value);
} catch (\Throwable $e) {
$this->handleException($e);
throw $e;
}
}

Expand All @@ -98,8 +89,11 @@ public function send(mixed $value): mixed
*/
public function getReturn(): mixed
{
$this->finished or throw new \LogicException('Cannot get return value of a generator that was not finished.');
return $this->result;
try {
return $this->generator->getReturn();
} catch (\Throwable $e) {
$this->handleException($e);
}
}

/**
Expand All @@ -108,7 +102,11 @@ public function getReturn(): mixed
public function current(): mixed
{
$this->start();
return $this->value;
try {
return $this->generator->current();
} catch (\Throwable $e) {
$this->handleException($e);
}
}

/**
Expand All @@ -117,7 +115,7 @@ public function current(): mixed
public function key(): mixed
{
$this->start();
return $this->key;
return $this->generator->key();
}

/**
Expand All @@ -132,10 +130,8 @@ public function next(): void

try {
$this->generator->next();
$this->fill();
} catch (\Throwable $e) {
$this->handleException($e);
throw $e;
}
}

Expand All @@ -147,12 +143,16 @@ public function next(): void
public function valid(): bool
{
$this->start();
return !$this->finished;
try {
return $this->generator->valid();
} catch (\Throwable $e) {
$this->handleException($e);
}
}

public function rewind(): void
{
$this->started and throw new \LogicException('Cannot rewind a generator that was already run.');
$this->generator->rewind();
}

/**
Expand All @@ -178,34 +178,30 @@ private function start(): void

if ($result instanceof \Generator) {
$this->generator = $result;
$this->fill();
return;
}

$this->result = $result;
$this->finished = true;
} catch (\Throwable $e) {
$this->handleException($e);
throw $e;
} finally {
unset($this->handler, $this->values);
}
}

private function fill(): void
private function handleException(\Throwable $e): never
{
$this->key = $this->generator->key();
$this->value = $this->generator->current();
$this->finished = !$this->generator->valid() and $this->result = $this->generator->getReturn();
}

private function handleException(\Throwable $e): void
{
$this->key = null;
$this->value = null;
$this->finished and throw $e;
$this->finished = true;
foreach ($this->catchers as $catch) {
$catch($e);
try {
$catch($e);
} catch (\Throwable) {
// Do nothing.
}
}

$this->catchers = [];
throw $e;
}
}
225 changes: 225 additions & 0 deletions tests/Unit/Workflow/DeferredGeneratorTestCase.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
<?php

declare(strict_types=1);

namespace Temporal\Tests\Unit\Workflow;

use PHPUnit\Framework\TestCase;
use Temporal\DataConverter\EncodedValues;
use Temporal\Internal\Workflow\Process\DeferredGenerator;

/**
* @psalm-type Action = 'current'|'send'|'key'|'next'|'valid'|'rewind'|'getReturn'
*/
final class DeferredGeneratorTestCase extends TestCase
{
public function testSimple(): void
{
$this->compare(
fn() => (function () {
yield 1;
yield 42 => 2;
yield 3;
})(),
[
'current', 'key', 'current', 'key',
'next',
'current', 'key', 'current', 'key', 'valid',
'next',
['send', 'foo'],
'current', 'key', 'current', 'key', 'valid',
],
);
}

public function testSendingValues(): void
{
$this->compare(
fn() => (function () {
$a = yield;
$b = yield $a;
$c = yield $b;
return [$a, $b, $c];
})(),
[
['send', 'foo'],
['send', 'bar'],
['send', 'baz'],
'current', 'key', 'current', 'key', 'valid',
],
);
}

public function testThrowingExceptions(): void
{
$this->compare(
fn() => (function () {
try {
yield;
throw new \Exception('foo');
} catch (\Exception $e) {
yield $e->getMessage();
}
})(),
[
'current', 'key', 'current', 'key', 'valid',
'next',
'current', 'key', 'current', 'key', 'valid',
'next',
'rewind',
],
);
}

public function testReturn(): void
{
$this->compare(
fn() => (function () {
yield 1;
return 2;
})(),
[
'current', 'key', 'current', 'key', 'valid',
'next',
'getReturn',
],
);
}

public function testEmpty(): void
{
$this->compare(
fn() => (function () {
yield from [];
})(),
[
'current', 'key', 'current', 'key', 'valid',
'next',
'rewind',
],
);
}

public function testEmptyReturn(): void
{
$this->compare(
fn() => (function () {
return;
yield;
})(),
[
'current', 'key', 'current', 'key', 'valid',
'next',
'getReturn',
],
);
}

public function testEmptyThrow(): void
{
$this->compare(
fn() => (function () {
throw new \Exception('foo');
yield;
})(),
['current', 'key', 'current', 'key', 'valid', 'getReturn', 'next', 'rewind'],
);
}

public function testEmptyThrowValid(): void
{
$this->compare(
fn() => (function () {
throw new \Exception('foo');
yield;
})(),
[
'valid', 'valid',
],
);
}

public function testEmptyThrowGetReturn(): void
{
$this->compare(
fn() => (function () {
throw new \Exception('foo');
yield;
})(),
[
'getReturn', 'getReturn',
],
);
}

/**
* @param callable(): \Generator $generatorFactory
* @param iterable<Action|int, array{Action, mixed}> $actions
* @return void
*/
private function compare(
callable $generatorFactory,
iterable $actions,
): void {
$c1 = $c2 = null;
$caught = false;
$gen = $generatorFactory();
$def = DeferredGenerator::fromGenerator($generatorFactory());
$def->catch(function (\Throwable $e) use (&$c1) {
$c1 = $e;
});
$lazy = DeferredGenerator::fromHandler($generatorFactory, EncodedValues::empty());
$lazy->catch(function (\Throwable $e) use (&$c2) {
$c2 = $e;
});


$i = 0;
foreach ($actions as $tuple) {
++$i;
$argLess = \is_string($tuple);
$method = $argLess ? $tuple : $tuple[0];
$arg = $argLess ? null : $tuple[1];
$c1 = $c2 = $e = $e2 = $e3 = $result = $result2 = $result3 = null;

try {
$result = $argLess ? $gen->$method() : $gen->$method($arg);
} catch (\Throwable $e) {
# ignore
}

try {
$result2 = $argLess ? $def->$method() : $def->$method($arg);
} catch (\Throwable $e2) {
# ignore
}

try {
$result3 = $argLess ? $lazy->$method() : $lazy->$method($arg);
} catch (\Throwable $e3) {
# ignore
}

$this->assertSame($result, $result2, "Generator and DeferredGenerator results differ [$i] `$method`");
$this->assertSame($result, $result3, "Generator and DeferredGenerator results differ [$i] `$method`");
if ($caught) {
$this->assertNull($c1, "Error was caught twice [$i] `$method`");
$this->assertNull($c2, "Error was caught twice [$i] `$method`");
}
if ($e !== null) {
$this->assertNotNull($e2, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
$this->assertNotNull($e3, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
if (!$caught && !\in_array($method, ['rewind'], true)) {
$this->assertNotNull($c1, "Error was not caught [$i] `$method`");
$this->assertNotNull($c2, "Error was not caught [$i] `$method`");
$caught = true;
}
} else {
$this->assertNull($e2, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
$this->assertNull($e3, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
$this->assertNull($c1, "There must be no error caught [$i] `$method`");
$this->assertNull($c2, "There must be no error caught [$i] `$method`");
}
}
}
}

0 comments on commit 589242f

Please sign in to comment.