Skip to content

Commit 81e95f9

Browse files
committed
phpredis integration tests
1 parent ef688e1 commit 81e95f9

File tree

4 files changed

+200
-16
lines changed

4 files changed

+200
-16
lines changed

src/Connections/PhpRedisConnection.php

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Monospice\LaravelRedisSentinel\Connections;
44

5+
use Monospice\LaravelRedisSentinel\Exceptions\RetryRedisException;
56
use Closure;
67
use Illuminate\Redis\Connections\PhpRedisConnection as LaravelPhpRedisConnection;
78
use Redis;
@@ -203,16 +204,16 @@ protected function retryOnFailure(callable $callback)
203204
usleep($this->retryWait * 1000);
204205

205206
try {
206-
$this->client = $this->connector();
207+
$this->client = $this->connector ? call_user_func($this->connector) : $this->client;
207208
} catch (RedisException $e) {
208209
// Ignore the the creation of a new client gets an exception.
209210
// If this exception isn't caught the retry will stop.
210211
}
211212

212213
$attempts++;
213214
}
214-
} while ($attempts <= $this->retryLimit);
215+
} while ($attempts < $this->retryLimit);
215216

216-
throw $exception;
217+
throw new RetryRedisException(sprintf('Reached the reconnect limit of %d attempts', $attempts));
217218
}
218219
}

src/Connectors/PhpRedisConnector.php

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,8 @@ class PhpRedisConnector extends LaravelPhpRedisConnector
2929
/**
3030
* Configuration options specific to Sentinel connection operation
3131
*
32-
* @TODO rewrite doc.
33-
* We cannot pass these options as an array to the PhpRedis client.
34-
* Instead, we'll set them on the connection directly using methods
35-
* provided by the SentinelReplication class of the PhpRedis package.
32+
* Some of the Sentinel configuration options can be entered in this class.
33+
* The retry_wait and retry_limit values are passed to the connection.
3634
*
3735
* @var array
3836
*/
@@ -60,7 +58,9 @@ class PhpRedisConnector extends LaravelPhpRedisConnector
6058
public function connect(array $servers, array $options = [ ])
6159
{
6260
// Set the initial Sentinel servers.
63-
$this->servers = $servers;
61+
$this->servers = array_map(function ($server) {
62+
return $this->formatServer($server);
63+
}, $servers);
6464

6565
// Merge the global options shared by all Sentinel connections with
6666
// connection-specific options
@@ -92,6 +92,10 @@ public function connect(array $servers, array $options = [ ])
9292
protected function createClientWithSentinel(array $options)
9393
{
9494
$servers = $this->servers;
95+
$timeout = isset($options['sentinel_timeout']) ? $options['sentinel_timeout'] : 0;
96+
$persistent = isset($options['sentinel_peristent']) ? $options['sentinel_peristent'] : null;
97+
$retryWait = isset($options['retry_wait']) ? $options['retry_wait'] : 0;
98+
$readTimeout = isset($options['sentinel_read_timeout']) ? $options['sentinel_read_timeout'] : 0;
9599

96100
// Shuffle the servers to perform some loadbalancing.
97101
shuffle($servers);
@@ -103,14 +107,7 @@ protected function createClientWithSentinel(array $options)
103107
$service = isset($options['service']) ? $options['service'] : 'mymaster';
104108

105109
// Create a connection to the Sentinel instance.
106-
$sentinel = new RedisSentinel(
107-
$host,
108-
$port,
109-
isset($options['sentinel_timeout']) ? $options['sentinel_timeout'] : 0,
110-
isset($options['sentinel_persistent']) ? $options['sentinel_persistent'] : null,
111-
isset($options['retry_wait']) ? $options['retry_wait'] : 0,
112-
isset($options['sentinel_read_timeout']) ? $options['sentinel_read_timeout'] : 0,
113-
);
110+
$sentinel = new RedisSentinel($host, $port, $timeout, $persistent, $retryWait, $readTimeout);
114111

115112
try {
116113
// Check if the Sentinel server list needs to be updated.
@@ -149,4 +146,27 @@ protected function createClientWithSentinel(array $options)
149146

150147
throw new RedisException('Could not create a client for the configured Sentinel servers.');
151148
}
149+
150+
/**
151+
* Format a server.
152+
*
153+
* @param array $server
154+
* @return array
155+
*
156+
* @throws RedisException
157+
*/
158+
private function formatServer($server)
159+
{
160+
if (is_string($server)) {
161+
list($host, $port) = explode(':', $server);
162+
163+
return ['host' => $host, 'port' => $port];
164+
}
165+
166+
if (! is_array($server)) {
167+
throw new RedisException('Could not format the server definition.');
168+
}
169+
170+
return $server;
171+
}
152172
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Monospice\LaravelRedisSentinel\Exceptions;
4+
5+
use RedisException as PhpRedisException;
6+
7+
class RetryRedisException extends PhpRedisException
8+
{
9+
//
10+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
<?php
2+
3+
namespace Monospice\LaravelRedisSentinel\Tests\Integration\Connections;
4+
5+
use Mockery;
6+
use Monospice\LaravelRedisSentinel\Connections\PhpRedisConnection;
7+
use Monospice\LaravelRedisSentinel\Connectors\PhpRedisConnector;
8+
use Monospice\LaravelRedisSentinel\Tests\Support\DummyException;
9+
use Monospice\LaravelRedisSentinel\Tests\Support\IntegrationTestCase;
10+
use Monospice\LaravelRedisSentinel\Exceptions\RetryRedisException;
11+
use Redis;
12+
use RedisException;
13+
14+
class PhpRedisConnectionTest extends IntegrationTestCase
15+
{
16+
/**
17+
* The instance of the PhpRedis client wrapper under test.
18+
*
19+
* @var PhpRedisConnection
20+
*/
21+
protected $subject;
22+
23+
/**
24+
* Run this setup before each test
25+
*
26+
* @return void
27+
*/
28+
public function setUp()
29+
{
30+
parent::setUp();
31+
32+
$this->subject = $this->makeClient();
33+
}
34+
35+
/**
36+
* Run this cleanup after each test.
37+
*
38+
* @return void
39+
*/
40+
public function tearDown()
41+
{
42+
parent::tearDown();
43+
44+
Mockery::close();
45+
}
46+
47+
public function testAllowsTransactionsOnAggregateConnection()
48+
{
49+
$transaction = $this->subject->transaction();
50+
51+
$this->assertInstanceOf(Redis::class, $transaction);
52+
}
53+
54+
public function testExecutesCommandsInTransaction()
55+
{
56+
$result = $this->subject->transaction(function ($trans) {
57+
$trans->set('test-key', 'test value');
58+
$trans->get('test-key');
59+
});
60+
61+
$this->assertCount(2, $result);
62+
$this->assertTrue($result[0]);
63+
$this->assertEquals('test value', $result[1]);
64+
$this->assertRedisKeyEquals('test-key', 'test value');
65+
}
66+
67+
public function testExecutesTransactionsOnMaster()
68+
{
69+
$expectedSubset = ['role' => 'master'];
70+
71+
$info = $this->subject->transaction(function ($transaction) {
72+
$transaction->info();
73+
});
74+
75+
$this->assertArraySubset($expectedSubset, $info[0]);
76+
}
77+
78+
public function testAbortsTransactionOnException()
79+
{
80+
$exception = null;
81+
82+
try {
83+
$this->subject->transaction(function ($trans) {
84+
$trans->set('test-key', 'test value');
85+
throw new DummyException();
86+
});
87+
} catch (DummyException $exception) {
88+
// With PHPUnit, we need to wrap the throwing block to perform
89+
// assertions afterward.
90+
}
91+
92+
$this->assertNotNull($exception);
93+
$this->assertRedisKeyEquals('test-key', null);
94+
}
95+
96+
public function testRetriesTransactionWhenConnectionFails()
97+
{
98+
$this->expectException(RetryRedisException::class);
99+
100+
$expectedRetries = 2;
101+
102+
$this->subject = $this->makeClient($expectedRetries, 0); // retry immediately
103+
104+
$this->subject->transaction(function () {
105+
throw new RedisException();
106+
});
107+
}
108+
109+
public function testCanReconnectWhenConnectionFails()
110+
{
111+
$retries = 3;
112+
$attempts = 0;
113+
114+
$this->subject = $this->makeClient($retries, 0); // retry immediately
115+
116+
$this->subject->transaction(function ($trans) use (&$attempts, $retries) {
117+
$attempts++;
118+
119+
if ($attempts < $retries) {
120+
throw new RedisException();
121+
} else {
122+
$trans->set('test-key', 'test value');
123+
}
124+
});
125+
126+
$this->assertGreaterThan(1, $attempts, 'First try does not count.');
127+
$this->assertRedisKeyEquals('test-key', 'test value');
128+
}
129+
130+
/**
131+
* Initialize a PhpRedis client using the test connection configuration
132+
* that can verify connectivity failure handling.
133+
*
134+
* @param int|null $retryLimit
135+
* @param int|null $retryWait
136+
* @return Redis A client instance for the subject under test.
137+
*/
138+
protected function makeClient(int $retryLimit = null, int $retryWait = null)
139+
{
140+
$connector = new PhpRedisConnector();
141+
142+
$options = $this->config['options'];
143+
if ($retryLimit !== null) {
144+
$options['retry_limit'] = $retryLimit;
145+
}
146+
147+
if ($retryWait !== null) {
148+
$options['retry_wait'] = $retryWait;
149+
}
150+
151+
return $connector->connect($this->config['default'], $options);
152+
}
153+
}

0 commit comments

Comments
 (0)