diff --git a/.gitattributes b/.gitattributes index 0925d33..eccc763 100644 --- a/.gitattributes +++ b/.gitattributes @@ -3,4 +3,5 @@ /.travis.yml export-ignore /examples/ export-ignore /phpunit.xml.dist export-ignore +/phpunit.xml.legacy export-ignore /tests/ export-ignore diff --git a/.travis.yml b/.travis.yml index cc9e05b..e8bf15b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,4 +29,5 @@ install: - composer install script: - - vendor/bin/phpunit --coverage-text + - if [[ "$TRAVIS_PHP_VERSION" > "7.2" ]]; then vendor/bin/phpunit --coverage-text; fi + - if [[ "$TRAVIS_PHP_VERSION" < "7.3" ]]; then vendor/bin/phpunit --coverage-text -c phpunit.xml.legacy; fi diff --git a/composer.json b/composer.json index d5a7138..ebcf44b 100644 --- a/composer.json +++ b/composer.json @@ -21,7 +21,7 @@ }, "require-dev": { "clue/block-react": "^1.1", - "phpunit/phpunit": "^9.0 || ^5.7 || ^4.8.35" + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35" }, "autoload": { "psr-4": { "Clue\\React\\Redis\\": "src/" } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5395aa6..e19a12c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,14 +1,19 @@ - + + ./tests/ - - + + ./src/ - - - \ No newline at end of file + + + diff --git a/phpunit.xml.legacy b/phpunit.xml.legacy new file mode 100644 index 0000000..8d93c4f --- /dev/null +++ b/phpunit.xml.legacy @@ -0,0 +1,18 @@ + + + + + + + ./tests/ + + + + + ./src/ + + + diff --git a/tests/FactoryStreamingClientTest.php b/tests/FactoryStreamingClientTest.php index afe63db..2c577a1 100644 --- a/tests/FactoryStreamingClientTest.php +++ b/tests/FactoryStreamingClientTest.php @@ -136,27 +136,45 @@ public function testWillWriteAuthCommandIfRedisUnixUriContainsUserInfo() public function testWillResolveWhenAuthCommandReceivesOkResponseIfRedisUriContainsUserInfo() { - $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write'))->getMock(); + $dataHandler = null; + $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); + $stream->expects($this->exactly(2))->method('on')->withConsecutive( + array('data', $this->callback(function ($arg) use (&$dataHandler) { + $dataHandler = $arg; + return true; + })), + array('close', $this->anything()) + ); $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); $promise = $this->factory->createClient('redis://:world@localhost'); - $stream->emit('data', array("+OK\r\n")); + $this->assertTrue(is_callable($dataHandler)); + $dataHandler("+OK\r\n"); $promise->then($this->expectCallableOnceWith($this->isInstanceOf('Clue\React\Redis\Client'))); } public function testWillRejectAndCloseAutomaticallyWhenAuthCommandReceivesErrorResponseIfRedisUriContainsUserInfo() { - $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock(); + $dataHandler = null; + $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); $stream->expects($this->once())->method('write')->with("*2\r\n$4\r\nauth\r\n$5\r\nworld\r\n"); $stream->expects($this->once())->method('close'); + $stream->expects($this->exactly(2))->method('on')->withConsecutive( + array('data', $this->callback(function ($arg) use (&$dataHandler) { + $dataHandler = $arg; + return true; + })), + array('close', $this->anything()) + ); $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); $promise = $this->factory->createClient('redis://:world@localhost'); - $stream->emit('data', array("-ERR invalid password\r\n")); + $this->assertTrue(is_callable($dataHandler)); + $dataHandler("-ERR invalid password\r\n"); $promise->then(null, $this->expectCallableOnceWith( $this->logicalAnd( @@ -182,27 +200,45 @@ public function testWillWriteSelectCommandIfRedisUnixUriContainsDbQueryParameter public function testWillResolveWhenSelectCommandReceivesOkResponseIfRedisUriContainsPath() { - $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write'))->getMock(); + $dataHandler = null; + $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$3\r\n123\r\n"); + $stream->expects($this->exactly(2))->method('on')->withConsecutive( + array('data', $this->callback(function ($arg) use (&$dataHandler) { + $dataHandler = $arg; + return true; + })), + array('close', $this->anything()) + ); $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); $promise = $this->factory->createClient('redis://localhost/123'); - $stream->emit('data', array("+OK\r\n")); + $this->assertTrue(is_callable($dataHandler)); + $dataHandler("+OK\r\n"); $promise->then($this->expectCallableOnceWith($this->isInstanceOf('Clue\React\Redis\Client'))); } public function testWillRejectAndCloseAutomaticallyWhenSelectCommandReceivesErrorResponseIfRedisUriContainsPath() { - $stream = $this->getMockBuilder('React\Socket\Connection')->disableOriginalConstructor()->setMethods(array('write', 'close'))->getMock(); + $dataHandler = null; + $stream = $this->getMockBuilder('React\Socket\ConnectionInterface')->getMock(); $stream->expects($this->once())->method('write')->with("*2\r\n$6\r\nselect\r\n$3\r\n123\r\n"); $stream->expects($this->once())->method('close'); + $stream->expects($this->exactly(2))->method('on')->withConsecutive( + array('data', $this->callback(function ($arg) use (&$dataHandler) { + $dataHandler = $arg; + return true; + })), + array('close', $this->anything()) + ); $this->connector->expects($this->once())->method('connect')->willReturn(Promise\resolve($stream)); $promise = $this->factory->createClient('redis://localhost/123'); - $stream->emit('data', array("-ERR DB index is out of range\r\n")); + $this->assertTrue(is_callable($dataHandler)); + $dataHandler("-ERR DB index is out of range\r\n"); $promise->then(null, $this->expectCallableOnceWith( $this->logicalAnd( diff --git a/tests/FunctionalTest.php b/tests/FunctionalTest.php index 0f5549d..7e70b71 100644 --- a/tests/FunctionalTest.php +++ b/tests/FunctionalTest.php @@ -149,10 +149,11 @@ public function testPubSub() $deferred = new Deferred(); $consumer->on('message', $this->expectCallableOnce()); $consumer->on('message', array($deferred, 'resolve')); - $consumer->subscribe($channel)->then($this->expectCallableOnce()); - - // producer sends a single message - $producer->publish($channel, 'hello world')->then($this->expectCallableOnceWith(1)); + $once = $this->expectCallableOnceWith(1); + $consumer->subscribe($channel)->then(function() use ($producer, $channel, $once){ + // producer sends a single message + $producer->publish($channel, 'hello world')->then($once); + })->then($this->expectCallableOnce()); // expect "message" event to take no longer than 0.1s Block\await($deferred->promise(), $this->loop, 0.1); diff --git a/tests/LazyClientTest.php b/tests/LazyClientTest.php index 0df029a..bf3ceb6 100644 --- a/tests/LazyClientTest.php +++ b/tests/LazyClientTest.php @@ -156,8 +156,15 @@ public function testPingAfterPreviousFactoryRejectsUnderlyingClientWillCreateNew public function testPingAfterPreviousUnderlyingClientAlreadyClosedWillCreateNewUnderlyingConnection() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $closeHandler = null; + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->with('ping')->willReturn(\React\Promise\resolve('PONG')); + $client->expects($this->any())->method('on')->withConsecutive( + array('close', $this->callback(function ($arg) use (&$closeHandler) { + $closeHandler = $arg; + return true; + })) + ); $this->factory->expects($this->exactly(2))->method('createClient')->willReturnOnConsecutiveCalls( \React\Promise\resolve($client), @@ -165,7 +172,8 @@ public function testPingAfterPreviousUnderlyingClientAlreadyClosedWillCreateNewU ); $this->client->ping(); - $client->emit('close'); + $this->assertTrue(is_callable($closeHandler)); + $closeHandler(); $this->client->ping(); } @@ -183,7 +191,7 @@ public function testPingAfterCloseWillRejectWithoutCreatingUnderlyingConnection( public function testPingAfterPingWillNotStartIdleTimerWhenFirstPingResolves() { $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->exactly(2))->method('__call')->willReturnOnConsecutiveCalls( $deferred->promise(), new Promise(function () { }) @@ -201,7 +209,7 @@ public function testPingAfterPingWillNotStartIdleTimerWhenFirstPingResolves() public function testPingAfterPingWillStartAndCancelIdleTimerWhenSecondPingStartsAfterFirstResolves() { $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->exactly(2))->method('__call')->willReturnOnConsecutiveCalls( $deferred->promise(), new Promise(function () { }) @@ -220,7 +228,7 @@ public function testPingAfterPingWillStartAndCancelIdleTimerWhenSecondPingStarts public function testPingFollowedByIdleTimerWillCloseUnderlyingConnectionWithoutCloseEvent() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call', 'close'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve()); $client->expects($this->once())->method('close')->willReturn(\React\Promise\resolve()); @@ -298,7 +306,7 @@ public function testCloseAfterPingWillCloseUnderlyingClientConnectionWhenAlready public function testCloseAfterPingWillCancelIdleTimerWhenPingIsAlreadyResolved() { $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call', 'close'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn($deferred->promise()); $client->expects($this->once())->method('close'); @@ -316,7 +324,7 @@ public function testCloseAfterPingWillCancelIdleTimerWhenPingIsAlreadyResolved() public function testCloseAfterPingRejectsWillEmitClose() { $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call', 'close'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn($deferred->promise()); $client->expects($this->once())->method('close')->willReturnCallback(function () use ($client) { $client->emit('close'); @@ -358,9 +366,15 @@ public function testEndAfterPingWillEndUnderlyingClient() public function testEndAfterPingWillCloseClientWhenUnderlyingClientEmitsClose() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call', 'end'))->getMock(); + $closeHandler = null; + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->with('ping')->willReturn(\React\Promise\resolve('PONG')); $client->expects($this->once())->method('end'); + $client->expects($this->any())->method('on')->willReturnCallback(function ($event, $callback) use (&$closeHandler) { + if ($event === 'close') { + $closeHandler = $callback; + } + }); $deferred = new Deferred(); $this->factory->expects($this->once())->method('createClient')->willReturn($deferred->promise()); @@ -371,14 +385,15 @@ public function testEndAfterPingWillCloseClientWhenUnderlyingClientEmitsClose() $this->client->on('close', $this->expectCallableOnce()); $this->client->end(); - $client->emit('close'); + $this->assertTrue(is_callable($closeHandler)); + $closeHandler(); } public function testEmitsNoErrorEventWhenUnderlyingClientEmitsError() { $error = new \RuntimeException(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve()); $deferred = new Deferred(); @@ -393,7 +408,7 @@ public function testEmitsNoErrorEventWhenUnderlyingClientEmitsError() public function testEmitsNoCloseEventWhenUnderlyingClientEmitsClose() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve()); $deferred = new Deferred(); @@ -408,9 +423,16 @@ public function testEmitsNoCloseEventWhenUnderlyingClientEmitsClose() public function testEmitsNoCloseEventButWillCancelIdleTimerWhenUnderlyingConnectionEmitsCloseAfterPingIsAlreadyResolved() { + $closeHandler = null; + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); $client->expects($this->once())->method('__call')->willReturn($deferred->promise()); + $client->expects($this->any())->method('on')->withConsecutive( + array('close', $this->callback(function ($arg) use (&$closeHandler) { + $closeHandler = $arg; + return true; + })) + ); $this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client)); @@ -423,13 +445,20 @@ public function testEmitsNoCloseEventButWillCancelIdleTimerWhenUnderlyingConnect $this->client->ping(); $deferred->resolve(); - $client->emit('close'); + $this->assertTrue(is_callable($closeHandler)); + $closeHandler(); } public function testEmitsMessageEventWhenUnderlyingClientEmitsMessageForPubSubChannel() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $messageHandler = null; + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->willReturn(\React\Promise\resolve()); + $client->expects($this->any())->method('on')->willReturnCallback(function ($event, $callback) use (&$messageHandler) { + if ($event === 'message') { + $messageHandler = $callback; + } + }); $deferred = new Deferred(); $this->factory->expects($this->once())->method('createClient')->willReturn($deferred->promise()); @@ -438,51 +467,73 @@ public function testEmitsMessageEventWhenUnderlyingClientEmitsMessageForPubSubCh $deferred->resolve($client); $this->client->on('message', $this->expectCallableOnce()); - $client->emit('message', array('foo', 'bar')); + $this->assertTrue(is_callable($messageHandler)); + $messageHandler('foo', 'bar'); } public function testEmitsUnsubscribeAndPunsubscribeEventsWhenUnderlyingClientClosesWhileUsingPubSubChannel() { - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $allHandler = null; + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->exactly(6))->method('__call')->willReturn(\React\Promise\resolve()); + $client->expects($this->any())->method('on')->willReturnCallback(function ($event, $callback) use (&$allHandler) { + if (!isset($allHandler[$event])) { + $allHandler[$event] = $callback; + } + }); $this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client)); $this->client->subscribe('foo'); - $client->emit('subscribe', array('foo', 1)); + $this->assertTrue(is_callable($allHandler['subscribe'])); + $allHandler['subscribe']('foo', 1); $this->client->subscribe('bar'); - $client->emit('subscribe', array('bar', 2)); + $this->assertTrue(is_callable($allHandler['subscribe'])); + $allHandler['subscribe']('bar', 2); $this->client->unsubscribe('bar'); - $client->emit('unsubscribe', array('bar', 1)); + $this->assertTrue(is_callable($allHandler['unsubscribe'])); + $allHandler['unsubscribe']('bar', 1); $this->client->psubscribe('foo*'); - $client->emit('psubscribe', array('foo*', 1)); + $this->assertTrue(is_callable($allHandler['psubscribe'])); + $allHandler['psubscribe']('foo*', 1); $this->client->psubscribe('bar*'); - $client->emit('psubscribe', array('bar*', 2)); + $this->assertTrue(is_callable($allHandler['psubscribe'])); + $allHandler['psubscribe']('bar*', 2); $this->client->punsubscribe('bar*'); - $client->emit('punsubscribe', array('bar*', 1)); + $this->assertTrue(is_callable($allHandler['punsubscribe'])); + $allHandler['punsubscribe']('bar*', 1); $this->client->on('unsubscribe', $this->expectCallableOnce()); $this->client->on('punsubscribe', $this->expectCallableOnce()); - $client->emit('close'); + + $this->assertTrue(is_callable($allHandler['close'])); + $allHandler['close'](); } public function testSubscribeWillResolveWhenUnderlyingClientResolvesSubscribeAndNotStartIdleTimerWithIdleDueToSubscription() { + $subscribeHandler = null; $deferred = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->once())->method('__call')->with('subscribe')->willReturn($deferred->promise()); + $client->expects($this->any())->method('on')->willReturnCallback(function ($event, $callback) use (&$subscribeHandler) { + if ($event === 'subscribe' && $subscribeHandler === null) { + $subscribeHandler = $callback; + } + }); $this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client)); $this->loop->expects($this->never())->method('addTimer'); $promise = $this->client->subscribe('foo'); - $client->emit('subscribe', array('foo', 1)); + $this->assertTrue(is_callable($subscribeHandler)); + $subscribeHandler('foo', 1); $deferred->resolve(array('subscribe', 'foo', 1)); $promise->then($this->expectCallableOnceWith(array('subscribe', 'foo', 1))); @@ -490,23 +541,46 @@ public function testSubscribeWillResolveWhenUnderlyingClientResolvesSubscribeAnd public function testUnsubscribeAfterSubscribeWillResolveWhenUnderlyingClientResolvesUnsubscribeAndStartIdleTimerWhenSubscriptionStopped() { + $subscribeHandler = null; + $unsubscribeHandler = null; $deferredSubscribe = new Deferred(); $deferredUnsubscribe = new Deferred(); - $client = $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods(array('__call'))->getMock(); + $client = $this->getMockBuilder('Clue\React\Redis\Client')->getMock(); $client->expects($this->exactly(2))->method('__call')->willReturnOnConsecutiveCalls($deferredSubscribe->promise(), $deferredUnsubscribe->promise()); + $client->expects($this->any())->method('on')->willReturnCallback(function ($event, $callback) use (&$subscribeHandler, &$unsubscribeHandler) { + if ($event === 'subscribe' && $subscribeHandler === null) { + $subscribeHandler = $callback; + } + if ($event === 'unsubscribe' && $unsubscribeHandler === null) { + $unsubscribeHandler = $callback; + } + }); $this->factory->expects($this->once())->method('createClient')->willReturn(\React\Promise\resolve($client)); $this->loop->expects($this->once())->method('addTimer'); $promise = $this->client->subscribe('foo'); - $client->emit('subscribe', array('foo', 1)); + $this->assertTrue(is_callable($subscribeHandler)); + $subscribeHandler('foo', 1); $deferredSubscribe->resolve(array('subscribe', 'foo', 1)); $promise->then($this->expectCallableOnceWith(array('subscribe', 'foo', 1))); $promise = $this->client->unsubscribe('foo'); - $client->emit('unsubscribe', array('foo', 0)); + $this->assertTrue(is_callable($unsubscribeHandler)); + $unsubscribeHandler('foo', 0); $deferredUnsubscribe->resolve(array('unsubscribe', 'foo', 0)); $promise->then($this->expectCallableOnceWith(array('unsubscribe', 'foo', 0))); } + + public function createCallableMockWithOriginalConstructorDisabled($array) + { + if (method_exists('PHPUnit\Framework\MockObject\MockBuilder', 'addMethods')) { + // PHPUnit 9+ + return $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->onlyMethods($array)->getMock(); + } else { + // legacy PHPUnit 4 - PHPUnit 8 + return $this->getMockBuilder('Clue\React\Redis\StreamingClient')->disableOriginalConstructor()->setMethods($array)->getMock(); + } + } }