From 0fdd6a4f55c37a9c99dea12761d518b7fc599d8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Oct 2022 11:26:02 +0200 Subject: [PATCH 1/2] Add PHPStan to test environment --- .gitattributes | 1 + .github/workflows/ci.yml | 17 +++++++++++++++++ README.md | 6 ++++++ composer.json | 1 + phpstan.neon.dist | 11 +++++++++++ src/functions.php | 6 +++--- tests/AsyncTest.php | 4 ++++ tests/AwaitTest.php | 2 +- tests/CoroutineTest.php | 4 ++++ tests/ParallelTest.php | 1 + tests/SeriesTest.php | 1 + tests/WaterfallTest.php | 1 + 12 files changed, 51 insertions(+), 4 deletions(-) create mode 100644 phpstan.neon.dist diff --git a/.gitattributes b/.gitattributes index aa6c312..838c8fa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,5 +1,6 @@ /.gitattributes export-ignore /.github/ export-ignore /.gitignore export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e13e6f..75213de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,3 +22,20 @@ jobs: ini-file: development - run: composer install - run: vendor/bin/phpunit --coverage-text + + PHPStan: + name: PHPStan (PHP ${{ matrix.php }}) + runs-on: ubuntu-22.04 + strategy: + matrix: + php: + - 8.2 + - 8.1 + steps: + - uses: actions/checkout@v3 + - uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + coverage: none + - run: composer install + - run: vendor/bin/phpstan diff --git a/README.md b/README.md index 4380c86..71a6288 100644 --- a/README.md +++ b/README.md @@ -659,6 +659,12 @@ To run the test suite, go to the project root and run: vendor/bin/phpunit ``` +On top of this, we use PHPStan on level 3 to ensure type safety across the project: + +```bash +vendor/bin/phpstan +``` + ## License MIT, see [LICENSE file](LICENSE). diff --git a/composer.json b/composer.json index 2d1ad50..848dd38 100644 --- a/composer.json +++ b/composer.json @@ -31,6 +31,7 @@ "react/promise": "^3.0 || ^2.8 || ^1.2.1" }, "require-dev": { + "phpstan/phpstan": "1.10.18", "phpunit/phpunit": "^9.5" }, "autoload": { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..903fb1f --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,11 @@ +parameters: + level: 3 + + paths: + - src/ + - tests/ + + reportUnmatchedIgnoredErrors: false + ignoreErrors: + # ignore generic usage like `PromiseInterface` until fixed upstream + - '/^PHPDoc .* contains generic type React\\Promise\\PromiseInterface<.+> but interface React\\Promise\\PromiseInterface is not generic\.$/' diff --git a/src/functions.php b/src/functions.php index 797911b..12f7d03 100644 --- a/src/functions.php +++ b/src/functions.php @@ -177,7 +177,7 @@ * ``` * * @param callable(mixed ...$args):mixed $function - * @return callable(): PromiseInterface + * @return callable(mixed ...$args): PromiseInterface * @since 4.0.0 * @see coroutine() */ @@ -587,7 +587,7 @@ function delay(float $seconds): void * }); * ``` * - * @param callable(...$args):\Generator $function + * @param callable(mixed ...$args):\Generator $function * @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is * @return PromiseInterface * @since 3.0.0 @@ -730,9 +730,9 @@ function series(iterable $tasks): PromiseInterface assert($tasks instanceof \Iterator); } - /** @var callable():void $next */ $taskCallback = function ($result) use (&$results, &$next) { $results[] = $result; + assert($next instanceof \Closure); $next(); }; diff --git a/tests/AsyncTest.php b/tests/AsyncTest.php index 7698b42..6e07112 100644 --- a/tests/AsyncTest.php +++ b/tests/AsyncTest.php @@ -194,6 +194,7 @@ public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPro })); })(); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled'))); @@ -211,6 +212,7 @@ public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPr } })(); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then($this->expectCallableOnceWith(42)); @@ -230,6 +232,7 @@ public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseR } })(); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableNever()); @@ -259,6 +262,7 @@ public function testCancelAsyncWillCancelNestedAwait() return time(); })(); + assert(method_exists($promise, 'cancel')); $promise->cancel(); await($promise); } diff --git a/tests/AwaitTest.php b/tests/AwaitTest.php index 3d2b886..3333062 100644 --- a/tests/AwaitTest.php +++ b/tests/AwaitTest.php @@ -361,7 +361,7 @@ public function testNestedAwaits(callable $await) $resolve($await(new Promise(function ($resolve) use ($await) { $resolve($await(new Promise(function ($resolve) use ($await) { $resolve($await(new Promise(function ($resolve) use ($await) { - $resolve($await(new Promise(function ($resolve) use ($await) { + $resolve($await(new Promise(function ($resolve) { Loop::addTimer(0.01, function () use ($resolve) { $resolve(true); }); diff --git a/tests/CoroutineTest.php b/tests/CoroutineTest.php index adc82bc..5ec4cde 100644 --- a/tests/CoroutineTest.php +++ b/tests/CoroutineTest.php @@ -114,6 +114,7 @@ public function testCancelCoroutineWillReturnRejectedPromiseWhenCancellingPendin }); }); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled'))); @@ -131,6 +132,7 @@ public function testCancelCoroutineWillReturnFulfilledPromiseWhenCancellingPendi } }); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then($this->expectCallableOnceWith(42)); @@ -150,6 +152,7 @@ public function testCancelCoroutineWillReturnPendigPromiseWhenCancellingFirstPro } }); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then($this->expectCallableNever(), $this->expectCallableNever()); @@ -209,6 +212,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseReject }); }); + assert(method_exists($promise, 'cancel')); $promise->cancel(); unset($promise); diff --git a/tests/ParallelTest.php b/tests/ParallelTest.php index 1a5759b..98bbce2 100644 --- a/tests/ParallelTest.php +++ b/tests/ParallelTest.php @@ -193,6 +193,7 @@ function () use (&$cancelled) { ); $promise = React\Async\parallel($tasks); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $this->assertSame(2, $cancelled); diff --git a/tests/SeriesTest.php b/tests/SeriesTest.php index 404c907..0bc5017 100644 --- a/tests/SeriesTest.php +++ b/tests/SeriesTest.php @@ -185,6 +185,7 @@ function () use (&$cancelled) { ); $promise = React\Async\series($tasks); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $this->assertSame(1, $cancelled); diff --git a/tests/WaterfallTest.php b/tests/WaterfallTest.php index 2fbbc23..d2f947f 100644 --- a/tests/WaterfallTest.php +++ b/tests/WaterfallTest.php @@ -199,6 +199,7 @@ function () use (&$cancelled) { ); $promise = React\Async\waterfall($tasks); + assert(method_exists($promise, 'cancel')); $promise->cancel(); $this->assertSame(1, $cancelled); From 6185725a7bf708f030fa456fb9edc986c222caf4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20L=C3=BCck?= Date: Wed, 26 Oct 2022 12:37:20 +0200 Subject: [PATCH 2/2] Improve type definitions and update to PHPStan level `max` --- README.md | 2 +- phpstan.neon.dist | 2 +- src/FiberMap.php | 12 +++++++++-- src/SimpleFiber.php | 10 +++++++++- src/functions.php | 25 ++++++++++++++++++----- tests/AsyncTest.php | 31 +++++++++++++++-------------- tests/AwaitTest.php | 44 ++++++++++++++++++++++------------------- tests/CoroutineTest.php | 42 +++++++++++++++++++-------------------- tests/DelayTest.php | 16 +++++++++------ tests/ParallelTest.php | 25 ++++++++++++----------- tests/SeriesTest.php | 27 +++++++++++++------------ tests/TestCase.php | 25 +++++++++++------------ tests/Timer.php | 18 ++++++++--------- tests/WaterfallTest.php | 27 +++++++++++++------------ 14 files changed, 173 insertions(+), 133 deletions(-) diff --git a/README.md b/README.md index 71a6288..523dd67 100644 --- a/README.md +++ b/README.md @@ -659,7 +659,7 @@ To run the test suite, go to the project root and run: vendor/bin/phpunit ``` -On top of this, we use PHPStan on level 3 to ensure type safety across the project: +On top of this, we use PHPStan on max level to ensure type safety across the project: ```bash vendor/bin/phpstan diff --git a/phpstan.neon.dist b/phpstan.neon.dist index 903fb1f..b7f8ddb 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,5 +1,5 @@ parameters: - level: 3 + level: max paths: - src/ diff --git a/src/FiberMap.php b/src/FiberMap.php index 36846b4..0648788 100644 --- a/src/FiberMap.php +++ b/src/FiberMap.php @@ -9,35 +9,43 @@ */ final class FiberMap { + /** @var array */ private static array $status = []; - private static array $map = []; + /** @var array */ + private static array $map = []; + + /** @param \Fiber $fiber */ public static function register(\Fiber $fiber): void { self::$status[\spl_object_id($fiber)] = false; - self::$map[\spl_object_id($fiber)] = []; } + /** @param \Fiber $fiber */ public static function cancel(\Fiber $fiber): void { self::$status[\spl_object_id($fiber)] = true; } + /** @param \Fiber $fiber */ public static function setPromise(\Fiber $fiber, PromiseInterface $promise): void { self::$map[\spl_object_id($fiber)] = $promise; } + /** @param \Fiber $fiber */ public static function unsetPromise(\Fiber $fiber, PromiseInterface $promise): void { unset(self::$map[\spl_object_id($fiber)]); } + /** @param \Fiber $fiber */ public static function getPromise(\Fiber $fiber): ?PromiseInterface { return self::$map[\spl_object_id($fiber)] ?? null; } + /** @param \Fiber $fiber */ public static function unregister(\Fiber $fiber): void { unset(self::$status[\spl_object_id($fiber)], self::$map[\spl_object_id($fiber)]); diff --git a/src/SimpleFiber.php b/src/SimpleFiber.php index acf3fad..8c5460a 100644 --- a/src/SimpleFiber.php +++ b/src/SimpleFiber.php @@ -9,8 +9,12 @@ */ final class SimpleFiber implements FiberInterface { + /** @var ?\Fiber */ private static ?\Fiber $scheduler = null; + private static ?\Closure $suspend = null; + + /** @var ?\Fiber */ private ?\Fiber $fiber = null; public function __construct() @@ -57,13 +61,17 @@ public function suspend(): mixed self::$scheduler = new \Fiber(static fn() => Loop::run()); // Run event loop to completion on shutdown. \register_shutdown_function(static function (): void { + assert(self::$scheduler instanceof \Fiber); if (self::$scheduler->isSuspended()) { self::$scheduler->resume(); } }); } - return (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start())(); + $ret = (self::$scheduler->isStarted() ? self::$scheduler->resume() : self::$scheduler->start()); + assert(\is_callable($ret)); + + return $ret(); } return \Fiber::suspend(); diff --git a/src/functions.php b/src/functions.php index 12f7d03..a3ce111 100644 --- a/src/functions.php +++ b/src/functions.php @@ -176,8 +176,8 @@ * await($promise); * ``` * - * @param callable(mixed ...$args):mixed $function - * @return callable(mixed ...$args): PromiseInterface + * @param callable $function + * @return callable(mixed ...): PromiseInterface * @since 4.0.0 * @see coroutine() */ @@ -192,6 +192,7 @@ function async(callable $function): callable } catch (\Throwable $exception) { $reject($exception); } finally { + assert($fiber instanceof \Fiber); FiberMap::unregister($fiber); } }); @@ -200,6 +201,7 @@ function async(callable $function): callable $fiber->start(); }, function () use (&$fiber): void { + assert($fiber instanceof \Fiber); FiberMap::cancel($fiber); $promise = FiberMap::getPromise($fiber); if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { @@ -287,6 +289,7 @@ function (mixed $value) use (&$resolved, &$resolvedValue, &$fiber, $lowLevelFibe FiberMap::unsetPromise($lowLevelFiber, $promise); } + /** @var ?\Fiber $fiber */ if ($fiber === null) { $resolved = true; $resolvedValue = $value; @@ -309,6 +312,7 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL // what a lovely piece of code! $r = new \ReflectionProperty('Exception', 'trace'); $trace = $r->getValue($throwable); + assert(\is_array($trace)); // Exception trace arguments only available when zend.exception_ignore_args is not set // @codeCoverageIgnoreStart @@ -340,6 +344,7 @@ function (mixed $throwable) use (&$rejected, &$rejectedThrowable, &$fiber, $lowL } if ($rejected) { + assert($rejectedThrowable instanceof \Throwable); throw $rejectedThrowable; } @@ -587,7 +592,7 @@ function delay(float $seconds): void * }); * ``` * - * @param callable(mixed ...$args):\Generator $function + * @param callable(mixed ...$args):(\Generator|mixed) $function * @param mixed ...$args Optional list of additional arguments that will be passed to the given `$function` as is * @return PromiseInterface * @since 3.0.0 @@ -606,6 +611,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface $promise = null; $deferred = new Deferred(function () use (&$promise) { + /** @var ?PromiseInterface $promise */ if ($promise instanceof PromiseInterface && \method_exists($promise, 'cancel')) { $promise->cancel(); } @@ -626,6 +632,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface return; } + /** @var mixed $promise */ $promise = $generator->current(); if (!$promise instanceof PromiseInterface) { $next = null; @@ -635,6 +642,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface return; } + assert($next instanceof \Closure); $promise->then(function ($value) use ($generator, $next) { $generator->send($value); $next(); @@ -657,6 +665,7 @@ function coroutine(callable $function, mixed ...$args): PromiseInterface */ function parallel(iterable $tasks): PromiseInterface { + /** @var array $pending */ $pending = []; $deferred = new Deferred(function () use (&$pending) { foreach ($pending as $promise) { @@ -718,6 +727,7 @@ function series(iterable $tasks): PromiseInterface { $pending = null; $deferred = new Deferred(function () use (&$pending) { + /** @var ?PromiseInterface $pending */ if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) { $pending->cancel(); } @@ -732,7 +742,7 @@ function series(iterable $tasks): PromiseInterface $taskCallback = function ($result) use (&$results, &$next) { $results[] = $result; - assert($next instanceof \Closure); + /** @var \Closure $next */ $next(); }; @@ -746,9 +756,11 @@ function series(iterable $tasks): PromiseInterface $task = $tasks->current(); $tasks->next(); } else { + assert(\is_array($tasks)); $task = \array_shift($tasks); } + assert(\is_callable($task)); $promise = \call_user_func($task); assert($promise instanceof PromiseInterface); $pending = $promise; @@ -762,13 +774,14 @@ function series(iterable $tasks): PromiseInterface } /** - * @param iterable> $tasks + * @param iterable<(callable():PromiseInterface)|(callable(mixed):PromiseInterface)> $tasks * @return PromiseInterface */ function waterfall(iterable $tasks): PromiseInterface { $pending = null; $deferred = new Deferred(function () use (&$pending) { + /** @var ?PromiseInterface $pending */ if ($pending instanceof PromiseInterface && \method_exists($pending, 'cancel')) { $pending->cancel(); } @@ -791,9 +804,11 @@ function waterfall(iterable $tasks): PromiseInterface $task = $tasks->current(); $tasks->next(); } else { + assert(\is_array($tasks)); $task = \array_shift($tasks); } + assert(\is_callable($task)); $promise = \call_user_func_array($task, func_get_args()); assert($promise instanceof PromiseInterface); $pending = $promise; diff --git a/tests/AsyncTest.php b/tests/AsyncTest.php index 6e07112..70a84b1 100644 --- a/tests/AsyncTest.php +++ b/tests/AsyncTest.php @@ -14,7 +14,7 @@ class AsyncTest extends TestCase { - public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsValue() + public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsValue(): void { $promise = async(function () { return 42; @@ -28,7 +28,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsV $this->assertEquals(42, $value); } - public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsPromiseThatFulfillsWithValue() + public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsPromiseThatFulfillsWithValue(): void { $promise = async(function () { return resolve(42); @@ -42,7 +42,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsP $this->assertEquals(42, $value); } - public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrows() + public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrows(): void { $promise = async(function () { throw new \RuntimeException('Foo', 42); @@ -59,7 +59,7 @@ public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrow $this->assertEquals(42, $exception->getCode()); } - public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackReturnsPromiseThatRejectsWithException() + public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackReturnsPromiseThatRejectsWithException(): void { $promise = async(function () { return reject(new \RuntimeException('Foo', 42)); @@ -76,7 +76,7 @@ public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackRetur $this->assertEquals(42, $exception->getCode()); } - public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise() + public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise(): void { $promise = async(function () { return new Promise(function () { }); @@ -85,7 +85,7 @@ public function testAsyncReturnsPendingPromiseWhenCallbackReturnsPendingPromise( $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmediatelyWhenPromiseIsFulfilled() + public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmediatelyWhenPromiseIsFulfilled(): void { $deferred = new Deferred(); @@ -105,7 +105,7 @@ public function testAsyncWithAwaitReturnsReturnsPromiseFulfilledWithValueImmedia $this->assertEquals(42, $return); } - public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediatelyWhenPromiseIsRejected() + public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediatelyWhenPromiseIsRejected(): void { $deferred = new Deferred(); @@ -122,13 +122,13 @@ public function testAsyncWithAwaitReturnsPromiseRejectedWithExceptionImmediately $deferred->reject(new \RuntimeException('Test', 42)); + /** @var \RuntimeException $exception */ $this->assertInstanceof(\RuntimeException::class, $exception); - assert($exception instanceof \RuntimeException); $this->assertEquals('Test', $exception->getMessage()); $this->assertEquals(42, $exception->getCode()); } - public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingPromise() + public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingPromise(): void { $promise = async(function () { $promise = new Promise(function ($resolve) { @@ -143,7 +143,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA $this->assertEquals(42, $value); } - public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrowsAfterAwaitingPromise() + public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrowsAfterAwaitingPromise(): void { $promise = async(function () { $promise = new Promise(function ($_, $reject) { @@ -159,7 +159,7 @@ public function testAsyncReturnsPromiseThatRejectsWithExceptionWhenCallbackThrow await($promise); } - public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingTwoConcurrentPromises() + public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsAfterAwaitingTwoConcurrentPromises(): void { $promise1 = async(function () { $promise = new Promise(function ($resolve) { @@ -174,6 +174,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA Loop::addTimer(0.11, fn () => $resolve($theAnswerToLifeTheUniverseAndEverything)); }); + /** @var int */ return await($promise); })(42); @@ -186,7 +187,7 @@ public function testAsyncReturnsPromiseThatFulfillsWithValueWhenCallbackReturnsA $this->assertLessThan(0.12, $time); } - public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects() + public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects(): void { $promise = async(function () { await(new Promise(function () { }, function () { @@ -200,7 +201,7 @@ public function testCancelAsyncWillReturnRejectedPromiseWhenCancellingPendingPro $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled'))); } - public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue() + public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue(): void { $promise = async(function () { try { @@ -218,7 +219,7 @@ public function testCancelAsyncWillReturnFulfilledPromiseWhenCancellingPendingPr $promise->then($this->expectCallableOnceWith(42)); } - public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatAwaitsSecondPromise() + public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatAwaitsSecondPromise(): void { $promise = async(function () { try { @@ -238,7 +239,7 @@ public function testCancelAsycWillReturnPendigPromiseWhenCancellingFirstPromiseR $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testCancelAsyncWillCancelNestedAwait() + public function testCancelAsyncWillCancelNestedAwait(): void { self::expectOutputString('abc'); $this->expectException(\RuntimeException::class); diff --git a/tests/AwaitTest.php b/tests/AwaitTest.php index 3333062..3158a1b 100644 --- a/tests/AwaitTest.php +++ b/tests/AwaitTest.php @@ -6,6 +6,7 @@ use React\EventLoop\Loop; use React\Promise\Deferred; use React\Promise\Promise; +use React\Promise\PromiseInterface; use function React\Async\async; class AwaitTest extends TestCase @@ -13,7 +14,7 @@ class AwaitTest extends TestCase /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsExceptionWhenPromiseIsRejectedWithException(callable $await) + public function testAwaitThrowsExceptionWhenPromiseIsRejectedWithException(callable $await): void { $promise = new Promise(function () { throw new \Exception('test'); @@ -27,7 +28,7 @@ public function testAwaitThrowsExceptionWhenPromiseIsRejectedWithException(calla /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsExceptionWithoutRunningLoop(callable $await) + public function testAwaitThrowsExceptionWithoutRunningLoop(callable $await): void { $now = true; Loop::futureTick(function () use (&$now) { @@ -48,7 +49,7 @@ public function testAwaitThrowsExceptionWithoutRunningLoop(callable $await) /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsExceptionImmediatelyWhenPromiseIsRejected(callable $await) + public function testAwaitThrowsExceptionImmediatelyWhenPromiseIsRejected(callable $await): void { $deferred = new Deferred(); @@ -72,7 +73,7 @@ public function testAwaitThrowsExceptionImmediatelyWhenPromiseIsRejected(callabl /** * @dataProvider provideAwaiters */ - public function testAwaitAsyncThrowsExceptionImmediatelyWhenPromiseIsRejected(callable $await) + public function testAwaitAsyncThrowsExceptionImmediatelyWhenPromiseIsRejected(callable $await): void { $deferred = new Deferred(); @@ -100,7 +101,7 @@ public function testAwaitAsyncThrowsExceptionImmediatelyWhenPromiseIsRejected(ca /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsExceptionImmediatelyInCustomFiberWhenPromiseIsRejected(callable $await) + public function testAwaitThrowsExceptionImmediatelyInCustomFiberWhenPromiseIsRejected(callable $await): void { $fiber = new \Fiber(function () use ($await) { $promise = new Promise(function ($resolve) { @@ -121,7 +122,7 @@ public function testAwaitThrowsExceptionImmediatelyInCustomFiberWhenPromiseIsRej /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithFalse(callable $await) + public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithFalse(callable $await): void { if (!interface_exists('React\Promise\CancellablePromiseInterface')) { $this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3'); @@ -139,7 +140,7 @@ public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWith /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithNull(callable $await) + public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWithNull(callable $await): void { if (!interface_exists('React\Promise\CancellablePromiseInterface')) { $this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3'); @@ -163,7 +164,7 @@ public function testAwaitThrowsUnexpectedValueExceptionWhenPromiseIsRejectedWith /** * @dataProvider provideAwaiters */ - public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError(callable $await) + public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError(callable $await): void { $promise = new Promise(function ($_, $reject) { throw new \Error('Test', 42); @@ -178,7 +179,7 @@ public function testAwaitThrowsErrorWhenPromiseIsRejectedWithError(callable $awa /** * @dataProvider provideAwaiters */ - public function testAwaitReturnsValueWhenPromiseIsFullfilled(callable $await) + public function testAwaitReturnsValueWhenPromiseIsFullfilled(callable $await): void { $promise = new Promise(function ($resolve) { $resolve(42); @@ -190,7 +191,7 @@ public function testAwaitReturnsValueWhenPromiseIsFullfilled(callable $await) /** * @dataProvider provideAwaiters */ - public function testAwaitReturnsValueImmediatelyWithoutRunningLoop(callable $await) + public function testAwaitReturnsValueImmediatelyWithoutRunningLoop(callable $await): void { $now = true; Loop::futureTick(function () use (&$now) { @@ -208,7 +209,7 @@ public function testAwaitReturnsValueImmediatelyWithoutRunningLoop(callable $awa /** * @dataProvider provideAwaiters */ - public function testAwaitReturnsValueImmediatelyWhenPromiseIsFulfilled(callable $await) + public function testAwaitReturnsValueImmediatelyWhenPromiseIsFulfilled(callable $await): void { $deferred = new Deferred(); @@ -229,7 +230,7 @@ public function testAwaitReturnsValueImmediatelyWhenPromiseIsFulfilled(callable /** * @dataProvider provideAwaiters */ - public function testAwaitAsyncReturnsValueImmediatelyWhenPromiseIsFulfilled(callable $await) + public function testAwaitAsyncReturnsValueImmediatelyWhenPromiseIsFulfilled(callable $await): void { $deferred = new Deferred(); @@ -254,7 +255,7 @@ public function testAwaitAsyncReturnsValueImmediatelyWhenPromiseIsFulfilled(call /** * @dataProvider provideAwaiters */ - public function testAwaitReturnsValueImmediatelyInCustomFiberWhenPromiseIsFulfilled(callable $await) + public function testAwaitReturnsValueImmediatelyInCustomFiberWhenPromiseIsFulfilled(callable $await): void { $fiber = new \Fiber(function () use ($await) { $promise = new Promise(function ($resolve) { @@ -273,7 +274,7 @@ public function testAwaitReturnsValueImmediatelyInCustomFiberWhenPromiseIsFulfil /** * @dataProvider provideAwaiters */ - public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise(callable $await) + public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise(callable $await): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -293,7 +294,7 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForResolvedPromise(c /** * @dataProvider provideAwaiters */ - public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise(callable $await) + public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise(callable $await): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -317,7 +318,7 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForRejectedPromise(c /** * @dataProvider provideAwaiters */ - public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithNullValue(callable $await) + public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithNullValue(callable $await): void { if (!interface_exists('React\Promise\CancellablePromiseInterface')) { $this->markTestSkipped('Promises must be rejected with a \Throwable instance since Promise v3'); @@ -345,7 +346,7 @@ public function testAwaitShouldNotCreateAnyGarbageReferencesForPromiseRejectedWi /** * @dataProvider provideAwaiters */ - public function testAlreadyFulfilledPromiseShouldNotSuspendFiber(callable $await) + public function testAlreadyFulfilledPromiseShouldNotSuspendFiber(callable $await): void { for ($i = 0; $i < 6; $i++) { $this->assertSame($i, $await(React\Promise\resolve($i))); @@ -355,7 +356,7 @@ public function testAlreadyFulfilledPromiseShouldNotSuspendFiber(callable $await /** * @dataProvider provideAwaiters */ - public function testNestedAwaits(callable $await) + public function testNestedAwaits(callable $await): void { $this->assertTrue($await(new Promise(function ($resolve) use ($await) { $resolve($await(new Promise(function ($resolve) use ($await) { @@ -375,10 +376,11 @@ public function testNestedAwaits(callable $await) /** * @dataProvider provideAwaiters */ - public function testResolvedPromisesShouldBeDetached(callable $await) + public function testResolvedPromisesShouldBeDetached(callable $await): void { $await(async(function () use ($await): int { $fiber = \Fiber::getCurrent(); + assert($fiber instanceof \Fiber); $await(new Promise(function ($resolve) { Loop::addTimer(0.01, fn() => $resolve(null)); })); @@ -391,13 +393,14 @@ public function testResolvedPromisesShouldBeDetached(callable $await) /** * @dataProvider provideAwaiters */ - public function testRejectedPromisesShouldBeDetached(callable $await) + public function testRejectedPromisesShouldBeDetached(callable $await): void { $this->expectException(\Exception::class); $this->expectExceptionMessage('Boom!'); $await(async(function () use ($await): int { $fiber = \Fiber::getCurrent(); + assert($fiber instanceof \Fiber); try { $await(React\Promise\reject(new \Exception('Boom!'))); } catch (\Throwable $throwable) { @@ -410,6 +413,7 @@ public function testRejectedPromisesShouldBeDetached(callable $await) })()); } + /** @return iterable> */ public function provideAwaiters(): iterable { yield 'await' => [static fn (React\Promise\PromiseInterface $promise): mixed => React\Async\await($promise)]; diff --git a/tests/CoroutineTest.php b/tests/CoroutineTest.php index 5ec4cde..c9b7439 100644 --- a/tests/CoroutineTest.php +++ b/tests/CoroutineTest.php @@ -9,7 +9,7 @@ class CoroutineTest extends TestCase { - public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsWithoutGenerator() + public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsWithoutGenerator(): void { $promise = coroutine(function () { return 42; @@ -18,10 +18,10 @@ public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsWithoutGene $promise->then($this->expectCallableOnceWith(42)); } - public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsImmediately() + public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsImmediately(): void { $promise = coroutine(function () { - if (false) { + if (false) { // @phpstan-ignore-line yield; } return 42; @@ -30,7 +30,7 @@ public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsImmediately $promise->then($this->expectCallableOnceWith(42)); } - public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldingPromise() + public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldingPromise(): void { $promise = coroutine(function () { $value = yield resolve(42); @@ -40,7 +40,7 @@ public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldi $promise->then($this->expectCallableOnceWith(42)); } - public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsWithoutGenerator() + public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsWithoutGenerator(): void { $promise = coroutine(function () { throw new \RuntimeException('Foo'); @@ -49,10 +49,10 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsWithoutGenera $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Foo'))); } - public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsImmediately() + public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsImmediately(): void { $promise = coroutine(function () { - if (false) { + if (false) { // @phpstan-ignore-line yield; } throw new \RuntimeException('Foo'); @@ -61,7 +61,7 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsImmediately() $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Foo'))); } - public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYieldingPromise() + public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYieldingPromise(): void { $promise = coroutine(function () { $reason = yield resolve('Foo'); @@ -71,7 +71,7 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYielding $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Foo'))); } - public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYieldingRejectedPromise() + public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYieldingRejectedPromise(): void { $promise = coroutine(function () { try { @@ -84,7 +84,7 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionThrowsAfterYielding $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Foo'))); } - public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldingRejectedPromise() + public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldingRejectedPromise(): void { $promise = coroutine(function () { try { @@ -97,7 +97,7 @@ public function testCoroutineReturnsFulfilledPromiseIfFunctionReturnsAfterYieldi $promise->then($this->expectCallableOnceWith(42)); } - public function testCoroutineReturnsRejectedPromiseIfFunctionYieldsInvalidValue() + public function testCoroutineReturnsRejectedPromiseIfFunctionYieldsInvalidValue(): void { $promise = coroutine(function () { yield 42; @@ -106,7 +106,7 @@ public function testCoroutineReturnsRejectedPromiseIfFunctionYieldsInvalidValue( $promise->then(null, $this->expectCallableOnceWith(new \UnexpectedValueException('Expected coroutine to yield React\Promise\PromiseInterface, but got integer'))); } - public function testCancelCoroutineWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects() + public function testCancelCoroutineWillReturnRejectedPromiseWhenCancellingPendingPromiseRejects(): void { $promise = coroutine(function () { yield new Promise(function () { }, function () { @@ -120,7 +120,7 @@ public function testCancelCoroutineWillReturnRejectedPromiseWhenCancellingPendin $promise->then(null, $this->expectCallableOnceWith(new \RuntimeException('Operation cancelled'))); } - public function testCancelCoroutineWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue() + public function testCancelCoroutineWillReturnFulfilledPromiseWhenCancellingPendingPromiseRejectsInsideCatchThatReturnsValue(): void { $promise = coroutine(function () { try { @@ -138,7 +138,7 @@ public function testCancelCoroutineWillReturnFulfilledPromiseWhenCancellingPendi $promise->then($this->expectCallableOnceWith(42)); } - public function testCancelCoroutineWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatYieldsSecondPromise() + public function testCancelCoroutineWillReturnPendigPromiseWhenCancellingFirstPromiseRejectsInsideCatchThatYieldsSecondPromise(): void { $promise = coroutine(function () { try { @@ -158,7 +158,7 @@ public function testCancelCoroutineWillReturnPendigPromiseWhenCancellingFirstPro $promise->then($this->expectCallableNever(), $this->expectCallableNever()); } - public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorReturns() + public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorReturns(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -168,7 +168,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorRet gc_collect_cycles(); $promise = coroutine(function () { - if (false) { + if (false) { // @phpstan-ignore-line yield; } return 42; @@ -179,7 +179,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorRet $this->assertEquals(0, gc_collect_cycles()); } - public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithExceptionImmediately() + public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithExceptionImmediately(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -198,7 +198,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseReject $this->assertEquals(0, gc_collect_cycles()); } - public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithExceptionOnCancellation() + public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseRejectedWithExceptionOnCancellation(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -219,7 +219,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesForPromiseReject $this->assertEquals(0, gc_collect_cycles()); } - public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorThrowsBeforeFirstYield() + public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorThrowsBeforeFirstYield(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); @@ -229,7 +229,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorThr $promise = coroutine(function () { throw new \RuntimeException('Failed', 42); - yield; + yield; // @phpstan-ignore-line }); unset($promise); @@ -237,7 +237,7 @@ public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorThr $this->assertEquals(0, gc_collect_cycles()); } - public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorYieldsInvalidValue() + public function testCoroutineShouldNotCreateAnyGarbageReferencesWhenGeneratorYieldsInvalidValue(): void { if (class_exists('React\Promise\When')) { $this->markTestSkipped('Not supported on legacy Promise v1 API'); diff --git a/tests/DelayTest.php b/tests/DelayTest.php index 46f0fba..2cadd6f 100644 --- a/tests/DelayTest.php +++ b/tests/DelayTest.php @@ -10,7 +10,7 @@ class DelayTest extends TestCase { - public function testDelayBlocksForGivenPeriod() + public function testDelayBlocksForGivenPeriod(): void { $time = microtime(true); delay(0.02); @@ -20,7 +20,7 @@ public function testDelayBlocksForGivenPeriod() $this->assertLessThan(0.03, $time); } - public function testDelaySmallPeriodBlocksForCloseToZeroSeconds() + public function testDelaySmallPeriodBlocksForCloseToZeroSeconds(): void { $time = microtime(true); delay(0.000001); @@ -29,7 +29,7 @@ public function testDelaySmallPeriodBlocksForCloseToZeroSeconds() $this->assertLessThan(0.01, $time); } - public function testDelayNegativePeriodBlocksForCloseToZeroSeconds() + public function testDelayNegativePeriodBlocksForCloseToZeroSeconds(): void { $time = microtime(true); delay(-1); @@ -38,7 +38,7 @@ public function testDelayNegativePeriodBlocksForCloseToZeroSeconds() $this->assertLessThan(0.01, $time); } - public function testAwaitAsyncDelayBlocksForGivenPeriod() + public function testAwaitAsyncDelayBlocksForGivenPeriod(): void { $promise = async(function () { delay(0.02); @@ -52,11 +52,13 @@ public function testAwaitAsyncDelayBlocksForGivenPeriod() $this->assertLessThan(0.03, $time); } - public function testAwaitAsyncDelayCancelledImmediatelyStopsTimerAndBlocksForCloseToZeroSeconds() + public function testAwaitAsyncDelayCancelledImmediatelyStopsTimerAndBlocksForCloseToZeroSeconds(): void { $promise = async(function () { delay(1.0); })(); + + assert(method_exists($promise, 'cancel')); $promise->cancel(); $time = microtime(true); @@ -70,11 +72,13 @@ public function testAwaitAsyncDelayCancelledImmediatelyStopsTimerAndBlocksForClo $this->assertLessThan(0.03, $time); } - public function testAwaitAsyncDelayCancelledAfterSmallPeriodStopsTimerAndBlocksUntilCancelled() + public function testAwaitAsyncDelayCancelledAfterSmallPeriodStopsTimerAndBlocksUntilCancelled(): void { $promise = async(function () { delay(1.0); })(); + + assert(method_exists($promise, 'cancel')); Loop::addTimer(0.02, fn() => $promise->cancel()); $time = microtime(true); diff --git a/tests/ParallelTest.php b/tests/ParallelTest.php index 98bbce2..37b1e10 100644 --- a/tests/ParallelTest.php +++ b/tests/ParallelTest.php @@ -6,10 +6,11 @@ use React\EventLoop\Loop; use React\Promise\Promise; use function React\Promise\reject; +use function React\Promise\resolve; class ParallelTest extends TestCase { - public function testParallelWithoutTasks() + public function testParallelWithoutTasks(): void { $tasks = array(); @@ -18,11 +19,11 @@ public function testParallelWithoutTasks() $promise->then($this->expectCallableOnceWith(array())); } - public function testParallelWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray() + public function testParallelWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray(): void { $tasks = (function () { - if (false) { - yield; + if (false) { // @phpstan-ignore-line + yield fn () => resolve(null); } })(); @@ -31,7 +32,7 @@ public function testParallelWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray $promise->then($this->expectCallableOnceWith([])); } - public function testParallelWithTasks() + public function testParallelWithTasks(): void { $tasks = array( function () { @@ -63,7 +64,7 @@ function () { $timer->assertInRange(0.1, 0.2); } - public function testParallelWithTasksFromGeneratorResolvesWithArrayOfFulfillmentValues() + public function testParallelWithTasksFromGeneratorResolvesWithArrayOfFulfillmentValues(): void { $tasks = (function () { yield function () { @@ -95,7 +96,7 @@ public function testParallelWithTasksFromGeneratorResolvesWithArrayOfFulfillment $timer->assertInRange(0.1, 0.2); } - public function testParallelWithErrorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testParallelWithErrorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $called = 0; @@ -127,12 +128,12 @@ function () use (&$called) { $this->assertSame(2, $called); } - public function testParallelWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testParallelWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $called = 0; $tasks = (function () use (&$called) { - while (true) { + while (true) { // @phpstan-ignore-line yield function () use (&$called) { return reject(new \RuntimeException('Rejected ' . ++$called)); }; @@ -146,7 +147,7 @@ public function testParallelWithErrorFromInfiniteGeneratorReturnsPromiseRejected $this->assertSame(1, $called); } - public function testParallelWithErrorWillCancelPendingPromises() + public function testParallelWithErrorWillCancelPendingPromises(): void { $cancelled = 0; @@ -175,7 +176,7 @@ function () use (&$cancelled) { $this->assertSame(1, $cancelled); } - public function testParallelWillCancelPendingPromisesWhenCallingCancelOnResultingPromise() + public function testParallelWillCancelPendingPromisesWhenCallingCancelOnResultingPromise(): void { $cancelled = 0; @@ -199,7 +200,7 @@ function () use (&$cancelled) { $this->assertSame(2, $cancelled); } - public function testParallelWithDelayedErrorReturnsPromiseRejectedWithExceptionFromTask() + public function testParallelWithDelayedErrorReturnsPromiseRejectedWithExceptionFromTask(): void { $called = 0; diff --git a/tests/SeriesTest.php b/tests/SeriesTest.php index 0bc5017..9b20815 100644 --- a/tests/SeriesTest.php +++ b/tests/SeriesTest.php @@ -6,10 +6,11 @@ use React\EventLoop\Loop; use React\Promise\Promise; use function React\Promise\reject; +use function React\Promise\resolve; class SeriesTest extends TestCase { - public function testSeriesWithoutTasks() + public function testSeriesWithoutTasks(): void { $tasks = array(); @@ -18,11 +19,11 @@ public function testSeriesWithoutTasks() $promise->then($this->expectCallableOnceWith(array())); } - public function testSeriesWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray() + public function testSeriesWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray(): void { $tasks = (function () { - if (false) { - yield; + if (false) { // @phpstan-ignore-line + yield fn () => resolve(null); } })(); @@ -31,7 +32,7 @@ public function testSeriesWithoutTasksFromEmptyGeneratorResolvesWithEmptyArray() $promise->then($this->expectCallableOnceWith([])); } - public function testSeriesWithTasks() + public function testSeriesWithTasks(): void { $tasks = array( function () { @@ -63,7 +64,7 @@ function () { $timer->assertInRange(0.10, 0.20); } - public function testSeriesWithTasksFromGeneratorResolvesWithArrayOfFulfillmentValues() + public function testSeriesWithTasksFromGeneratorResolvesWithArrayOfFulfillmentValues(): void { $tasks = (function () { yield function () { @@ -95,7 +96,7 @@ public function testSeriesWithTasksFromGeneratorResolvesWithArrayOfFulfillmentVa $timer->assertInRange(0.10, 0.20); } - public function testSeriesWithError() + public function testSeriesWithError(): void { $called = 0; @@ -126,12 +127,12 @@ function () use (&$called) { $this->assertSame(1, $called); } - public function testSeriesWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testSeriesWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $called = 0; $tasks = (function () use (&$called) { - while (true) { + while (true) { // @phpstan-ignore-line yield function () use (&$called) { return reject(new \RuntimeException('Rejected ' . ++$called)); }; @@ -145,14 +146,14 @@ public function testSeriesWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWi $this->assertSame(1, $called); } - public function testSeriesWithErrorFromInfiniteIteratorAggregateReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testSeriesWithErrorFromInfiniteIteratorAggregateReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $tasks = new class() implements \IteratorAggregate { - public $called = 0; + public int $called = 0; public function getIterator(): \Iterator { - while (true) { + while (true) { // @phpstan-ignore-line yield function () { return reject(new \RuntimeException('Rejected ' . ++$this->called)); }; @@ -167,7 +168,7 @@ public function getIterator(): \Iterator $this->assertSame(1, $tasks->called); } - public function testSeriesWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise() + public function testSeriesWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise(): void { $cancelled = 0; diff --git a/tests/TestCase.php b/tests/TestCase.php index 904c63b..e43397d 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -2,42 +2,39 @@ namespace React\Tests\Async; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase as BaseTestCase; class TestCase extends BaseTestCase { - protected function expectCallableOnce() + protected function expectCallableOnce(): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke'); + $mock->expects($this->once())->method('__invoke'); + assert(is_callable($mock)); return $mock; } - protected function expectCallableOnceWith($value) + protected function expectCallableOnceWith(mixed $value): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->once()) - ->method('__invoke') - ->with($value); + $mock->expects($this->once())->method('__invoke')->with($value); + assert(is_callable($mock)); return $mock; } - protected function expectCallableNever() + protected function expectCallableNever(): callable { $mock = $this->createCallableMock(); - $mock - ->expects($this->never()) - ->method('__invoke'); + $mock->expects($this->never())->method('__invoke'); + assert(is_callable($mock)); return $mock; } - protected function createCallableMock() + protected function createCallableMock(): MockObject { return $this->getMockBuilder(\stdClass::class)->addMethods(['__invoke'])->getMock(); } diff --git a/tests/Timer.php b/tests/Timer.php index 0a37a73..0e755a7 100644 --- a/tests/Timer.php +++ b/tests/Timer.php @@ -4,41 +4,41 @@ class Timer { - private $testCase; - private $start; - private $stop; + private TestCase $testCase; + private float $start; + private float $stop; public function __construct(TestCase $testCase) { $this->testCase = $testCase; } - public function start() + public function start(): void { $this->start = microtime(true); } - public function stop() + public function stop(): void { $this->stop = microtime(true); } - public function getInterval() + public function getInterval(): float { return $this->stop - $this->start; } - public function assertLessThan($milliseconds) + public function assertLessThan(float $milliseconds): void { $this->testCase->assertLessThan($milliseconds, $this->getInterval()); } - public function assertGreaterThan($milliseconds) + public function assertGreaterThan(float $milliseconds): void { $this->testCase->assertGreaterThan($milliseconds, $this->getInterval()); } - public function assertInRange($minMs, $maxMs) + public function assertInRange(float $minMs, float $maxMs): void { $this->assertGreaterThan($minMs); $this->assertLessThan($maxMs); diff --git a/tests/WaterfallTest.php b/tests/WaterfallTest.php index d2f947f..2b274b2 100644 --- a/tests/WaterfallTest.php +++ b/tests/WaterfallTest.php @@ -6,10 +6,11 @@ use React\EventLoop\Loop; use React\Promise\Promise; use function React\Promise\reject; +use function React\Promise\resolve; class WaterfallTest extends TestCase { - public function testWaterfallWithoutTasks() + public function testWaterfallWithoutTasks(): void { $tasks = array(); @@ -18,11 +19,11 @@ public function testWaterfallWithoutTasks() $promise->then($this->expectCallableOnceWith(null)); } - public function testWaterfallWithoutTasksFromEmptyGeneratorResolvesWithNull() + public function testWaterfallWithoutTasksFromEmptyGeneratorResolvesWithNull(): void { $tasks = (function () { - if (false) { - yield; + if (false) { // @phpstan-ignore-line + yield fn () => resolve(null); } })(); @@ -31,7 +32,7 @@ public function testWaterfallWithoutTasksFromEmptyGeneratorResolvesWithNull() $promise->then($this->expectCallableOnceWith(null)); } - public function testWaterfallWithTasks() + public function testWaterfallWithTasks(): void { $tasks = array( function ($foo = 'foo') { @@ -70,7 +71,7 @@ function ($bar) { $timer->assertInRange(0.15, 0.30); } - public function testWaterfallWithTasksFromGeneratorResolvesWithFinalFulfillmentValue() + public function testWaterfallWithTasksFromGeneratorResolvesWithFinalFulfillmentValue(): void { $tasks = (function () { yield function ($foo = 'foo') { @@ -109,7 +110,7 @@ public function testWaterfallWithTasksFromGeneratorResolvesWithFinalFulfillmentV $timer->assertInRange(0.15, 0.30); } - public function testWaterfallWithError() + public function testWaterfallWithError(): void { $called = 0; @@ -140,12 +141,12 @@ function () use (&$called) { $this->assertSame(1, $called); } - public function testWaterfallWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testWaterfallWithErrorFromInfiniteGeneratorReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $called = 0; $tasks = (function () use (&$called) { - while (true) { + while (true) { // @phpstan-ignore-line yield function () use (&$called) { return reject(new \RuntimeException('Rejected ' . ++$called)); }; @@ -159,14 +160,14 @@ public function testWaterfallWithErrorFromInfiniteGeneratorReturnsPromiseRejecte $this->assertSame(1, $called); } - public function testWaterfallWithErrorFromInfiniteIteratorAggregateReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks() + public function testWaterfallWithErrorFromInfiniteIteratorAggregateReturnsPromiseRejectedWithExceptionFromTaskAndStopsCallingAdditionalTasks(): void { $tasks = new class() implements \IteratorAggregate { - public $called = 0; + public int $called = 0; public function getIterator(): \Iterator { - while (true) { + while (true) { // @phpstan-ignore-line yield function () { return reject(new \RuntimeException('Rejected ' . ++$this->called)); }; @@ -181,7 +182,7 @@ public function getIterator(): \Iterator $this->assertSame(1, $tasks->called); } - public function testWaterfallWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise() + public function testWaterfallWillCancelFirstPendingPromiseWhenCallingCancelOnResultingPromise(): void { $cancelled = 0;