Skip to content

Commit

Permalink
Add PHPStan to test environment
Browse files Browse the repository at this point in the history
  • Loading branch information
clue committed Jun 21, 2023
1 parent 6019855 commit fd5e886
Show file tree
Hide file tree
Showing 18 changed files with 144 additions and 73 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/.gitattributes export-ignore
/.github/ export-ignore
/.gitignore export-ignore
/phpstan.neon.dist export-ignore
/phpunit.xml.dist export-ignore
/phpunit.xml.legacy export-ignore
/tests/ export-ignore
22 changes: 22 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,25 @@ jobs:
if: ${{ matrix.php >= 7.3 }}
- run: vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy
if: ${{ matrix.php < 7.3 }}

PHPStan:
name: PHPStan (PHP ${{ matrix.php }})
runs-on: ubuntu-22.04
strategy:
matrix:
php:
- 8.2
- 8.1
- 8.0
- 7.4
- 7.3
- 7.2
- 7.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
26 changes: 24 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ Table of Contents
* [Rejection forwarding](#rejection-forwarding)
* [Mixed resolution and rejection forwarding](#mixed-resolution-and-rejection-forwarding)
5. [Install](#install)
6. [Credits](#credits)
7. [License](#license)
6. [Tests](#tests)
7. [Credits](#credits)
8. [License](#license)

Introduction
------------
Expand Down Expand Up @@ -586,6 +587,27 @@ PHP versions like this:
composer require "react/promise:^3@dev || ^2 || ^1"
```

## Tests

To run the test suite, you first need to clone this repo and then install all
dependencies [through Composer](https://getcomposer.org/):

```bash
composer install
```

To run the test suite, go to the project root and run:

```bash
vendor/bin/phpunit
```

On top of this, we use PHPStan on level 3 to ensure type safety across the project:

```bash
vendor/bin/phpstan
```

Credits
-------

Expand Down
10 changes: 8 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,27 @@
"php": ">=7.1.0"
},
"require-dev": {
"phpstan/phpstan": "1.10.20 || 1.4.10",
"phpunit/phpunit": "^9.5 || ^7.5"
},
"autoload": {
"psr-4": {
"React\\Promise\\": "src/"
},
"files": ["src/functions_include.php"]
"files": [
"src/functions_include.php"
]
},
"autoload-dev": {
"psr-4": {
"React\\Promise\\": [
"tests/fixtures/",
"tests/"
]
}
},
"files": [
"tests/Fiber.php"
]
},
"keywords": [
"promise",
Expand Down
6 changes: 6 additions & 0 deletions phpstan.neon.dist
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
parameters:
level: 3

paths:
- src/
- tests/
3 changes: 3 additions & 0 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -256,12 +256,15 @@ function _checkTypehint(callable $callback, \Throwable $reason): bool

if ($type instanceof \ReflectionIntersectionType) {
foreach ($type->getTypes() as $typeToMatch) {
assert($typeToMatch instanceof \ReflectionNamedType);
if (!($matches = ($typeToMatch->isBuiltin() && \gettype($reason) === $typeToMatch->getName())
|| (new \ReflectionClass($typeToMatch->getName()))->isInstance($reason))) {
break;
}
}
assert(isset($matches));
} else {
assert($type instanceof \ReflectionNamedType);
$matches = ($type->isBuiltin() && \gettype($reason) === $type->getName())
|| (new \ReflectionClass($type->getName()))->isInstance($reason);
}
Expand Down
4 changes: 3 additions & 1 deletion tests/DeferredTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenc
gc_collect_cycles();
gc_collect_cycles(); // clear twice to avoid leftovers in PHP 7.4 with ext-xdebug and code coverage turned on

$deferred = new Deferred(function () use (&$deferred) { });
$deferred = new Deferred(function () use (&$deferred) {
assert($deferred instanceof Deferred);
});
$deferred->reject(new \Exception('foo'));
unset($deferred);

Expand Down
27 changes: 27 additions & 0 deletions tests/Fiber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

if (!class_exists(Fiber::class)) {
/**
* Fiber stub to make PHPStan happy on PHP < 8.1
*
* @link https://www.php.net/manual/en/class.fiber.php
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/framework-x with permission
*/
class Fiber
{
public static function suspend(mixed $value): void
{
// NOOP
}

public function __construct(callable $callback)
{
assert(is_callable($callback));
}

public function start(): int
{
return 42;
}
}
}
2 changes: 1 addition & 1 deletion tests/FunctionAnyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,6 @@ public function shouldNotCancelOtherPendingInputArrayPromisesIfOnePromiseFulfill

$promise2 = new Promise(function () {}, $this->expectCallableNever());

any([$deferred->promise(), $promise2], 1)->cancel();
any([$deferred->promise(), $promise2])->cancel();
}
}
6 changes: 3 additions & 3 deletions tests/FunctionCheckTypehintTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public function shouldAcceptStaticClassCallbackWithIntersectionTypehint()
public function shouldAcceptInvokableObjectCallbackWithDNFTypehint()
{
self::assertFalse(_checkTypehint(new CallbackWithDNFTypehintClass(), new \RuntimeException()));
self::assertTrue(_checkTypehint(new CallbackWithDNFTypehintClass(), new ArrayAccessibleException()));
self::assertTrue(_checkTypehint(new CallbackWithDNFTypehintClass(), new IterableException()));
self::assertTrue(_checkTypehint(new CallbackWithDNFTypehintClass(), new CountableException()));
}

Expand All @@ -134,7 +134,7 @@ public function shouldAcceptObjectMethodCallbackWithDNFTypehint()
{
self::assertFalse(_checkTypehint([new CallbackWithDNFTypehintClass(), 'testCallback'], new \RuntimeException()));
self::assertTrue(_checkTypehint([new CallbackWithDNFTypehintClass(), 'testCallback'], new CountableException()));
self::assertTrue(_checkTypehint([new CallbackWithDNFTypehintClass(), 'testCallback'], new ArrayAccessibleException()));
self::assertTrue(_checkTypehint([new CallbackWithDNFTypehintClass(), 'testCallback'], new IterableException()));
}

/**
Expand All @@ -145,7 +145,7 @@ public function shouldAcceptStaticClassCallbackWithDNFTypehint()
{
self::assertFalse(_checkTypehint([CallbackWithDNFTypehintClass::class, 'testCallbackStatic'], new \RuntimeException()));
self::assertTrue(_checkTypehint([CallbackWithDNFTypehintClass::class, 'testCallbackStatic'], new CountableException()));
self::assertTrue(_checkTypehint([CallbackWithDNFTypehintClass::class, 'testCallbackStatic'], new ArrayAccessibleException()));
self::assertTrue(_checkTypehint([CallbackWithDNFTypehintClass::class, 'testCallbackStatic'], new IterableException()));
}

/** @test */
Expand Down
17 changes: 17 additions & 0 deletions tests/PHP8.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

namespace React\Promise;

/**
* Dummy attribute used to comment out code for PHP < 8 to ensure compatibility across test matrix
*
* @copyright Copyright (c) 2023 Christian Lück, taken from https://github.com/clue/framework-x with permission
*/
#[\Attribute]
class PHP8
{
public function __construct()
{
assert(\PHP_VERSION_ID >= 80000);
}
}
17 changes: 5 additions & 12 deletions tests/PromiseTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerWithReference
{
gc_collect_cycles();
$promise = new Promise(function () {}, function () use (&$promise) {
assert($promise instanceof Promise);
throw new \Exception('foo');
});
$promise->cancel();
Expand All @@ -155,6 +156,7 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfResolverWithReferenceT
{
gc_collect_cycles();
$promise = new Promise(function () use (&$promise) {
assert($promise instanceof Promise);
throw new \Exception('foo');
});
unset($promise);
Expand All @@ -172,7 +174,9 @@ public function shouldRejectWithoutCreatingGarbageCyclesIfCancellerHoldsReferenc
gc_collect_cycles();
$promise = new Promise(function () {
throw new \Exception('foo');
}, function () use (&$promise) { });
}, function () use (&$promise) {
assert($promise instanceof Promise);
});
unset($promise);

$this->assertSame(0, gc_collect_cycles());
Expand Down Expand Up @@ -249,17 +253,6 @@ public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPro
$this->assertSame(0, gc_collect_cycles());
}

/** @test */
public function shouldNotLeaveGarbageCyclesWhenRemovingLastReferenceToPendingPromiseWithProgressFollowers()
{
gc_collect_cycles();
$promise = new Promise(function () { });
$promise->then(null, null, function () { });
unset($promise);

$this->assertSame(0, gc_collect_cycles());
}

/** @test */
public function shouldFulfillIfFullfilledWithSimplePromise()
{
Expand Down
7 changes: 4 additions & 3 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,13 @@ public function expectCallableNever(): callable

protected function createCallableMock()
{
if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) {
$builder = $this->getMockBuilder(\stdClass::class);
if (method_exists($builder, 'addMethods')) {
// PHPUnit 9+
return $this->getMockBuilder('stdClass')->addMethods(array('__invoke'))->getMock();
return $builder->addMethods(['__invoke'])->getMock();
} else {
// legacy PHPUnit 4 - PHPUnit 9
return $this->getMockBuilder('stdClass')->setMethods(array('__invoke'))->getMock();
return $builder->setMethods(['__invoke'])->getMock();
}
}
}
22 changes: 0 additions & 22 deletions tests/fixtures/ArrayAccessibleException.php

This file was deleted.

12 changes: 3 additions & 9 deletions tests/fixtures/CallbackWithDNFTypehintClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@

class CallbackWithDNFTypehintClass
{
public function __invoke((RuntimeException&Countable)|(RuntimeException&\ArrayAccess) $e)
{
}
#[PHP8] public function __invoke((RuntimeException&Countable)|(RuntimeException&\IteratorAggregate) $e) { }

public function testCallback((RuntimeException&Countable)|(RuntimeException&\ArrayAccess) $e)
{
}
#[PHP8] public function testCallback((RuntimeException&Countable)|(RuntimeException&\IteratorAggregate) $e) { }

public static function testCallbackStatic((RuntimeException&Countable)|(RuntimeException&\ArrayAccess) $e)
{
}
#[PHP8] public static function testCallbackStatic((RuntimeException&Countable)|(RuntimeException&\IteratorAggregate) $e) { }
}
12 changes: 3 additions & 9 deletions tests/fixtures/CallbackWithIntersectionTypehintClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@

class CallbackWithIntersectionTypehintClass
{
public function __invoke(RuntimeException&Countable $e)
{
}
#[PHP8] public function __invoke(RuntimeException&Countable $e) { }

public function testCallback(RuntimeException&Countable $e)
{
}
#[PHP8] public function testCallback(RuntimeException&Countable $e) { }

public static function testCallbackStatic(RuntimeException&Countable $e)
{
}
#[PHP8] public static function testCallbackStatic(RuntimeException&Countable $e) { }
}
12 changes: 3 additions & 9 deletions tests/fixtures/CallbackWithUnionTypehintClass.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@

class CallbackWithUnionTypehintClass
{
public function __invoke(RuntimeException|InvalidArgumentException $e)
{
}
#[PHP8] public function __invoke(RuntimeException|InvalidArgumentException $e) { }

public function testCallback(RuntimeException|InvalidArgumentException $e)
{
}
#[PHP8] public function testCallback(RuntimeException|InvalidArgumentException $e) { }

public static function testCallbackStatic(RuntimeException|InvalidArgumentException $e)
{
}
#[PHP8] public static function testCallbackStatic(RuntimeException|InvalidArgumentException $e) { }
}
11 changes: 11 additions & 0 deletions tests/fixtures/IterableException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

namespace React\Promise;

class IterableException extends \RuntimeException implements \IteratorAggregate
{
public function getIterator(): \Traversable
{
return new \ArrayIterator([]);
}
}

0 comments on commit fd5e886

Please sign in to comment.