Skip to content

Commit 589242f

Browse files
committed
Add unit tests for DeferredGenerator
1 parent 7f8e2e5 commit 589242f

File tree

2 files changed

+254
-33
lines changed

2 files changed

+254
-33
lines changed

src/Internal/Workflow/Process/DeferredGenerator.php

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ final class DeferredGenerator implements \Iterator
1818
{
1919
private bool $started = false;
2020
private bool $finished = false;
21-
private mixed $key = null;
22-
private mixed $value = null;
23-
private mixed $result = null;
2421
private \Generator $generator;
2522

2623
/** @var array<\Closure(\Throwable): mixed> */
@@ -50,7 +47,6 @@ public static function fromGenerator(\Generator $generator): self
5047
$self = new self();
5148
$self->generator = $generator;
5249
$self->started = true;
53-
$self->fill();
5450
return $self;
5551
}
5652

@@ -67,10 +63,8 @@ public function throw(\Throwable $exception): void
6763
);
6864
try {
6965
$this->generator->throw($exception);
70-
$this->fill();
7166
} catch (\Throwable $e) {
7267
$this->handleException($e);
73-
throw $e;
7468
}
7569
}
7670

@@ -81,15 +75,12 @@ public function throw(\Throwable $exception): void
8175
*/
8276
public function send(mixed $value): mixed
8377
{
84-
$this->started or throw new \LogicException('Cannot send value to a generator that was not started.');
78+
$this->start();
8579
$this->finished and throw new \LogicException('Cannot send value to a generator that was already finished.');
8680
try {
87-
$result = $this->generator->send($value);
88-
$this->fill();
89-
return $result;
81+
return $this->generator->send($value);
9082
} catch (\Throwable $e) {
9183
$this->handleException($e);
92-
throw $e;
9384
}
9485
}
9586

@@ -98,8 +89,11 @@ public function send(mixed $value): mixed
9889
*/
9990
public function getReturn(): mixed
10091
{
101-
$this->finished or throw new \LogicException('Cannot get return value of a generator that was not finished.');
102-
return $this->result;
92+
try {
93+
return $this->generator->getReturn();
94+
} catch (\Throwable $e) {
95+
$this->handleException($e);
96+
}
10397
}
10498

10599
/**
@@ -108,7 +102,11 @@ public function getReturn(): mixed
108102
public function current(): mixed
109103
{
110104
$this->start();
111-
return $this->value;
105+
try {
106+
return $this->generator->current();
107+
} catch (\Throwable $e) {
108+
$this->handleException($e);
109+
}
112110
}
113111

114112
/**
@@ -117,7 +115,7 @@ public function current(): mixed
117115
public function key(): mixed
118116
{
119117
$this->start();
120-
return $this->key;
118+
return $this->generator->key();
121119
}
122120

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

133131
try {
134132
$this->generator->next();
135-
$this->fill();
136133
} catch (\Throwable $e) {
137134
$this->handleException($e);
138-
throw $e;
139135
}
140136
}
141137

@@ -147,12 +143,16 @@ public function next(): void
147143
public function valid(): bool
148144
{
149145
$this->start();
150-
return !$this->finished;
146+
try {
147+
return $this->generator->valid();
148+
} catch (\Throwable $e) {
149+
$this->handleException($e);
150+
}
151151
}
152152

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

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

179179
if ($result instanceof \Generator) {
180180
$this->generator = $result;
181-
$this->fill();
182181
return;
183182
}
184183

185-
$this->result = $result;
186184
$this->finished = true;
187185
} catch (\Throwable $e) {
188186
$this->handleException($e);
189-
throw $e;
190187
} finally {
191188
unset($this->handler, $this->values);
192189
}
193190
}
194191

195-
private function fill(): void
192+
private function handleException(\Throwable $e): never
196193
{
197-
$this->key = $this->generator->key();
198-
$this->value = $this->generator->current();
199-
$this->finished = !$this->generator->valid() and $this->result = $this->generator->getReturn();
200-
}
201-
202-
private function handleException(\Throwable $e): void
203-
{
204-
$this->key = null;
205-
$this->value = null;
194+
$this->finished and throw $e;
206195
$this->finished = true;
207196
foreach ($this->catchers as $catch) {
208-
$catch($e);
197+
try {
198+
$catch($e);
199+
} catch (\Throwable) {
200+
// Do nothing.
201+
}
209202
}
203+
204+
$this->catchers = [];
205+
throw $e;
210206
}
211207
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Temporal\Tests\Unit\Workflow;
6+
7+
use PHPUnit\Framework\TestCase;
8+
use Temporal\DataConverter\EncodedValues;
9+
use Temporal\Internal\Workflow\Process\DeferredGenerator;
10+
11+
/**
12+
* @psalm-type Action = 'current'|'send'|'key'|'next'|'valid'|'rewind'|'getReturn'
13+
*/
14+
final class DeferredGeneratorTestCase extends TestCase
15+
{
16+
public function testSimple(): void
17+
{
18+
$this->compare(
19+
fn() => (function () {
20+
yield 1;
21+
yield 42 => 2;
22+
yield 3;
23+
})(),
24+
[
25+
'current', 'key', 'current', 'key',
26+
'next',
27+
'current', 'key', 'current', 'key', 'valid',
28+
'next',
29+
['send', 'foo'],
30+
'current', 'key', 'current', 'key', 'valid',
31+
],
32+
);
33+
}
34+
35+
public function testSendingValues(): void
36+
{
37+
$this->compare(
38+
fn() => (function () {
39+
$a = yield;
40+
$b = yield $a;
41+
$c = yield $b;
42+
return [$a, $b, $c];
43+
})(),
44+
[
45+
['send', 'foo'],
46+
['send', 'bar'],
47+
['send', 'baz'],
48+
'current', 'key', 'current', 'key', 'valid',
49+
],
50+
);
51+
}
52+
53+
public function testThrowingExceptions(): void
54+
{
55+
$this->compare(
56+
fn() => (function () {
57+
try {
58+
yield;
59+
throw new \Exception('foo');
60+
} catch (\Exception $e) {
61+
yield $e->getMessage();
62+
}
63+
})(),
64+
[
65+
'current', 'key', 'current', 'key', 'valid',
66+
'next',
67+
'current', 'key', 'current', 'key', 'valid',
68+
'next',
69+
'rewind',
70+
],
71+
);
72+
}
73+
74+
public function testReturn(): void
75+
{
76+
$this->compare(
77+
fn() => (function () {
78+
yield 1;
79+
return 2;
80+
})(),
81+
[
82+
'current', 'key', 'current', 'key', 'valid',
83+
'next',
84+
'getReturn',
85+
],
86+
);
87+
}
88+
89+
public function testEmpty(): void
90+
{
91+
$this->compare(
92+
fn() => (function () {
93+
yield from [];
94+
})(),
95+
[
96+
'current', 'key', 'current', 'key', 'valid',
97+
'next',
98+
'rewind',
99+
],
100+
);
101+
}
102+
103+
public function testEmptyReturn(): void
104+
{
105+
$this->compare(
106+
fn() => (function () {
107+
return;
108+
yield;
109+
})(),
110+
[
111+
'current', 'key', 'current', 'key', 'valid',
112+
'next',
113+
'getReturn',
114+
],
115+
);
116+
}
117+
118+
public function testEmptyThrow(): void
119+
{
120+
$this->compare(
121+
fn() => (function () {
122+
throw new \Exception('foo');
123+
yield;
124+
})(),
125+
['current', 'key', 'current', 'key', 'valid', 'getReturn', 'next', 'rewind'],
126+
);
127+
}
128+
129+
public function testEmptyThrowValid(): void
130+
{
131+
$this->compare(
132+
fn() => (function () {
133+
throw new \Exception('foo');
134+
yield;
135+
})(),
136+
[
137+
'valid', 'valid',
138+
],
139+
);
140+
}
141+
142+
public function testEmptyThrowGetReturn(): void
143+
{
144+
$this->compare(
145+
fn() => (function () {
146+
throw new \Exception('foo');
147+
yield;
148+
})(),
149+
[
150+
'getReturn', 'getReturn',
151+
],
152+
);
153+
}
154+
155+
/**
156+
* @param callable(): \Generator $generatorFactory
157+
* @param iterable<Action|int, array{Action, mixed}> $actions
158+
* @return void
159+
*/
160+
private function compare(
161+
callable $generatorFactory,
162+
iterable $actions,
163+
): void {
164+
$c1 = $c2 = null;
165+
$caught = false;
166+
$gen = $generatorFactory();
167+
$def = DeferredGenerator::fromGenerator($generatorFactory());
168+
$def->catch(function (\Throwable $e) use (&$c1) {
169+
$c1 = $e;
170+
});
171+
$lazy = DeferredGenerator::fromHandler($generatorFactory, EncodedValues::empty());
172+
$lazy->catch(function (\Throwable $e) use (&$c2) {
173+
$c2 = $e;
174+
});
175+
176+
177+
$i = 0;
178+
foreach ($actions as $tuple) {
179+
++$i;
180+
$argLess = \is_string($tuple);
181+
$method = $argLess ? $tuple : $tuple[0];
182+
$arg = $argLess ? null : $tuple[1];
183+
$c1 = $c2 = $e = $e2 = $e3 = $result = $result2 = $result3 = null;
184+
185+
try {
186+
$result = $argLess ? $gen->$method() : $gen->$method($arg);
187+
} catch (\Throwable $e) {
188+
# ignore
189+
}
190+
191+
try {
192+
$result2 = $argLess ? $def->$method() : $def->$method($arg);
193+
} catch (\Throwable $e2) {
194+
# ignore
195+
}
196+
197+
try {
198+
$result3 = $argLess ? $lazy->$method() : $lazy->$method($arg);
199+
} catch (\Throwable $e3) {
200+
# ignore
201+
}
202+
203+
$this->assertSame($result, $result2, "Generator and DeferredGenerator results differ [$i] `$method`");
204+
$this->assertSame($result, $result3, "Generator and DeferredGenerator results differ [$i] `$method`");
205+
if ($caught) {
206+
$this->assertNull($c1, "Error was caught twice [$i] `$method`");
207+
$this->assertNull($c2, "Error was caught twice [$i] `$method`");
208+
}
209+
if ($e !== null) {
210+
$this->assertNotNull($e2, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
211+
$this->assertNotNull($e3, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
212+
if (!$caught && !\in_array($method, ['rewind'], true)) {
213+
$this->assertNotNull($c1, "Error was not caught [$i] `$method`");
214+
$this->assertNotNull($c2, "Error was not caught [$i] `$method`");
215+
$caught = true;
216+
}
217+
} else {
218+
$this->assertNull($e2, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
219+
$this->assertNull($e3, "Generator and DeferredGenerator exceptions differ [$i] `$method`");
220+
$this->assertNull($c1, "There must be no error caught [$i] `$method`");
221+
$this->assertNull($c2, "There must be no error caught [$i] `$method`");
222+
}
223+
}
224+
}
225+
}

0 commit comments

Comments
 (0)