diff --git a/.gitattributes b/.gitattributes index da20d18..0373309 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,6 +2,7 @@ /.github/workflows/ export-ignore /.gitignore export-ignore /examples/ export-ignore +/phpstan.neon.dist export-ignore /phpunit.xml.dist export-ignore /phpunit.xml.legacy export-ignore /tests/ export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 368a0aa..21293f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,25 @@ jobs: project->metrics; exit((int) $metrics['statements'] === (int) $metrics['coveredstatements'] ? 0 : 1); + + 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 diff --git a/composer.json b/composer.json index 151a433..3f933de 100644 --- a/composer.json +++ b/composer.json @@ -21,6 +21,7 @@ }, "require-dev": { "clue/block-react": "^1.5", + "phpstan/phpstan": "1.8.11 || 1.4.10", "phpunit/phpunit": "^9.5 || ^7.5" }, "autoload": { diff --git a/examples/cli.php b/examples/cli.php index 0eb8b65..23c9e55 100644 --- a/examples/cli.php +++ b/examples/cli.php @@ -25,7 +25,9 @@ $params = explode(' ', $line); $method = array_shift($params); - $promise = call_user_func_array([$redis, $method], $params); + + assert(is_callable([$redis, $method])); + $promise = $redis->$method(...$params); // special method such as end() / close() called if (!$promise instanceof React\Promise\PromiseInterface) { diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..aa2f1e3 --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,17 @@ +parameters: + level: max + + paths: + - examples/ + - src/ + - tests/ + + reportUnmatchedIgnoredErrors: false + ignoreErrors: + # ignore generic usage like `PromiseInterface` until fixed upstream + - '/^PHPDoc tag @return contains generic type React\\Promise\\PromiseInterface<.+> but interface React\\Promise\\PromiseInterface is not generic\.$/' + # ignore undefined methods due to magic `__call()` method + - '/^Call to an undefined method Clue\\React\\Redis\\RedisClient::.+\(\)\.$/' + - '/^Call to an undefined method Clue\\React\\Redis\\Io\\StreamingClient::.+\(\)\.$/' + # ignore incomplete type information for mocks in legacy PHPUnit 7.5 + - '/^Parameter #\d+ .+ of .+ expects .+, PHPUnit\\Framework\\MockObject\\MockObject given\.$/' diff --git a/src/Io/Factory.php b/src/Io/Factory.php index 0f88825..a760832 100644 --- a/src/Io/Factory.php +++ b/src/Io/Factory.php @@ -75,6 +75,7 @@ public function createClient(string $uri): PromiseInterface if ($parts['scheme'] === 'rediss') { $authority = 'tls://' . $authority; } elseif ($parts['scheme'] === 'redis+unix') { + assert(isset($parts['path'])); $authority = 'unix://' . substr($parts['path'], 1); unset($parts['path']); } @@ -91,6 +92,7 @@ public function createClient(string $uri): PromiseInterface $connecting->then(function (ConnectionInterface $connection) { $connection->close(); }); + assert(\method_exists($connecting, 'cancel')); $connecting->cancel(); }); @@ -106,7 +108,7 @@ public function createClient(string $uri): PromiseInterface // use `?password=secret` query or `user:secret@host` password form URL if (isset($args['password']) || isset($parts['pass'])) { - $pass = $args['password'] ?? rawurldecode($parts['pass']); + $pass = $args['password'] ?? rawurldecode($parts['pass']); // @phpstan-ignore-line $promise = $promise->then(function (StreamingClient $redis) use ($pass, $uri) { return $redis->auth($pass)->then( function () use ($redis) { @@ -134,7 +136,7 @@ function (\Exception $e) use ($redis, $uri) { // use `?db=1` query or `/1` path (skip first slash) if (isset($args['db']) || (isset($parts['path']) && $parts['path'] !== '/')) { - $db = $args['db'] ?? substr($parts['path'], 1); + $db = $args['db'] ?? substr($parts['path'], 1); // @phpstan-ignore-line $promise = $promise->then(function (StreamingClient $redis) use ($db, $uri) { return $redis->select($db)->then( function () use ($redis) { diff --git a/src/Io/StreamingClient.php b/src/Io/StreamingClient.php index 126bff5..1dc2fde 100644 --- a/src/Io/StreamingClient.php +++ b/src/Io/StreamingClient.php @@ -2,7 +2,6 @@ namespace Clue\React\Redis\Io; -use Clue\Redis\Protocol\Factory as ProtocolFactory; use Clue\Redis\Protocol\Model\ErrorReply; use Clue\Redis\Protocol\Model\ModelInterface; use Clue\Redis\Protocol\Model\MultiBulkReply; @@ -22,9 +21,6 @@ class StreamingClient extends EventEmitter /** @var DuplexStreamInterface */ private $stream; - /** @var ParserInterface */ - private $parser; - /** @var SerializerInterface */ private $serializer; @@ -43,18 +39,8 @@ class StreamingClient extends EventEmitter /** @var int */ private $psubscribed = 0; - public function __construct(DuplexStreamInterface $stream, ParserInterface $parser = null, SerializerInterface $serializer = null) + public function __construct(DuplexStreamInterface $stream, ParserInterface $parser, SerializerInterface $serializer) { - if ($parser === null || $serializer === null) { - $factory = new ProtocolFactory(); - if ($parser === null) { - $parser = $factory->createResponseParser(); - } - if ($serializer === null) { - $serializer = $factory->createSerializer(); - } - } - $stream->on('data', function (string $chunk) use ($parser) { try { $models = $parser->pushIncoming($chunk); @@ -82,10 +68,10 @@ public function __construct(DuplexStreamInterface $stream, ParserInterface $pars $stream->on('close', [$this, 'close']); $this->stream = $stream; - $this->parser = $parser; $this->serializer = $serializer; } + /** @param string[] $args */ public function __call(string $name, array $args): PromiseInterface { $request = new Deferred(); @@ -139,6 +125,7 @@ public function handleMessage(ModelInterface $message): void { if (($this->subscribed !== 0 || $this->psubscribed !== 0) && $message instanceof MultiBulkReply) { $array = $message->getValueNative(); + assert(\is_array($array)); $first = array_shift($array); // pub/sub messages are to be forwarded and should not be processed as request responses diff --git a/src/RedisClient.php b/src/RedisClient.php index 0761454..8230294 100644 --- a/src/RedisClient.php +++ b/src/RedisClient.php @@ -46,7 +46,7 @@ class RedisClient extends EventEmitter /** @var float */ private $idlePeriod = 0.001; - /** @var ?TimerInterface */ + /** @var ?\React\EventLoop\TimerInterface */ private $idleTimer = null; /** @var int */ @@ -161,6 +161,7 @@ public function __call(string $name, array $args): PromiseInterface return $this->client()->then(function (StreamingClient $redis) use ($name, $args) { $this->awake(); + assert(\is_callable([$redis, $name])); // @phpstan-ignore-next-line return \call_user_func_array([$redis, $name], $args)->then( function ($result) { $this->idle(); @@ -221,6 +222,7 @@ public function close(): void $redis->close(); }); if ($this->promise !== null) { + assert(\method_exists($this->promise, 'cancel')); $this->promise->cancel(); $this->promise = null; } @@ -251,6 +253,7 @@ private function idle(): void if ($this->pending < 1 && $this->idlePeriod >= 0 && !$this->subscribed && !$this->psubscribed && $this->promise !== null) { $this->idleTimer = $this->loop->addTimer($this->idlePeriod, function () { + assert($this->promise instanceof PromiseInterface); $this->promise->then(function (StreamingClient $redis) { $redis->close(); }); diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index 4dd0936..673ab73 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -26,7 +26,7 @@ public function setUp(): void $this->loop = new StreamSelectLoop(); } - public function testPing() + public function testPing(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -38,7 +38,7 @@ public function testPing() $this->assertEquals('PONG', $ret); } - public function testPingLazy() + public function testPingLazy(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -53,7 +53,7 @@ public function testPingLazy() /** * @doesNotPerformAssertions */ - public function testPingLazyWillNotBlockLoop() + public function testPingLazyWillNotBlockLoop(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -65,7 +65,7 @@ public function testPingLazyWillNotBlockLoop() /** * @doesNotPerformAssertions */ - public function testLazyClientWithoutCommandsWillNotBlockLoop() + public function testLazyClientWithoutCommandsWillNotBlockLoop(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -74,7 +74,7 @@ public function testLazyClientWithoutCommandsWillNotBlockLoop() unset($redis); } - public function testMgetIsNotInterpretedAsSubMessage() + public function testMgetIsNotInterpretedAsSubMessage(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -86,7 +86,7 @@ public function testMgetIsNotInterpretedAsSubMessage() await($promise, $this->loop); } - public function testPipeline() + public function testPipeline(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -98,7 +98,7 @@ public function testPipeline() await($promise, $this->loop); } - public function testInvalidCommand() + public function testInvalidCommand(): void { $redis = new RedisClient($this->uri, null, $this->loop); $promise = $redis->doesnotexist(1, 2, 3); @@ -106,12 +106,13 @@ public function testInvalidCommand() if (method_exists($this, 'expectException')) { $this->expectException('Exception'); } else { + assert(method_exists($this, 'setExpectedException')); $this->setExpectedException('Exception'); } await($promise, $this->loop); } - public function testMultiExecEmpty() + public function testMultiExecEmpty(): void { $redis = new RedisClient($this->uri, null, $this->loop); $redis->multi()->then($this->expectCallableOnceWith('OK')); @@ -120,7 +121,7 @@ public function testMultiExecEmpty() await($promise, $this->loop); } - public function testMultiExecQueuedExecHasValues() + public function testMultiExecQueuedExecHasValues(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -134,7 +135,7 @@ public function testMultiExecQueuedExecHasValues() await($promise, $this->loop); } - public function testPubSub() + public function testPubSub(): void { $consumer = new RedisClient($this->uri, null, $this->loop); $producer = new RedisClient($this->uri, null, $this->loop); @@ -155,7 +156,7 @@ public function testPubSub() await($deferred->promise(), $this->loop, 0.1); } - public function testClose() + public function testClose(): void { $redis = new RedisClient($this->uri, null, $this->loop); @@ -166,7 +167,7 @@ public function testClose() $redis->get('willBeRejectedRightAway')->then(null, $this->expectCallableOnce()); } - public function testCloseLazy() + public function testCloseLazy(): void { $redis = new RedisClient($this->uri, null, $this->loop); diff --git a/tests/Io/FactoryStreamingClientTest.php b/tests/Io/FactoryStreamingClientTest.php index 9941b07..3cb09c6 100644 --- a/tests/Io/FactoryStreamingClientTest.php +++ b/tests/Io/FactoryStreamingClientTest.php @@ -31,7 +31,7 @@ public function setUp(): void $this->factory = new Factory($this->loop, $this->connector); } - public function testConstructWithoutLoopAssignsLoopAutomatically() + public function testConstructWithoutLoopAssignsLoopAutomatically(): void { $factory = new Factory(); @@ -45,24 +45,24 @@ public function testConstructWithoutLoopAssignsLoopAutomatically() /** * @doesNotPerformAssertions */ - public function testCtor() + public function testCtor(): void { $this->factory = new Factory($this->loop); } - public function testWillConnectWithDefaultPort() + public function testWillConnectWithDefaultPort(): void { $this->connector->expects($this->once())->method('connect')->with('redis.example.com:6379')->willReturn(reject(new \RuntimeException())); $this->factory->createClient('redis.example.com'); } - public function testWillConnectToLocalhost() + public function testWillConnectToLocalhost(): void { $this->connector->expects($this->once())->method('connect')->with('localhost:1337')->willReturn(reject(new \RuntimeException())); $this->factory->createClient('localhost:1337'); } - public function testWillResolveIfConnectorResolves() + public function testWillResolveIfConnectorResolves(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->never())->method('write'); @@ -73,7 +73,7 @@ public function testWillResolveIfConnectorResolves() $this->expectPromiseResolve($promise); } - public function testWillWriteSelectCommandIfTargetContainsPath() + public function testWillWriteSelectCommandIfTargetContainsPath(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$4\r\ndemo\r\n"); @@ -82,7 +82,7 @@ public function testWillWriteSelectCommandIfTargetContainsPath() $this->factory->createClient('redis://127.0.0.1/demo'); } - public function testWillWriteSelectCommandIfTargetContainsDbQueryParameter() + public function testWillWriteSelectCommandIfTargetContainsDbQueryParameter(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$1\r\n4\r\n"); @@ -91,7 +91,7 @@ public function testWillWriteSelectCommandIfTargetContainsDbQueryParameter() $this->factory->createClient('redis://127.0.0.1?db=4'); } - public function testWillWriteAuthCommandIfRedisUriContainsUserInfo() + public function testWillWriteAuthCommandIfRedisUriContainsUserInfo(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); @@ -100,7 +100,7 @@ public function testWillWriteAuthCommandIfRedisUriContainsUserInfo() $this->factory->createClient('redis://hello:world@example.com'); } - public function testWillWriteAuthCommandIfRedisUriContainsEncodedUserInfo() + public function testWillWriteAuthCommandIfRedisUriContainsEncodedUserInfo(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nh@llo\r\n"); @@ -109,7 +109,7 @@ public function testWillWriteAuthCommandIfRedisUriContainsEncodedUserInfo() $this->factory->createClient('redis://:h%40llo@example.com'); } - public function testWillWriteAuthCommandIfTargetContainsPasswordQueryParameter() + public function testWillWriteAuthCommandIfTargetContainsPasswordQueryParameter(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$6\r\nsecret\r\n"); @@ -118,7 +118,7 @@ public function testWillWriteAuthCommandIfTargetContainsPasswordQueryParameter() $this->factory->createClient('redis://example.com?password=secret'); } - public function testWillWriteAuthCommandIfTargetContainsEncodedPasswordQueryParameter() + public function testWillWriteAuthCommandIfTargetContainsEncodedPasswordQueryParameter(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nh@llo\r\n"); @@ -127,7 +127,7 @@ public function testWillWriteAuthCommandIfTargetContainsEncodedPasswordQueryPara $this->factory->createClient('redis://example.com?password=h%40llo'); } - public function testWillWriteAuthCommandIfRedissUriContainsUserInfo() + public function testWillWriteAuthCommandIfRedissUriContainsUserInfo(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); @@ -136,7 +136,7 @@ public function testWillWriteAuthCommandIfRedissUriContainsUserInfo() $this->factory->createClient('rediss://hello:world@example.com'); } - public function testWillWriteAuthCommandIfRedisUnixUriContainsPasswordQueryParameter() + public function testWillWriteAuthCommandIfRedisUnixUriContainsPasswordQueryParameter(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); @@ -145,7 +145,7 @@ public function testWillWriteAuthCommandIfRedisUnixUriContainsPasswordQueryParam $this->factory->createClient('redis+unix:///tmp/redis.sock?password=world'); } - public function testWillNotWriteAnyCommandIfRedisUnixUriContainsNoPasswordOrDb() + public function testWillNotWriteAnyCommandIfRedisUnixUriContainsNoPasswordOrDb(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->never())->method('write'); @@ -154,7 +154,7 @@ public function testWillNotWriteAnyCommandIfRedisUnixUriContainsNoPasswordOrDb() $this->factory->createClient('redis+unix:///tmp/redis.sock'); } - public function testWillWriteAuthCommandIfRedisUnixUriContainsUserInfo() + public function testWillWriteAuthCommandIfRedisUnixUriContainsUserInfo(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); @@ -163,7 +163,7 @@ public function testWillWriteAuthCommandIfRedisUnixUriContainsUserInfo() $this->factory->createClient('redis+unix://hello:world@/tmp/redis.sock'); } - public function testWillResolveWhenAuthCommandReceivesOkResponseIfRedisUriContainsUserInfo() + public function testWillResolveWhenAuthCommandReceivesOkResponseIfRedisUriContainsUserInfo(): void { $dataHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -185,7 +185,7 @@ public function testWillResolveWhenAuthCommandReceivesOkResponseIfRedisUriContai $promise->then($this->expectCallableOnceWith($this->isInstanceOf(StreamingClient::class))); } - public function testWillRejectAndCloseAutomaticallyWhenAuthCommandReceivesErrorResponseIfRedisUriContainsUserInfo() + public function testWillRejectAndCloseAutomaticallyWhenAuthCommandReceivesErrorResponseIfRedisUriContainsUserInfo(): void { $dataHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -215,13 +215,14 @@ public function testWillRejectAndCloseAutomaticallyWhenAuthCommandReceivesErrorR return $e->getCode() === (defined('SOCKET_EACCES') ? SOCKET_EACCES : 13); }), $this->callback(function (\RuntimeException $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'ERR invalid password'; }) ) )); } - public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWaitingForAuthCommand() + public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWaitingForAuthCommand(): void { $closeHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -253,13 +254,14 @@ public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWa return $e->getCode() === (defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104); }), $this->callback(function (\Exception $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'Connection closed by peer (ECONNRESET)'; }) ) )); } - public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter() + public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$4\r\ndemo\r\n"); @@ -268,7 +270,7 @@ public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter $this->factory->createClient('redis+unix:///tmp/redis.sock?db=demo'); } - public function testWillResolveWhenSelectCommandReceivesOkResponseIfRedisUriContainsPath() + public function testWillResolveWhenSelectCommandReceivesOkResponseIfRedisUriContainsPath(): void { $dataHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -290,7 +292,7 @@ public function testWillResolveWhenSelectCommandReceivesOkResponseIfRedisUriCont $promise->then($this->expectCallableOnceWith($this->isInstanceOf(StreamingClient::class))); } - public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesErrorResponseIfRedisUriContainsPath() + public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesErrorResponseIfRedisUriContainsPath(): void { $dataHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -320,13 +322,14 @@ public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesErro return $e->getCode() === (defined('SOCKET_ENOENT') ? SOCKET_ENOENT : 2); }), $this->callback(function (\RuntimeException $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'ERR DB index is out of range'; }) ) )); } - public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesAuthErrorResponseIfRedisUriContainsPath() + public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesAuthErrorResponseIfRedisUriContainsPath(): void { $dataHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -356,13 +359,14 @@ public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesAuth return $e->getCode() === (defined('SOCKET_EACCES') ? SOCKET_EACCES : 13); }), $this->callback(function (\Exception $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'NOAUTH Authentication required.'; }) ) )); } - public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWaitingForSelectCommand() + public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWaitingForSelectCommand(): void { $closeHandler = null; $stream = $this->createMock(ConnectionInterface::class); @@ -394,13 +398,14 @@ public function testWillRejectAndCloseAutomaticallyWhenConnectionIsClosedWhileWa return $e->getCode() === (defined('SOCKET_ECONNRESET') ? SOCKET_ECONNRESET : 104); }), $this->callback(function (\Exception $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'Connection closed by peer (ECONNRESET)'; }) ) )); } - public function testWillRejectIfConnectorRejects() + public function testWillRejectIfConnectorRejects(): void { $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn(reject(new \RuntimeException('Foo', 42))); $promise = $this->factory->createClient('redis://127.0.0.1:2'); @@ -415,13 +420,14 @@ public function testWillRejectIfConnectorRejects() return $e->getCode() === 42; }), $this->callback(function (\RuntimeException $e) { + assert($e->getPrevious() !== null); return $e->getPrevious()->getMessage() === 'Foo'; }) ) )); } - public function testWillRejectIfTargetIsInvalid() + public function testWillRejectIfTargetIsInvalid(): void { $promise = $this->factory->createClient('http://invalid target'); @@ -438,18 +444,21 @@ public function testWillRejectIfTargetIsInvalid() )); } - public function testCancelWillRejectPromise() + public function testCancelWillRejectPromise(): void { $promise = new \React\Promise\Promise(function () { }); $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn($promise); $promise = $this->factory->createClient('redis://127.0.0.1:2'); + + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then(null, $this->expectCallableOnceWith($this->isInstanceOf(\RuntimeException::class))); } - public function provideUris() + /** @return list> */ + public function provideUris(): array { return [ [ @@ -517,15 +526,15 @@ public function provideUris() /** * @dataProvider provideUris - * @param string $uri - * @param string $safe */ - public function testCancelWillRejectWithUriInMessageAndCancelConnectorWhenConnectionIsPending($uri, $safe) + public function testCancelWillRejectWithUriInMessageAndCancelConnectorWhenConnectionIsPending(string $uri, string $safe): void { $deferred = new Deferred($this->expectCallableOnce()); $this->connector->expects($this->once())->method('connect')->willReturn($deferred->promise()); $promise = $this->factory->createClient($uri); + + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then(null, $this->expectCallableOnceWith( @@ -541,7 +550,7 @@ public function testCancelWillRejectWithUriInMessageAndCancelConnectorWhenConnec )); } - public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect() + public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect(): void { $stream = $this->createMock(ConnectionInterface::class); $stream->expects($this->once())->method('write'); @@ -550,6 +559,8 @@ public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect() $this->connector->expects($this->once())->method('connect')->willReturn(resolve($stream)); $promise = $this->factory->createClient('redis://127.0.0.1:2/123'); + + assert(method_exists($promise, 'cancel')); $promise->cancel(); $promise->then(null, $this->expectCallableOnceWith( @@ -565,7 +576,7 @@ public function testCancelWillCloseConnectionWhenConnectionWaitsForSelect() )); } - public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExplicitTimeout() + public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExplicitTimeout(): void { $timeout = null; $this->loop->expects($this->once())->method('addTimer')->with(0, $this->callback(function ($cb) use (&$timeout) { @@ -594,7 +605,7 @@ public function testCreateClientWithTimeoutParameterWillStartTimerAndRejectOnExp )); } - public function testCreateClientWithNegativeTimeoutParameterWillNotStartTimer() + public function testCreateClientWithNegativeTimeoutParameterWillNotStartTimer(): void { $this->loop->expects($this->never())->method('addTimer'); @@ -604,7 +615,7 @@ public function testCreateClientWithNegativeTimeoutParameterWillNotStartTimer() $this->factory->createClient('redis://127.0.0.1:2?timeout=-1'); } - public function testCreateClientWithoutTimeoutParameterWillStartTimerWithDefaultTimeoutFromIni() + public function testCreateClientWithoutTimeoutParameterWillStartTimerWithDefaultTimeoutFromIni(): void { $this->loop->expects($this->once())->method('addTimer')->with(42, $this->anything()); @@ -612,6 +623,7 @@ public function testCreateClientWithoutTimeoutParameterWillStartTimerWithDefault $this->connector->expects($this->once())->method('connect')->with('127.0.0.1:2')->willReturn($deferred->promise()); $old = ini_get('default_socket_timeout'); + assert(is_string($old)); ini_set('default_socket_timeout', '42'); $this->factory->createClient('redis://127.0.0.1:2'); ini_set('default_socket_timeout', $old); diff --git a/tests/Io/StreamingClientTest.php b/tests/Io/StreamingClientTest.php index b0274f8..dc47ad7 100644 --- a/tests/Io/StreamingClientTest.php +++ b/tests/Io/StreamingClientTest.php @@ -38,29 +38,7 @@ public function setUp(): void $this->redis = new StreamingClient($this->stream, $this->parser, $this->serializer); } - public function testConstructWithoutParserAssignsParserAutomatically() - { - $this->redis = new StreamingClient($this->stream, null, $this->serializer); - - $ref = new \ReflectionProperty($this->redis, 'parser'); - $ref->setAccessible(true); - $parser = $ref->getValue($this->redis); - - $this->assertInstanceOf(ParserInterface::class, $parser); - } - - public function testConstructWithoutParserAndSerializerAssignsParserAndSerializerAutomatically() - { - $this->redis = new StreamingClient($this->stream, $this->parser); - - $ref = new \ReflectionProperty($this->redis, 'serializer'); - $ref->setAccessible(true); - $serializer = $ref->getValue($this->redis); - - $this->assertInstanceOf(SerializerInterface::class, $serializer); - } - - public function testSending() + public function testSending(): void { $this->serializer->expects($this->once())->method('getRequestMessage')->with($this->equalTo('ping'))->will($this->returnValue('message')); $this->stream->expects($this->once())->method('write')->with($this->equalTo('message')); @@ -68,14 +46,14 @@ public function testSending() $this->redis->ping(); } - public function testClosingClientEmitsEvent() + public function testClosingClientEmitsEvent(): void { $this->redis->on('close', $this->expectCallableOnce()); $this->redis->close(); } - public function testClosingStreamClosesClient() + public function testClosingStreamClosesClient(): void { $stream = new ThroughStream(); $this->redis = new StreamingClient($stream, $this->parser, $this->serializer); @@ -85,7 +63,7 @@ public function testClosingStreamClosesClient() $stream->emit('close'); } - public function testReceiveParseErrorEmitsErrorEvent() + public function testReceiveParseErrorEmitsErrorEvent(): void { $stream = new ThroughStream(); $this->redis = new StreamingClient($stream, $this->parser, $this->serializer); @@ -107,7 +85,7 @@ public function testReceiveParseErrorEmitsErrorEvent() $stream->emit('data', ['message']); } - public function testReceiveUnexpectedReplyEmitsErrorEvent() + public function testReceiveUnexpectedReplyEmitsErrorEvent(): void { $stream = new ThroughStream(); $this->redis = new StreamingClient($stream, $this->parser, $this->serializer); @@ -130,15 +108,7 @@ public function testReceiveUnexpectedReplyEmitsErrorEvent() $stream->emit('data', ['message']); } - /** - * @doesNotPerformAssertions - */ - public function testDefaultCtor() - { - new StreamingClient($this->stream); - } - - public function testPingPong() + public function testPingPong(): void { $this->serializer->expects($this->once())->method('getRequestMessage')->with($this->equalTo('ping')); @@ -150,7 +120,7 @@ public function testPingPong() $promise->then($this->expectCallableOnceWith('PONG')); } - public function testMonitorCommandIsNotSupported() + public function testMonitorCommandIsNotSupported(): void { $promise = $this->redis->monitor(); @@ -167,7 +137,7 @@ public function testMonitorCommandIsNotSupported() )); } - public function testErrorReply() + public function testErrorReply(): void { $promise = $this->redis->invalid(); @@ -177,7 +147,7 @@ public function testErrorReply() $promise->then(null, $this->expectCallableOnceWith($err)); } - public function testClosingClientRejectsAllRemainingRequests() + public function testClosingClientRejectsAllRemainingRequests(): void { $promise = $this->redis->ping(); $this->redis->close(); @@ -195,7 +165,7 @@ public function testClosingClientRejectsAllRemainingRequests() )); } - public function testClosingStreamRejectsAllRemainingRequests() + public function testClosingStreamRejectsAllRemainingRequests(): void { $stream = new ThroughStream(function () { return ''; }); $this->parser->expects($this->once())->method('pushIncoming')->willReturn([]); @@ -217,7 +187,7 @@ public function testClosingStreamRejectsAllRemainingRequests() )); } - public function testEndingClientRejectsAllNewRequests() + public function testEndingClientRejectsAllNewRequests(): void { $this->redis->ping(); $this->redis->end(); @@ -236,7 +206,7 @@ public function testEndingClientRejectsAllNewRequests() )); } - public function testClosedClientRejectsAllNewRequests() + public function testClosedClientRejectsAllNewRequests(): void { $this->redis->close(); $promise = $this->redis->ping(); @@ -254,13 +224,13 @@ public function testClosedClientRejectsAllNewRequests() )); } - public function testEndingNonBusyClosesClient() + public function testEndingNonBusyClosesClient(): void { $this->redis->on('close', $this->expectCallableOnce()); $this->redis->end(); } - public function testEndingBusyClosesClientWhenNotBusyAnymore() + public function testEndingBusyClosesClientWhenNotBusyAnymore(): void { // count how often the "close" method has been called $closed = 0; @@ -279,7 +249,7 @@ public function testEndingBusyClosesClientWhenNotBusyAnymore() $this->assertEquals(1, $closed); } - public function testClosingMultipleTimesEmitsOnce() + public function testClosingMultipleTimesEmitsOnce(): void { $this->redis->on('close', $this->expectCallableOnce()); @@ -287,13 +257,13 @@ public function testClosingMultipleTimesEmitsOnce() $this->redis->close(); } - public function testReceivingUnexpectedMessageThrowsException() + public function testReceivingUnexpectedMessageThrowsException(): void { $this->expectException(\UnderflowException::class); $this->redis->handleMessage(new BulkReply('PONG')); } - public function testPubsubSubscribe() + public function testPubsubSubscribe(): StreamingClient { $promise = $this->redis->subscribe('test'); $this->expectPromiseResolve($promise); @@ -306,9 +276,8 @@ public function testPubsubSubscribe() /** * @depends testPubsubSubscribe - * @param StreamingClient $client */ - public function testPubsubPatternSubscribe(StreamingClient $client) + public function testPubsubPatternSubscribe(StreamingClient $client): StreamingClient { $promise = $client->psubscribe('demo_*'); $this->expectPromiseResolve($promise); @@ -321,15 +290,14 @@ public function testPubsubPatternSubscribe(StreamingClient $client) /** * @depends testPubsubPatternSubscribe - * @param StreamingClient $client */ - public function testPubsubMessage(StreamingClient $client) + public function testPubsubMessage(StreamingClient $client): void { $client->on('message', $this->expectCallableOnce()); $client->handleMessage(new MultiBulkReply([new BulkReply('message'), new BulkReply('test'), new BulkReply('payload')])); } - public function testSubscribeWithMultipleArgumentsRejects() + public function testSubscribeWithMultipleArgumentsRejects(): void { $promise = $this->redis->subscribe('a', 'b'); @@ -346,7 +314,7 @@ public function testSubscribeWithMultipleArgumentsRejects() )); } - public function testUnsubscribeWithoutArgumentsRejects() + public function testUnsubscribeWithoutArgumentsRejects(): void { $promise = $this->redis->unsubscribe(); diff --git a/tests/RedisClientTest.php b/tests/RedisClientTest.php index 54b8e97..813523a 100644 --- a/tests/RedisClientTest.php +++ b/tests/RedisClientTest.php @@ -34,7 +34,7 @@ public function setUp(): void $ref->setValue($this->redis, $this->factory); } - public function testPingWillCreateUnderlyingClientAndReturnPendingPromise() + public function testPingWillCreateUnderlyingClientAndReturnPendingPromise(): void { $promise = new Promise(function () { }); $this->factory->expects($this->once())->method('createClient')->willReturn($promise); @@ -46,7 +46,7 @@ public function testPingWillCreateUnderlyingClientAndReturnPendingPromise() $promise->then($this->expectCallableNever()); } - public function testPingTwiceWillCreateOnceUnderlyingClient() + public function testPingTwiceWillCreateOnceUnderlyingClient(): void { $promise = new Promise(function () { }); $this->factory->expects($this->once())->method('createClient')->willReturn($promise); @@ -55,7 +55,7 @@ public function testPingTwiceWillCreateOnceUnderlyingClient() $this->redis->ping(); } - public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleTimer() + public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleTimer(): void { $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->with('ping')->willReturn(\React\Promise\resolve('PONG')); @@ -71,7 +71,7 @@ public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleT $promise->then($this->expectCallableOnceWith('PONG')); } - public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleTimerWithIdleTimeFromQueryParam() + public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleTimerWithIdleTimeFromQueryParam(): void { $this->redis = new RedisClient('localhost?idle=10', null, $this->loop); @@ -93,7 +93,7 @@ public function testPingWillResolveWhenUnderlyingClientResolvesPingAndStartIdleT $promise->then($this->expectCallableOnceWith('PONG')); } - public function testPingWillResolveWhenUnderlyingClientResolvesPingAndNotStartIdleTimerWhenIdleParamIsNegative() + public function testPingWillResolveWhenUnderlyingClientResolvesPingAndNotStartIdleTimerWhenIdleParamIsNegative(): void { $this->redis = new RedisClient('localhost?idle=-1', null, $this->loop); @@ -115,7 +115,7 @@ public function testPingWillResolveWhenUnderlyingClientResolvesPingAndNotStartId $promise->then($this->expectCallableOnceWith('PONG')); } - public function testPingWillRejectWhenUnderlyingClientRejectsPingAndStartIdleTimer() + public function testPingWillRejectWhenUnderlyingClientRejectsPingAndStartIdleTimer(): void { $error = new \RuntimeException(); $client = $this->createMock(StreamingClient::class); @@ -132,7 +132,7 @@ public function testPingWillRejectWhenUnderlyingClientRejectsPingAndStartIdleTim $promise->then(null, $this->expectCallableOnceWith($error)); } - public function testPingWillRejectAndNotEmitErrorOrCloseWhenFactoryRejectsUnderlyingClient() + public function testPingWillRejectAndNotEmitErrorOrCloseWhenFactoryRejectsUnderlyingClient(): void { $error = new \RuntimeException(); @@ -148,7 +148,7 @@ public function testPingWillRejectAndNotEmitErrorOrCloseWhenFactoryRejectsUnderl $promise->then(null, $this->expectCallableOnceWith($error)); } - public function testPingAfterPreviousFactoryRejectsUnderlyingClientWillCreateNewUnderlyingConnection() + public function testPingAfterPreviousFactoryRejectsUnderlyingClientWillCreateNewUnderlyingConnection(): void { $error = new \RuntimeException(); @@ -164,7 +164,7 @@ public function testPingAfterPreviousFactoryRejectsUnderlyingClientWillCreateNew $this->redis->ping(); } - public function testPingAfterPreviousUnderlyingClientAlreadyClosedWillCreateNewUnderlyingConnection() + public function testPingAfterPreviousUnderlyingClientAlreadyClosedWillCreateNewUnderlyingConnection(): void { $closeHandler = null; $client = $this->createMock(StreamingClient::class); @@ -188,7 +188,7 @@ public function testPingAfterPreviousUnderlyingClientAlreadyClosedWillCreateNewU $this->redis->ping(); } - public function testPingAfterCloseWillRejectWithoutCreatingUnderlyingConnection() + public function testPingAfterCloseWillRejectWithoutCreatingUnderlyingConnection(): void { $this->factory->expects($this->never())->method('createClient'); @@ -208,7 +208,7 @@ public function testPingAfterCloseWillRejectWithoutCreatingUnderlyingConnection( )); } - public function testPingAfterPingWillNotStartIdleTimerWhenFirstPingResolves() + public function testPingAfterPingWillNotStartIdleTimerWhenFirstPingResolves(): void { $deferred = new Deferred(); $client = $this->createMock(StreamingClient::class); @@ -226,7 +226,7 @@ public function testPingAfterPingWillNotStartIdleTimerWhenFirstPingResolves() $deferred->resolve(null); } - public function testPingAfterPingWillStartAndCancelIdleTimerWhenSecondPingStartsAfterFirstResolves() + public function testPingAfterPingWillStartAndCancelIdleTimerWhenSecondPingStartsAfterFirstResolves(): void { $deferred = new Deferred(); $client = $this->createMock(StreamingClient::class); @@ -246,7 +246,7 @@ public function testPingAfterPingWillStartAndCancelIdleTimerWhenSecondPingStarts $this->redis->ping(); } - public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWithoutCloseEvent() + public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWithoutCloseEvent(): void { $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve(null)); @@ -269,7 +269,7 @@ public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWithoutC $timeout(); } - public function testCloseWillEmitCloseEventWithoutCreatingUnderlyingClient() + public function testCloseWillEmitCloseEventWithoutCreatingUnderlyingClient(): void { $this->factory->expects($this->never())->method('createClient'); @@ -278,7 +278,7 @@ public function testCloseWillEmitCloseEventWithoutCreatingUnderlyingClient() $this->redis->close(); } - public function testCloseTwiceWillEmitCloseEventOnce() + public function testCloseTwiceWillEmitCloseEventOnce(): void { $this->redis->on('close', $this->expectCallableOnce()); @@ -286,7 +286,7 @@ public function testCloseTwiceWillEmitCloseEventOnce() $this->redis->close(); } - public function testCloseAfterPingWillCancelUnderlyingClientConnectionWhenStillPending() + public function testCloseAfterPingWillCancelUnderlyingClientConnectionWhenStillPending(): void { $promise = new Promise(function () { }, $this->expectCallableOnce()); $this->factory->expects($this->once())->method('createClient')->willReturn($promise); @@ -295,7 +295,7 @@ public function testCloseAfterPingWillCancelUnderlyingClientConnectionWhenStillP $this->redis->close(); } - public function testCloseAfterPingWillEmitCloseWithoutErrorWhenUnderlyingClientConnectionThrowsDueToCancellation() + public function testCloseAfterPingWillEmitCloseWithoutErrorWhenUnderlyingClientConnectionThrowsDueToCancellation(): void { $promise = new Promise(function () { }, function () { throw new \RuntimeException('Discarded'); @@ -309,7 +309,7 @@ public function testCloseAfterPingWillEmitCloseWithoutErrorWhenUnderlyingClientC $this->redis->close(); } - public function testCloseAfterPingWillCloseUnderlyingClientConnectionWhenAlreadyResolved() + public function testCloseAfterPingWillCloseUnderlyingClientConnectionWhenAlreadyResolved(): void { $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve(null)); @@ -323,7 +323,7 @@ public function testCloseAfterPingWillCloseUnderlyingClientConnectionWhenAlready $this->redis->close(); } - public function testCloseAfterPingWillCancelIdleTimerWhenPingIsAlreadyResolved() + public function testCloseAfterPingWillCancelIdleTimerWhenPingIsAlreadyResolved(): void { $deferred = new Deferred(); $client = $this->createMock(StreamingClient::class); @@ -341,12 +341,13 @@ public function testCloseAfterPingWillCancelIdleTimerWhenPingIsAlreadyResolved() $this->redis->close(); } - public function testCloseAfterPingRejectsWillEmitClose() + public function testCloseAfterPingRejectsWillEmitClose(): void { $deferred = new Deferred(); $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->willReturn($deferred->promise()); $client->expects($this->once())->method('close')->willReturnCallback(function () use ($client) { + assert($client instanceof StreamingClient); $client->emit('close'); }); @@ -356,21 +357,20 @@ public function testCloseAfterPingRejectsWillEmitClose() $this->loop->expects($this->once())->method('addTimer')->willReturn($timer); $this->loop->expects($this->once())->method('cancelTimer')->with($timer); - $ref = $this->redis; - $ref->ping()->then(null, function () use ($ref, $client) { - $ref->close(); + $this->redis->ping()->then(null, function () { + $this->redis->close(); }); - $ref->on('close', $this->expectCallableOnce()); + $this->redis->on('close', $this->expectCallableOnce()); $deferred->reject(new \RuntimeException()); } - public function testEndWillCloseClientIfUnderlyingConnectionIsNotPending() + public function testEndWillCloseClientIfUnderlyingConnectionIsNotPending(): void { $this->redis->on('close', $this->expectCallableOnce()); $this->redis->end(); } - public function testEndAfterPingWillEndUnderlyingClient() + public function testEndAfterPingWillEndUnderlyingClient(): void { $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->with('ping')->willReturn(\React\Promise\resolve('PONG')); @@ -384,7 +384,7 @@ public function testEndAfterPingWillEndUnderlyingClient() $this->redis->end(); } - public function testEndAfterPingWillCloseClientWhenUnderlyingClientEmitsClose() + public function testEndAfterPingWillCloseClientWhenUnderlyingClientEmitsClose(): void { $closeHandler = null; $client = $this->createMock(StreamingClient::class); @@ -409,7 +409,7 @@ public function testEndAfterPingWillCloseClientWhenUnderlyingClientEmitsClose() $closeHandler(); } - public function testEmitsNoErrorEventWhenUnderlyingClientEmitsError() + public function testEmitsNoErrorEventWhenUnderlyingClientEmitsError(): void { $error = new \RuntimeException(); @@ -423,10 +423,11 @@ public function testEmitsNoErrorEventWhenUnderlyingClientEmitsError() $deferred->resolve($client); $this->redis->on('error', $this->expectCallableNever()); + assert($client instanceof StreamingClient); $client->emit('error', [$error]); } - public function testEmitsNoCloseEventWhenUnderlyingClientEmitsClose() + public function testEmitsNoCloseEventWhenUnderlyingClientEmitsClose(): void { $client = $this->createMock(StreamingClient::class); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve(null)); @@ -438,10 +439,11 @@ public function testEmitsNoCloseEventWhenUnderlyingClientEmitsClose() $deferred->resolve($client); $this->redis->on('close', $this->expectCallableNever()); + assert($client instanceof StreamingClient); $client->emit('close'); } - public function testEmitsNoCloseEventButWillCancelIdleTimerWhenUnderlyingConnectionEmitsCloseAfterPingIsAlreadyResolved() + public function testEmitsNoCloseEventButWillCancelIdleTimerWhenUnderlyingConnectionEmitsCloseAfterPingIsAlreadyResolved(): void { $closeHandler = null; $client = $this->createMock(StreamingClient::class); @@ -469,7 +471,7 @@ public function testEmitsNoCloseEventButWillCancelIdleTimerWhenUnderlyingConnect $closeHandler(); } - public function testEmitsMessageEventWhenUnderlyingClientEmitsMessageForPubSubChannel() + public function testEmitsMessageEventWhenUnderlyingClientEmitsMessageForPubSubChannel(): void { $messageHandler = null; $client = $this->createMock(StreamingClient::class); @@ -491,7 +493,7 @@ public function testEmitsMessageEventWhenUnderlyingClientEmitsMessageForPubSubCh $messageHandler('foo', 'bar'); } - public function testEmitsUnsubscribeAndPunsubscribeEventsWhenUnderlyingClientClosesWhileUsingPubSubChannel() + public function testEmitsUnsubscribeAndPunsubscribeEventsWhenUnderlyingClientClosesWhileUsingPubSubChannel(): void { $allHandler = null; $client = $this->createMock(StreamingClient::class); @@ -505,27 +507,25 @@ public function testEmitsUnsubscribeAndPunsubscribeEventsWhenUnderlyingClientClo $this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client)); $this->redis->subscribe('foo'); - $this->assertTrue(is_callable($allHandler['subscribe'])); + assert(isset($allHandler['subscribe']) && is_callable($allHandler['subscribe'])); $allHandler['subscribe']('foo', 1); $this->redis->subscribe('bar'); - $this->assertTrue(is_callable($allHandler['subscribe'])); $allHandler['subscribe']('bar', 2); $this->redis->unsubscribe('bar'); - $this->assertTrue(is_callable($allHandler['unsubscribe'])); + assert(isset($allHandler['unsubscribe']) && is_callable($allHandler['unsubscribe'])); $allHandler['unsubscribe']('bar', 1); $this->redis->psubscribe('foo*'); - $this->assertTrue(is_callable($allHandler['psubscribe'])); + assert(isset($allHandler['psubscribe']) && is_callable($allHandler['psubscribe'])); $allHandler['psubscribe']('foo*', 1); $this->redis->psubscribe('bar*'); - $this->assertTrue(is_callable($allHandler['psubscribe'])); $allHandler['psubscribe']('bar*', 2); $this->redis->punsubscribe('bar*'); - $this->assertTrue(is_callable($allHandler['punsubscribe'])); + assert(isset($allHandler['punsubscribe']) && is_callable($allHandler['punsubscribe'])); $allHandler['punsubscribe']('bar*', 1); $this->redis->on('unsubscribe', $this->expectCallableOnce()); @@ -535,7 +535,7 @@ public function testEmitsUnsubscribeAndPunsubscribeEventsWhenUnderlyingClientClo $allHandler['close'](); } - public function testSubscribeWillResolveWhenUnderlyingClientResolvesSubscribeAndNotStartIdleTimerWithIdleDueToSubscription() + public function testSubscribeWillResolveWhenUnderlyingClientResolvesSubscribeAndNotStartIdleTimerWithIdleDueToSubscription(): void { $subscribeHandler = null; $deferred = new Deferred(); @@ -559,7 +559,7 @@ public function testSubscribeWillResolveWhenUnderlyingClientResolvesSubscribeAnd $promise->then($this->expectCallableOnceWith(['subscribe', 'foo', 1])); } - public function testUnsubscribeAfterSubscribeWillResolveWhenUnderlyingClientResolvesUnsubscribeAndStartIdleTimerWhenSubscriptionStopped() + public function testUnsubscribeAfterSubscribeWillResolveWhenUnderlyingClientResolvesUnsubscribeAndStartIdleTimerWhenSubscriptionStopped(): void { $subscribeHandler = null; $unsubscribeHandler = null; @@ -593,7 +593,7 @@ public function testUnsubscribeAfterSubscribeWillResolveWhenUnderlyingClientReso $promise->then($this->expectCallableOnceWith(['unsubscribe', 'foo', 0])); } - public function testBlpopWillRejectWhenUnderlyingClientClosesWhileWaitingForResponse() + public function testBlpopWillRejectWhenUnderlyingClientClosesWhileWaitingForResponse(): void { $closeHandler = null; $deferred = new Deferred(); diff --git a/tests/TestCase.php b/tests/TestCase.php index 4a73762..b79f7ab 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -13,14 +13,17 @@ protected function expectCallableOnce(): callable { $mock = $this->createCallableMock(); $mock->expects($this->once())->method('__invoke'); + assert(is_callable($mock)); return $mock; } + /** @param mixed $argument */ protected function expectCallableOnceWith($argument): callable { $mock = $this->createCallableMock(); $mock->expects($this->once())->method('__invoke')->with($argument); + assert(is_callable($mock)); return $mock; } @@ -29,6 +32,7 @@ protected function expectCallableNever(): callable { $mock = $this->createCallableMock(); $mock->expects($this->never())->method('__invoke'); + assert(is_callable($mock)); return $mock; } @@ -36,7 +40,7 @@ protected function expectCallableNever(): callable protected function createCallableMock(): MockObject { if (method_exists(MockBuilder::class, 'addMethods')) { - // PHPUnit 9+ + // @phpstan-ignore-next-line requires PHPUnit 9+ return $this->getMockBuilder(\stdClass::class)->addMethods(['__invoke'])->getMock(); } else { // legacy PHPUnit < 9