From 6372661dbfba9bc1e4aba107e036af9ee2646776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20Bok?= Date: Wed, 13 Sep 2023 18:59:50 +0200 Subject: [PATCH] Make Relay work with Redis cluster (#1397) --- .coveralls.yml | 1 + .gitattributes | 1 + .../workflows/cluster}/Dockerfile | 0 .../workflows/cluster}/create_cluster.sh | 0 .../workflows/cluster}/docker-compose.yml | 0 .../workflows/cluster}/redis.conf | 0 .github/workflows/linters.yml | 2 +- .github/workflows/tests.yml | 19 ++- src/Connection/Cluster/RedisCluster.php | 8 + src/Connection/RelayConnection.php | 23 ++- tests/Predis/Command/Redis/CLUSTER_Test.php | 15 +- tests/Predis/Command/Redis/GET_Test.php | 40 +++++ tests/Predis/Command/Redis/HGETALL_Test.php | 26 +++- tests/Predis/Command/Redis/HGET_Test.php | 26 +++- tests/Predis/Command/Redis/HSET_Test.php | 26 +++- tests/Predis/Command/Redis/SET_Test.php | 51 ++++++- .../Connection/Cluster/RedisClusterTest.php | 20 ++- .../Predis/Connection/RelayConnectionTest.php | 140 ++++++++++++++++++ 18 files changed, 379 insertions(+), 19 deletions(-) create mode 100644 .coveralls.yml rename {docker/unstable_cluster => .github/workflows/cluster}/Dockerfile (100%) rename {docker/unstable_cluster => .github/workflows/cluster}/create_cluster.sh (100%) rename {docker/unstable_cluster => .github/workflows/cluster}/docker-compose.yml (100%) rename {docker/unstable_cluster => .github/workflows/cluster}/redis.conf (100%) diff --git a/.coveralls.yml b/.coveralls.yml new file mode 100644 index 00000000..c8f7bd3d --- /dev/null +++ b/.coveralls.yml @@ -0,0 +1 @@ +coverage_clover: build/logs/clover-*.xml diff --git a/.gitattributes b/.gitattributes index 226cb6ab..93cba263 100644 --- a/.gitattributes +++ b/.gitattributes @@ -5,6 +5,7 @@ /examples export-ignore /tests export-ignore /.codespellrc export-ignore linguist-language=INI +/.coveralls.yml export-ignore /.editorconfig export-ignore /.gitattributes export-ignore /.gitignore export-ignore diff --git a/docker/unstable_cluster/Dockerfile b/.github/workflows/cluster/Dockerfile similarity index 100% rename from docker/unstable_cluster/Dockerfile rename to .github/workflows/cluster/Dockerfile diff --git a/docker/unstable_cluster/create_cluster.sh b/.github/workflows/cluster/create_cluster.sh similarity index 100% rename from docker/unstable_cluster/create_cluster.sh rename to .github/workflows/cluster/create_cluster.sh diff --git a/docker/unstable_cluster/docker-compose.yml b/.github/workflows/cluster/docker-compose.yml similarity index 100% rename from docker/unstable_cluster/docker-compose.yml rename to .github/workflows/cluster/docker-compose.yml diff --git a/docker/unstable_cluster/redis.conf b/.github/workflows/cluster/redis.conf similarity index 100% rename from docker/unstable_cluster/redis.conf rename to .github/workflows/cluster/redis.conf diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 1e22c899..b6d481b0 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -136,7 +136,7 @@ jobs: EXPECTED="LICENSE,README.md,autoload.php,composer.json" CURRENT="$( git archive HEAD \ - | tar --list --exclude="src" --exclude="src/*" --exclude="bin" --exclude="bin/*" --exclude="docker" --exclude="docker/*" \ + | tar --list --exclude="src" --exclude="src/*" --exclude="bin" --exclude="bin/*" \ | paste --serial --delimiters="," )" echo "CURRENT =${CURRENT}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 44f3b7f3..acc4fa4d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -65,12 +65,16 @@ jobs: - name: Run tests with coverage if: ${{ matrix.php == '8.1' && matrix.redis == '7' }} - run: vendor/bin/phpunit --coverage-clover build/logs/clover.xml --coverage-filter ./src + run: vendor/bin/phpunit --coverage-clover build/logs/clover-default.xml --coverage-filter ./src - name: Run tests using Relay if: ${{ matrix.redis >= '6' }} run: vendor/bin/phpunit -c phpunit.relay.xml + - name: Run tests using Relay with coverage + if: ${{ matrix.php == '8.1' && matrix.redis == '7' }} + run: vendor/bin/phpunit -c phpunit.relay.xml --coverage-clover build/logs/clover-relay.xml --coverage-filter ./src + - name: Send coverage to Coveralls env: COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -99,12 +103,12 @@ jobs: - name: Checkout repository uses: actions/checkout@v3 - - name: Run redis cluster + - name: Run Redis cluster uses: isbang/compose-action@v1.4.1 with: - compose-file: "./docker/unstable_cluster/docker-compose.yml" + compose-file: .github/workflows/cluster/docker-compose.yml - - name: Setup PHP with Composer and extensions + - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php }} @@ -119,5 +123,10 @@ jobs: - name: Run tests against cluster run: | - sleep 5 # Timeout to make sure that docker image is setup + sleep 5 # make sure that docker image is setup vendor/bin/phpunit --group cluster + + - name: Run tests against cluster using Relay + run: | + sleep 5 # make sure nodes are stable and fully joined + vendor/bin/phpunit -c phpunit.relay.xml --group cluster diff --git a/src/Connection/Cluster/RedisCluster.php b/src/Connection/Cluster/RedisCluster.php index 7f3013c1..ae2e18db 100644 --- a/src/Connection/Cluster/RedisCluster.php +++ b/src/Connection/Cluster/RedisCluster.php @@ -464,6 +464,14 @@ class RedisCluster implements ClusterInterface, IteratorAggregate, Countable { [$slot, $connectionID] = explode(' ', $details, 2); + // Handle connection ID in the form of "IP:port (details about exception)" + // by trimming everything after first space (including the space) + $startPositionOfExtraDetails = strpos($connectionID, ' '); + + if ($startPositionOfExtraDetails !== false) { + $connectionID = substr($connectionID, 0, $startPositionOfExtraDetails); + } + if (!$connection = $this->getConnectionById($connectionID)) { $connection = $this->createConnection($connectionID); } diff --git a/src/Connection/RelayConnection.php b/src/Connection/RelayConnection.php index 4ff674f5..aa78e313 100644 --- a/src/Connection/RelayConnection.php +++ b/src/Connection/RelayConnection.php @@ -16,6 +16,7 @@ use InvalidArgumentException; use Predis\ClientException; use Predis\Command\CommandInterface; use Predis\NotSupportedException; +use Predis\Response\ErrorInterface as ErrorResponseInterface; use Predis\Response\ServerException; use Relay\Exception as RelayException; use Relay\Relay; @@ -198,9 +199,13 @@ class RelayConnection extends StreamConnection /** * {@inheritdoc} */ - protected function getIdentifier() + public function getIdentifier() { - return $this->client->endpointId(); + try { + return $this->client->endpointId(); + } catch (RelayException $ex) { + return parent::getIdentifier(); + } } /** @@ -253,7 +258,13 @@ class RelayConnection extends StreamConnection ? $this->client->{$name}(...$command->getArguments()) : $this->client->rawCommand($name, ...$command->getArguments()); } catch (RelayException $ex) { - throw $this->onCommandError($ex, $command); + $exception = $this->onCommandError($ex, $command); + + if ($exception instanceof ErrorResponseInterface) { + return $exception; + } + + throw $exception; } } @@ -265,15 +276,15 @@ class RelayConnection extends StreamConnection $code = $exception->getCode(); $message = $exception->getMessage(); - if (strpos($message, 'RELAY_ERR_IO')) { + if (strpos($message, 'RELAY_ERR_IO') !== false) { return new ConnectionException($this, $message, $code, $exception); } - if (strpos($message, 'RELAY_ERR_REDIS')) { + if (strpos($message, 'RELAY_ERR_REDIS') !== false) { return new ServerException($message, $code, $exception); } - if (strpos($message, 'RELAY_ERR_WRONGTYPE') && strpos($message, "Got reply-type 'status'")) { + if (strpos($message, 'RELAY_ERR_WRONGTYPE') !== false && strpos($message, "Got reply-type 'status'") !== false) { $message = 'Operation against a key holding the wrong kind of value'; } diff --git a/tests/Predis/Command/Redis/CLUSTER_Test.php b/tests/Predis/Command/Redis/CLUSTER_Test.php index 097f3716..f693db75 100644 --- a/tests/Predis/Command/Redis/CLUSTER_Test.php +++ b/tests/Predis/Command/Redis/CLUSTER_Test.php @@ -96,7 +96,20 @@ class CLUSTER_Test extends PredisCommandTestCase { $redis = $this->getClient(); - [$startSlot, $endSlot] = $redis->cluster->shards()[0][1]; + // Sometimes the cluster can be in a state where slots are + // missing on some shards (e.g. they are being rebalanced) + $shards = $redis->cluster->shards(); + $slots = $shards[0][1] ?? $shards[0]['slots']; + + if (empty($slots)) { + $slots = $shards[1][1] ?? $shards[1]['slots']; + } + + if (empty($slots)) { + $slots = $shards[2][1] ?? $shards[2]['slots']; + } + + [$startSlot, $endSlot] = $slots; $this->assertEquals('OK', $redis->cluster->delSlotsRange($startSlot, $endSlot)); $this->assertEquals('OK', $redis->cluster->addSlotsRange($startSlot, $endSlot)); diff --git a/tests/Predis/Command/Redis/GET_Test.php b/tests/Predis/Command/Redis/GET_Test.php index 80c047b7..3409ae34 100644 --- a/tests/Predis/Command/Redis/GET_Test.php +++ b/tests/Predis/Command/Redis/GET_Test.php @@ -67,6 +67,16 @@ class GET_Test extends PredisCommandTestCase $this->assertEquals('bar', $redis->get('foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsStringValueUsingCluster(): void + { + $this->testReturnsStringValue(); + } + /** * @group connected */ @@ -80,6 +90,16 @@ class GET_Test extends PredisCommandTestCase $this->assertSame('', $redis->get('foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsEmptyStringOnEmptyStringsUsingCluster(): void + { + $this->testReturnsEmptyStringOnEmptyStrings(); + } + /** * @group connected */ @@ -91,6 +111,16 @@ class GET_Test extends PredisCommandTestCase $this->assertNull($redis->get('foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsNullOnNonExistingKeysUsingCluster(): void + { + $this->testReturnsNullOnNonExistingKeys(); + } + /** * @group connected */ @@ -104,4 +134,14 @@ class GET_Test extends PredisCommandTestCase $redis->rpush('metavars', 'foo'); $redis->get('metavars'); } + + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testThrowsExceptionOnWrongTypeUsingCluster(): void + { + $this->testThrowsExceptionOnWrongType(); + } } diff --git a/tests/Predis/Command/Redis/HGETALL_Test.php b/tests/Predis/Command/Redis/HGETALL_Test.php index 308a3f58..41e799f3 100644 --- a/tests/Predis/Command/Redis/HGETALL_Test.php +++ b/tests/Predis/Command/Redis/HGETALL_Test.php @@ -75,6 +75,20 @@ class HGETALL_Test extends PredisCommandTestCase $this->assertSame([], $redis->hgetall('unknown')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsAllTheFieldsAndTheirValuesUsingCluster(): void + { + $redis = $this->getClient(); + + $redis->del('metavars'); + + $this->testReturnsAllTheFieldsAndTheirValues(); + } + /** * @group connected * @requiresRedisVersion >= 2.0.0 @@ -82,11 +96,21 @@ class HGETALL_Test extends PredisCommandTestCase public function testThrowsExceptionOnWrongType(): void { $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + $this->expectExceptionMessageMatches('/.*Operation against a key holding the wrong kind of value.*/'); $redis = $this->getClient(); $redis->set('foo', 'bar'); $redis->hgetall('foo'); } + + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testThrowsExceptionOnWrongTypeUsingCluster(): void + { + $this->testThrowsExceptionOnWrongType(); + } } diff --git a/tests/Predis/Command/Redis/HGET_Test.php b/tests/Predis/Command/Redis/HGET_Test.php index 443cb4e8..3efe2a35 100644 --- a/tests/Predis/Command/Redis/HGET_Test.php +++ b/tests/Predis/Command/Redis/HGET_Test.php @@ -71,6 +71,20 @@ class HGET_Test extends PredisCommandTestCase $this->assertNull($redis->hget('unknown', 'foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testReturnsValueOfSpecifiedFieldUsingCluster(): void + { + $redis = $this->getClient(); + + $redis->del('metavars'); + + $this->testReturnsValueOfSpecifiedField(); + } + /** * @group connected * @requiresRedisVersion >= 2.0.0 @@ -78,11 +92,21 @@ class HGET_Test extends PredisCommandTestCase public function testThrowsExceptionOnWrongType(): void { $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + $this->expectExceptionMessageMatches('/.*Operation against a key holding the wrong kind of value.*/'); $redis = $this->getClient(); $redis->set('foo', 'bar'); $redis->hget('foo', 'bar'); } + + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testThrowsExceptionOnWrongTypeUsingCluster(): void + { + $this->testThrowsExceptionOnWrongType(); + } } diff --git a/tests/Predis/Command/Redis/HSET_Test.php b/tests/Predis/Command/Redis/HSET_Test.php index e6d77ae2..6cd60918 100644 --- a/tests/Predis/Command/Redis/HSET_Test.php +++ b/tests/Predis/Command/Redis/HSET_Test.php @@ -73,6 +73,20 @@ class HSET_Test extends PredisCommandTestCase $this->assertSame(['bar', 'piyo'], $redis->hmget('metavars', 'foo', 'hoge')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetsValueOfSpecifiedFieldUsingCluster(): void + { + $redis = $this->getClient(); + + $redis->del('metavars'); + + $this->testSetsValueOfSpecifiedField(); + } + /** * @group connected * @requiresRedisVersion >= 2.0.0 @@ -80,11 +94,21 @@ class HSET_Test extends PredisCommandTestCase public function testThrowsExceptionOnWrongType(): void { $this->expectException('Predis\Response\ServerException'); - $this->expectExceptionMessage('Operation against a key holding the wrong kind of value'); + $this->expectExceptionMessageMatches('/.*Operation against a key holding the wrong kind of value.*/'); $redis = $this->getClient(); $redis->set('metavars', 'foo'); $redis->hset('metavars', 'foo', 'bar'); } + + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testThrowsExceptionOnWrongTypeUsingCluster(): void + { + $this->testThrowsExceptionOnWrongType(); + } } diff --git a/tests/Predis/Command/Redis/SET_Test.php b/tests/Predis/Command/Redis/SET_Test.php index 107ac9ad..71f4aaf2 100644 --- a/tests/Predis/Command/Redis/SET_Test.php +++ b/tests/Predis/Command/Redis/SET_Test.php @@ -82,6 +82,16 @@ class SET_Test extends PredisCommandTestCase $this->assertSame('bar', $redis->get('foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetStringValueUsingCluster(): void + { + $this->testSetStringValue(); + } + /** * @group connected * @requiresRedisVersion >= 2.6.12 @@ -94,6 +104,16 @@ class SET_Test extends PredisCommandTestCase $this->assertSame(1, $redis->ttl('foo')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetStringValueWithModifierEXUsingCluster(): void + { + $this->testSetStringValueWithModifierEX(); + } + /** * @group connected * @requiresRedisVersion >= 2.6.12 @@ -109,6 +129,16 @@ class SET_Test extends PredisCommandTestCase $this->assertLessThanOrEqual(1000, $pttl); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetStringValueWithModifierPXUsingCluster(): void + { + $this->testSetStringValueWithModifierPX(); + } + /** * @group connected * @requiresRedisVersion >= 2.6.12 @@ -121,6 +151,16 @@ class SET_Test extends PredisCommandTestCase $this->assertNull($redis->set('foo', 'bar', 'NX')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetStringValueWithModifierNXUsingCluster(): void + { + $this->testSetStringValueWithModifierNX(); + } + /** * @group connected * @requiresRedisVersion >= 2.6.12 @@ -135,11 +175,20 @@ class SET_Test extends PredisCommandTestCase $this->assertNull($redis->set('foofoo', 'barbar', 'XX')); } + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 6.0.0 + */ + public function testSetStringValueWithModifierXXUsingCluster(): void + { + $this->testSetStringValueWithModifierXX(); + } + /** * @group connected * @group cluster * @requiresRedisVersion >= 3.0.0 - * @return void */ public function testSetStringValueInClusterMode(): void { diff --git a/tests/Predis/Connection/Cluster/RedisClusterTest.php b/tests/Predis/Connection/Cluster/RedisClusterTest.php index cf021b2c..7388bed3 100644 --- a/tests/Predis/Connection/Cluster/RedisClusterTest.php +++ b/tests/Predis/Connection/Cluster/RedisClusterTest.php @@ -12,6 +12,7 @@ namespace Predis\Connection\Cluster; +use Iterator; use PHPUnit\Framework\MockObject\MockObject; use Predis\Cluster; use Predis\Command; @@ -1212,11 +1213,12 @@ class RedisClusterTest extends PredisTestCase /** * @group disconnected + * @dataProvider onMovedResponsesDataProvider */ - public function testAskSlotMapToRedisClusterOnMovedResponseByDefault(): void + public function testAskSlotMapToRedisClusterOnMovedResponseByDefault(string $movedErrorMessage): void { $cmdGET = Command\RawCommand::create('GET', 'node:1001'); - $rspMOVED = new Response\Error('MOVED 1970 127.0.0.1:6380'); + $rspMOVED = new Response\Error($movedErrorMessage); $rspSlotsArray = [ [0, 8191, ['127.0.0.1', 6379]], [8192, 16383, ['127.0.0.1', 6380]], @@ -1261,6 +1263,20 @@ class RedisClusterTest extends PredisTestCase $this->assertCount(2, $cluster); } + /** + * @return Iterator + */ + public function onMovedResponsesDataProvider(): Iterator + { + yield 'MOVED 1970 127.0.0.1:6380' => [ + 'movedErrorMessage' => 'MOVED 1970 127.0.0.1:6380', + ]; + + yield 'MOVED 1970 127.0.0.1:6380 (relay exception details)' => [ + 'movedErrorMessage' => 'MOVED 1970 127.0.0.1:6380 (relay exception details)', + ]; + } + /** * @group disconnected */ diff --git a/tests/Predis/Connection/RelayConnectionTest.php b/tests/Predis/Connection/RelayConnectionTest.php index 11bc72e3..895fb78e 100644 --- a/tests/Predis/Connection/RelayConnectionTest.php +++ b/tests/Predis/Connection/RelayConnectionTest.php @@ -13,8 +13,13 @@ namespace Predis\Connection; use PHPUnit\Framework\MockObject\MockObject; +use Predis\ClientException; use Predis\Command\RawCommand; use Predis\Response\Error as ErrorResponse; +use Predis\Response\ErrorInterface as ErrorResponseInterface; +use ReflectionClass; +use Relay\Exception as RelayException; +use Relay\Relay; /** * @group ext-relay @@ -59,6 +64,141 @@ class RelayConnectionTest extends PredisConnectionTestCase $connection->connect(); } + /** + * @group connected + */ + public function testGetIdentifierUsesParentGetIdentifier(): void + { + $relayMock = $this + ->getMockBuilder(Relay::class) + ->onlyMethods(['endpointId']) + ->getMock(); + + $relayMock->method('endpointId') + ->willThrowException( + new RelayException('Not Connected') + ); + + /** @var RelayConnection&MockObject $connection */ + $connection = $this + ->getMockBuilder($this->getConnectionClass()) + ->onlyMethods(['createResource']) + ->disableOriginalConstructor() + ->getMock(); + + $reflection = new ReflectionClass($connection); + $propertyClient = $reflection->getProperty('client'); + $propertyClient->setAccessible(true); + $propertyClient->setValue($connection, $relayMock); + $propertyParameters = $reflection->getProperty('parameters'); + $propertyParameters->setAccessible(true); + $propertyParameters->setValue($connection, new Parameters([ + 'host' => '127.0.0.1', + 'port' => 6379, + ])); + + $this->assertEquals('127.0.0.1:6379', $connection->getIdentifier()); + } + + /** + * @group connected + */ + public function testGetIdentifierUsesClientEndpointId(): void + { + $relayMock = $this + ->getMockBuilder(Relay::class) + ->onlyMethods(['endpointId']) + ->getMock(); + + $relayMock->method('endpointId') + ->willReturn('127.0.0.1:6379'); + + /** @var RelayConnection&MockObject $connection */ + $connection = $this + ->getMockBuilder($this->getConnectionClass()) + ->onlyMethods(['createResource']) + ->disableOriginalConstructor() + ->getMock(); + + $reflection = new ReflectionClass($connection); + $propertyClient = $reflection->getProperty('client'); + $propertyClient->setAccessible(true); + $propertyClient->setValue($connection, $relayMock); + + $this->assertEquals('127.0.0.1:6379', $connection->getIdentifier()); + } + + /** + * @group connected + */ + public function testExecuteCommandReturnsErrorResponseWhenItIsThrownByRelay(): void + { + $cmdSelect = RawCommand::create('GET', '1'); + + $relayMock = $this + ->getMockBuilder(Relay::class) + ->onlyMethods(['rawCommand']) + ->getMock(); + + $relayMock->method('rawCommand') + ->willThrowException( + new RelayException('RELAY_ERR_REDIS') + ); + + /** @var RelayConnection&MockObject $connection */ + $connection = $this + ->getMockBuilder($this->getConnectionClass()) + ->onlyMethods(['createResource', 'createClient']) + ->disableOriginalConstructor() + ->getMock(); + + $reflection = new ReflectionClass($connection); + $property = $reflection->getProperty('client'); + $property->setAccessible(true); + $property->setValue($connection, $relayMock); + + $connection->method('createResource'); + + $response = $connection->executeCommand($cmdSelect); + + $this->assertInstanceOf(ErrorResponseInterface::class, $response); + } + + /** + * @group connected + */ + public function testExecuteCommandThrowsExceptionWhenThrownByRelayAndItIsNotErrorResponse(): void + { + $this->expectException('Predis\ClientException'); + $cmdSelect = RawCommand::create('GET', '1'); + + $relayMock = $this + ->getMockBuilder(Relay::class) + ->onlyMethods(['rawCommand']) + ->getMock(); + + $relayMock->method('rawCommand') + ->willThrowException( + new ClientException('RELAY_ERR_REDIS') + ); + + /** @var RelayConnection&MockObject $connection */ + $connection = $this + ->getMockBuilder($this->getConnectionClass()) + ->onlyMethods(['createResource', 'createClient']) + ->disableOriginalConstructor() + ->getMock(); + + $reflection = new ReflectionClass($connection); + $property = $reflection->getProperty('client'); + $property->setAccessible(true); + $property->setValue($connection, $relayMock); + + $connection->method('createResource'); + + $connection->executeCommand($cmdSelect); + } + // ******************************************************************** // // ---- INTEGRATION TESTS --------------------------------------------- // // ******************************************************************** //