Skip to content

Commit 6ccb261

Browse files
authored
Stop event loop if exception is thrown from test (#9)
1 parent 99db640 commit 6ccb261

2 files changed

Lines changed: 51 additions & 6 deletions

File tree

src/AsyncTestCase.php

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ abstract class AsyncTestCase extends PHPUnitTestCase
3737
/** @var \Generator|null */
3838
private $generator;
3939

40+
/** @var int|null */
41+
private $timeout;
42+
43+
final protected function runTest()
44+
{
45+
parent::setName('runAsyncTest');
46+
return parent::runTest();
47+
}
48+
4049
/** @internal */
4150
final public function runAsyncTest(...$args)
4251
{
@@ -61,6 +70,28 @@ final public function runAsyncTest(...$args)
6170
$invoked = true;
6271
$exception = $error;
6372
$returnValue = $value;
73+
74+
if ($this->timeout === null) {
75+
Loop::unreference(Loop::defer(function () use ($exception) {
76+
Loop::stop();
77+
78+
if ($exception) {
79+
$this->fail(\sprintf(
80+
'An exception was thrown from the test method or promise returned from test method failed,'
81+
. ' but the event loop continued to run; set a timeout with %s::setTimeout() to allow the loop to continue'
82+
. ' to run for a given period of time; Exception thrown: %s',
83+
self::class,
84+
$exception
85+
));
86+
}
87+
88+
$this->fail(\sprintf(
89+
'The event loop continued to run after the test method completed or the promise returned resolved;'
90+
. ' set a timeout with %s::setTimeout() to allow the loop to continue to run for a given period of time',
91+
self::class
92+
));
93+
}));
94+
}
6495
});
6596
});
6697

@@ -124,12 +155,6 @@ private function runAsyncTestCycle(array $args): \Generator
124155
return $returnValue;
125156
}
126157

127-
final protected function runTest()
128-
{
129-
parent::setName('runAsyncTest');
130-
return parent::runTest();
131-
}
132-
133158
/**
134159
* Called before each test. Similar to {@see TestCase::setUp()}, except the method may return a promise or
135160
* coroutine (@see \Amp\call()} that will be awaited before executing the test.
@@ -169,6 +194,8 @@ final protected function setMinimumRuntime(int $runtime)
169194
*/
170195
final protected function setTimeout(int $timeout)
171196
{
197+
$this->timeout = $timeout;
198+
172199
$this->timeoutId = Loop::delay($timeout, function () use ($timeout) {
173200
Loop::stop();
174201
Loop::setErrorHandler(null);

test/AsyncTestCaseTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ public function testSetMinimumRunTime(): \Generator
163163
public function testSetMinimumRunTimeWithWatchersOnly()
164164
{
165165
$this->setMinimumRuntime(100);
166+
$this->setTimeout(200);
166167
Loop::delay(100, $this->createCallback(1));
167168
}
168169

@@ -182,4 +183,21 @@ public function testCreateCallback()
182183

183184
$this->assertSame(2, $mock(1));
184185
}
186+
187+
public function testIssue8()
188+
{
189+
$this->expectException(AssertionFailedError::class);
190+
$this->expectExceptionMessage('Exception thrown: ');
191+
192+
Loop::repeat(1000, static function () {});
193+
throw new \Exception('Test exception');
194+
}
195+
196+
public function testLoopContinuesToRunAfterResolve()
197+
{
198+
$this->expectException(AssertionFailedError::class);
199+
$this->expectExceptionMessage('event loop continued to run');
200+
201+
Loop::repeat(1000, static function () {});
202+
}
185203
}

0 commit comments

Comments
 (0)