PredisConnectionTestCase.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592
  1. <?php
  2. /*
  3. * This file is part of the Predis package.
  4. *
  5. * (c) Daniele Alessandri <suppakilla@gmail.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Predis\Connection;
  11. use PredisTestCase;
  12. /**
  13. * @group realm-connection
  14. */
  15. abstract class PredisConnectionTestCase extends PredisTestCase
  16. {
  17. /**
  18. * @group disconnected
  19. */
  20. public function testConstructorDoesNotOpenConnection()
  21. {
  22. $connection = $this->createConnection();
  23. $this->assertFalse($connection->isConnected());
  24. }
  25. /**
  26. * @group disconnected
  27. */
  28. public function testSupportsSchemeTCP()
  29. {
  30. $connection = $this->createConnectionWithParams(array('scheme' => 'tcp'));
  31. $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
  32. }
  33. /**
  34. * @group disconnected
  35. */
  36. public function testSupportsSchemeRedis()
  37. {
  38. $connection = $this->createConnectionWithParams(array('scheme' => 'redis'));
  39. $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
  40. }
  41. /**
  42. * @group disconnected
  43. */
  44. public function testSupportsSchemeUnix()
  45. {
  46. $connection = $this->createConnectionWithParams(array('scheme' => 'unix'));
  47. $this->assertInstanceOf('Predis\Connection\NodeConnectionInterface', $connection);
  48. }
  49. /**
  50. * @group disconnected
  51. * @expectedException \InvalidArgumentException
  52. * @expectedExceptionMessage Invalid scheme: 'udp'.
  53. */
  54. public function testThrowsExceptionOnInvalidScheme()
  55. {
  56. $this->createConnectionWithParams(array('scheme' => 'udp'));
  57. }
  58. /**
  59. * @group disconnected
  60. */
  61. public function testExposesParameters()
  62. {
  63. $parameters = $this->getParameters();
  64. $connection = $this->createConnectionWithParams($parameters);
  65. $this->assertSame($parameters, $connection->getParameters());
  66. }
  67. /**
  68. * @group disconnected
  69. */
  70. public function testCanBeSerialized()
  71. {
  72. $parameters = $this->getParameters(array(
  73. 'alias' => 'redis',
  74. 'read_write_timeout' => 10,
  75. ));
  76. $connection = $this->createConnectionWithParams($parameters);
  77. $unserialized = unserialize(serialize($connection));
  78. $this->assertInstanceOf(static::CONNECTION_CLASS, $unserialized);
  79. $this->assertEquals($parameters, $unserialized->getParameters());
  80. }
  81. /**
  82. * @group disconnected
  83. * @group slow
  84. * @expectedException \Predis\Connection\ConnectionException
  85. */
  86. public function testThrowExceptionWhenUnableToConnect()
  87. {
  88. $parameters = array('host' => '169.254.10.10', 'timeout' => 0.5);
  89. $connection = $this->createConnectionWithParams($parameters, false);
  90. $connection->executeCommand($this->getCurrentProfile()->createCommand('ping'));
  91. }
  92. // ******************************************************************** //
  93. // ---- INTEGRATION TESTS --------------------------------------------- //
  94. // ******************************************************************** //
  95. /**
  96. * @group connected
  97. * @requires PHP 5.4
  98. */
  99. public function testAcceptsTcpNodelayParameter()
  100. {
  101. $connection = $this->createConnectionWithParams(array('tcp_nodelay' => false));
  102. $connection->connect();
  103. $this->assertTrue($connection->isConnected());
  104. $connection = $this->createConnectionWithParams(array('tcp_nodelay' => true));
  105. $connection->connect();
  106. $this->assertTrue($connection->isConnected());
  107. }
  108. /**
  109. * @group connected
  110. */
  111. public function testConnectForcesConnection()
  112. {
  113. $connection = $this->createConnection();
  114. $this->assertFalse($connection->isConnected());
  115. $connection->connect();
  116. $this->assertTrue($connection->isConnected());
  117. }
  118. /**
  119. * @group connected
  120. */
  121. public function testDoesNotThrowExceptionOnConnectWhenAlreadyConnected()
  122. {
  123. $connection = $this->createConnection();
  124. $connection->connect();
  125. $this->assertTrue($connection->isConnected());
  126. $connection->connect();
  127. $this->assertTrue($connection->isConnected());
  128. }
  129. /**
  130. * @group connected
  131. */
  132. public function testDisconnectForcesDisconnection()
  133. {
  134. $connection = $this->createConnection();
  135. $connection->connect();
  136. $this->assertTrue($connection->isConnected());
  137. $connection->disconnect();
  138. $this->assertFalse($connection->isConnected());
  139. }
  140. /**
  141. * @group disconnected
  142. */
  143. public function testDoesNotThrowExceptionOnDisconnectWhenAlreadyDisconnected()
  144. {
  145. $connection = $this->createConnection();
  146. $this->assertFalse($connection->isConnected());
  147. $connection->disconnect();
  148. $this->assertFalse($connection->isConnected());
  149. }
  150. /**
  151. * @group connected
  152. */
  153. public function testGetResourceForcesConnection()
  154. {
  155. $connection = $this->createConnection();
  156. $this->assertFalse($connection->isConnected());
  157. $this->assertInternalType('resource', $connection->getResource());
  158. $this->assertTrue($connection->isConnected());
  159. }
  160. /**
  161. * @group connected
  162. */
  163. public function testSendingCommandForcesConnection()
  164. {
  165. $connection = $this->createConnection();
  166. $profile = $this->getCurrentProfile();
  167. $cmdPing = $profile->createCommand('ping');
  168. $this->assertEquals('PONG', $connection->executeCommand($cmdPing));
  169. $this->assertTrue($connection->isConnected());
  170. }
  171. /**
  172. * @group connected
  173. */
  174. public function testExecutesCommandOnServer()
  175. {
  176. $profile = $this->getCurrentProfile();
  177. $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
  178. $cmdPing->expects($this->never())
  179. ->method('parseResponse');
  180. $connection = $this->createConnection();
  181. $this->assertEquals('PONG', $connection->executeCommand($cmdPing));
  182. }
  183. /**
  184. * @group disconnected
  185. */
  186. public function testExecutesCommandWithHolesInArguments()
  187. {
  188. $profile = $this->getCurrentProfile();
  189. $cmdDel = $profile->createCommand('mget', array(0 => 'key:0', 2 => 'key:2'));
  190. $connection = $this->createConnection();
  191. $this->assertSame(array(null, null), $connection->executeCommand($cmdDel));
  192. }
  193. /**
  194. * @group connected
  195. */
  196. public function testExecutesMultipleCommandsOnServer()
  197. {
  198. $profile = $this->getCurrentProfile();
  199. $cmdPing = $profile->createCommand('ping');
  200. $cmdEcho = $profile->createCommand('echo', array('echoed'));
  201. $cmdGet = $profile->createCommand('get', array('foobar'));
  202. $cmdRpush = $profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol'));
  203. $cmdLrange = $profile->createCommand('lrange', array('metavars', 0, -1));
  204. $connection = $this->createConnection(true);
  205. $this->assertEquals('PONG', $connection->executeCommand($cmdPing));
  206. $this->assertSame('echoed', $connection->executeCommand($cmdEcho));
  207. $this->assertNull($connection->executeCommand($cmdGet));
  208. $this->assertSame(3, $connection->executeCommand($cmdRpush));
  209. $this->assertSame(array('foo', 'hoge', 'lol'), $connection->executeCommand($cmdLrange));
  210. }
  211. /**
  212. * @group connected
  213. */
  214. public function testWritesCommandToServer()
  215. {
  216. $profile = $this->getCurrentProfile();
  217. $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('parseResponse'));
  218. $cmdEcho->setArguments(array('ECHOED'));
  219. $cmdEcho->expects($this->never())
  220. ->method('parseResponse');
  221. $connection = $this->createConnection();
  222. $connection->writeRequest($cmdEcho);
  223. $connection->disconnect();
  224. }
  225. /**
  226. * @group connected
  227. */
  228. public function testReadsCommandFromServer()
  229. {
  230. $profile = $this->getCurrentProfile();
  231. $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('parseResponse'));
  232. $cmdEcho->setArguments(array('ECHOED'));
  233. $cmdEcho->expects($this->never())
  234. ->method('parseResponse');
  235. $connection = $this->createConnection();
  236. $connection->writeRequest($cmdEcho);
  237. $this->assertSame('ECHOED', $connection->readResponse($cmdEcho));
  238. }
  239. /**
  240. * @group connected
  241. */
  242. public function testIsAbleToWriteMultipleCommandsAndReadThemBackForPipelining()
  243. {
  244. $profile = $this->getCurrentProfile();
  245. $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('parseResponse'));
  246. $cmdPing->expects($this->never())
  247. ->method('parseResponse');
  248. $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('parseResponse'));
  249. $cmdEcho->setArguments(array('ECHOED'));
  250. $cmdEcho->expects($this->never())
  251. ->method('parseResponse');
  252. $connection = $this->createConnection();
  253. $connection->writeRequest($cmdPing);
  254. $connection->writeRequest($cmdEcho);
  255. $this->assertEquals('PONG', $connection->readResponse($cmdPing));
  256. $this->assertSame('ECHOED', $connection->readResponse($cmdEcho));
  257. }
  258. /**
  259. * @group connected
  260. */
  261. public function testSendsInitializationCommandsOnConnection()
  262. {
  263. $profile = $this->getCurrentProfile();
  264. $cmdPing = $this->getMock($profile->getCommandClass('ping'), array('getArguments'));
  265. $cmdPing->expects($this->once())
  266. ->method('getArguments')
  267. ->will($this->returnValue(array()));
  268. $cmdEcho = $this->getMock($profile->getCommandClass('echo'), array('getArguments'));
  269. $cmdEcho->expects($this->once())
  270. ->method('getArguments')
  271. ->will($this->returnValue(array('ECHOED')));
  272. $connection = $this->createConnection();
  273. $connection->addConnectCommand($cmdPing);
  274. $connection->addConnectCommand($cmdEcho);
  275. $connection->connect();
  276. }
  277. /**
  278. * @group connected
  279. */
  280. public function testReadsStatusResponses()
  281. {
  282. $profile = $this->getCurrentProfile();
  283. $connection = $this->createConnection(true);
  284. $connection->writeRequest($profile->createCommand('set', array('foo', 'bar')));
  285. $this->assertInstanceOf('Predis\Response\Status', $connection->read());
  286. $connection->writeRequest($profile->createCommand('ping'));
  287. $this->assertInstanceOf('Predis\Response\Status', $connection->read());
  288. $connection->writeRequest($profile->createCommand('multi'));
  289. $connection->writeRequest($profile->createCommand('ping'));
  290. $this->assertInstanceOf('Predis\Response\Status', $connection->read());
  291. $this->assertInstanceOf('Predis\Response\Status', $connection->read());
  292. }
  293. /**
  294. * @group connected
  295. */
  296. public function testReadsBulkResponses()
  297. {
  298. $profile = $this->getCurrentProfile();
  299. $connection = $this->createConnection(true);
  300. $connection->executeCommand($profile->createCommand('set', array('foo', 'bar')));
  301. $connection->writeRequest($profile->createCommand('get', array('foo')));
  302. $this->assertSame('bar', $connection->read());
  303. $connection->writeRequest($profile->createCommand('get', array('hoge')));
  304. $this->assertNull($connection->read());
  305. }
  306. /**
  307. * @group connected
  308. */
  309. public function testReadsIntegerResponses()
  310. {
  311. $profile = $this->getCurrentProfile();
  312. $connection = $this->createConnection(true);
  313. $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
  314. $connection->writeRequest($profile->createCommand('llen', array('metavars')));
  315. $this->assertSame(3, $connection->read());
  316. }
  317. /**
  318. * @group connected
  319. */
  320. public function testReadsErrorResponsesAsResponseErrorObjects()
  321. {
  322. $profile = $this->getCurrentProfile();
  323. $connection = $this->createConnection(true);
  324. $connection->executeCommand($profile->createCommand('set', array('foo', 'bar')));
  325. $connection->writeRequest($profile->createCommand('rpush', array('foo', 'baz')));
  326. $this->assertInstanceOf('Predis\Response\Error', $error = $connection->read());
  327. $this->assertRegExp('/[ERR|WRONGTYPE] Operation against a key holding the wrong kind of value/', $error->getMessage());
  328. }
  329. /**
  330. * @group connected
  331. */
  332. public function testReadsMultibulkResponsesAsArrays()
  333. {
  334. $profile = $this->getCurrentProfile();
  335. $connection = $this->createConnection(true);
  336. $connection->executeCommand($profile->createCommand('rpush', array('metavars', 'foo', 'hoge', 'lol')));
  337. $connection->writeRequest($profile->createCommand('lrange', array('metavars', 0, -1)));
  338. $this->assertSame(array('foo', 'hoge', 'lol'), $connection->read());
  339. }
  340. /**
  341. * @group connected
  342. * @group slow
  343. * @expectedException \Predis\Connection\ConnectionException
  344. * @expectedExceptionMessageRegExp /.* \[tcp:\/\/169.254.10.10:6379\]/
  345. */
  346. public function testThrowsExceptionOnConnectionTimeout()
  347. {
  348. $connection = $this->createConnectionWithParams(array(
  349. 'host' => '169.254.10.10',
  350. 'timeout' => 0.1,
  351. ), false);
  352. $connection->connect();
  353. }
  354. /**
  355. * @group connected
  356. * @group slow
  357. * @expectedException \Predis\Connection\ConnectionException
  358. * @expectedExceptionMessageRegExp /.* \[tcp:\/\/\[0:0:0:0:0:ffff:a9fe:a0a\]:6379\]/
  359. */
  360. public function testThrowsExceptionOnConnectionTimeoutIPv6()
  361. {
  362. $connection = $this->createConnectionWithParams(array(
  363. 'host' => '0:0:0:0:0:ffff:a9fe:a0a',
  364. 'timeout' => 0.1,
  365. ), false);
  366. $connection->connect();
  367. }
  368. /**
  369. * @group connected
  370. * @group slow
  371. * @expectedException \Predis\Connection\ConnectionException
  372. * @expectedExceptionMessageRegExp /.* \[unix:\/tmp\/nonexistent\/redis\.sock]/
  373. */
  374. public function testThrowsExceptionOnUnixDomainSocketNotFound()
  375. {
  376. $connection = $this->createConnectionWithParams(array(
  377. 'scheme' => 'unix',
  378. 'path' => '/tmp/nonexistent/redis.sock',
  379. ), false);
  380. $connection->connect();
  381. }
  382. /**
  383. * @group connected
  384. * @group slow
  385. * @expectedException \Predis\Connection\ConnectionException
  386. */
  387. public function testThrowsExceptionOnReadWriteTimeout()
  388. {
  389. $profile = $this->getCurrentProfile();
  390. $connection = $this->createConnectionWithParams(array(
  391. 'read_write_timeout' => 0.5,
  392. ), true);
  393. $connection->executeCommand($profile->createCommand('brpop', array('foo', 3)));
  394. }
  395. /**
  396. * @medium
  397. * @group connected
  398. * @expectedException \Predis\Protocol\ProtocolException
  399. */
  400. public function testThrowsExceptionOnProtocolDesynchronizationErrors()
  401. {
  402. $connection = $this->createConnection();
  403. $stream = $connection->getResource();
  404. $connection->writeRequest($this->getCurrentProfile()->createCommand('ping'));
  405. fread($stream, 1);
  406. $connection->read();
  407. }
  408. // ******************************************************************** //
  409. // ---- HELPER METHODS ------------------------------------------------ //
  410. // ******************************************************************** //
  411. /**
  412. * Returns a named array with the default connection parameters and their values.
  413. *
  414. * @return array Default connection parameters.
  415. */
  416. protected function getDefaultParametersArray()
  417. {
  418. return array(
  419. 'scheme' => 'tcp',
  420. 'host' => REDIS_SERVER_HOST,
  421. 'port' => REDIS_SERVER_PORT,
  422. 'database' => REDIS_SERVER_DBNUM,
  423. 'read_write_timeout' => 2,
  424. );
  425. }
  426. /**
  427. * Asserts that the connection is using a persistent resource stream.
  428. *
  429. * This assertion will trigger a connect() operation if the connection has
  430. * not been open yet.
  431. *
  432. * @param NodeConnectionInterface $connection Connection instance.
  433. */
  434. protected function assertPersistentConnection(NodeConnectionInterface $connection)
  435. {
  436. $this->assertSame('persistent stream', get_resource_type($connection->getResource()));
  437. }
  438. /**
  439. * Asserts that the connection is not using a persistent resource stream.
  440. *
  441. * This assertion will trigger a connect() operation if the connection has
  442. * not been open yet.
  443. *
  444. * @param NodeConnectionInterface $connection Connection instance.
  445. */
  446. protected function assertNonPersistentConnection(NodeConnectionInterface $connection)
  447. {
  448. $this->assertSame('stream', get_resource_type($connection->getResource()));
  449. }
  450. /**
  451. * Creates a new connection instance.
  452. *
  453. * @param bool $initialize Push default initialization commands (SELECT and FLUSHDB).
  454. *
  455. * @return NodeConnectionInterface
  456. */
  457. protected function createConnection($initialize = false)
  458. {
  459. return $this->createConnectionWithParams(array(), $initialize);
  460. }
  461. /**
  462. * Creates a new connection instance using additional connection parameters.
  463. *
  464. * @param mixed $parameters Additional connection parameters.
  465. * @param bool $initialize Push default initialization commands (SELECT and FLUSHDB).
  466. *
  467. * @return NodeConnectionInterface
  468. */
  469. protected function createConnectionWithParams($parameters, $initialize = false)
  470. {
  471. $class = static::CONNECTION_CLASS;
  472. $profile = $this->getCurrentProfile();
  473. if (!$parameters instanceof ParametersInterface) {
  474. $parameters = $this->getParameters($parameters);
  475. }
  476. $connection = new $class($parameters);
  477. if ($initialize) {
  478. $connection->addConnectCommand(
  479. $profile->createCommand('select', array($parameters->database))
  480. );
  481. $connection->addConnectCommand(
  482. $profile->createCommand('flushdb')
  483. );
  484. }
  485. return $connection;
  486. }
  487. }