MultiExecContextTest.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  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\Transaction;
  11. use \PHPUnit_Framework_TestCase as StandardTestCase;
  12. use Predis\Client;
  13. use Predis\ResponseQueued;
  14. use Predis\ServerException;
  15. use Predis\Command\CommandInterface;
  16. /**
  17. * @group realm-transaction
  18. */
  19. class MultiExecContextTest extends StandardTestCase
  20. {
  21. /**
  22. * @group disconnected
  23. * @expectedException Predis\NotSupportedException
  24. * @expectedExceptionMessage The current profile does not support MULTI, EXEC and DISCARD
  25. */
  26. public function testThrowsExceptionOnUnsupportedMultiExecInProfile()
  27. {
  28. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  29. $client = new Client($connection, array('profile' => '1.2'));
  30. $tx = new MultiExecContext($client);
  31. }
  32. /**
  33. * @group disconnected
  34. * @expectedException Predis\NotSupportedException
  35. * @expectedExceptionMessage The current profile does not support WATCH and UNWATCH
  36. */
  37. public function testThrowsExceptionOnUnsupportedWatchUnwatchInProfile()
  38. {
  39. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  40. $client = new Client($connection, array('profile' => '2.0'));
  41. $tx = new MultiExecContext($client, array('options' => 'cas'));
  42. $tx->watch('foo');
  43. }
  44. /**
  45. * @group disconnected
  46. */
  47. public function testExecutionWithFluentInterface()
  48. {
  49. $commands = array();
  50. $expected = array('one', 'two', 'three');
  51. $callback = $this->getExecuteCallback($expected, $commands);
  52. $tx = $this->getMockedTransaction($callback);
  53. $this->assertSame($expected, $tx->echo('one')->echo('two')->echo('three')->execute());
  54. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  55. }
  56. /**
  57. * @group disconnected
  58. */
  59. public function testExecutionWithCallable()
  60. {
  61. $commands = array();
  62. $expected = array('one', 'two', 'three');
  63. $callback = $this->getExecuteCallback($expected, $commands);
  64. $tx = $this->getMockedTransaction($callback);
  65. $replies = $tx->execute(function ($tx) {
  66. $tx->echo('one');
  67. $tx->echo('two');
  68. $tx->echo('three');
  69. });
  70. $this->assertSame($expected, $replies);
  71. $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  72. }
  73. /**
  74. * @group disconnected
  75. */
  76. public function testCannotMixExecutionWithFluentInterfaceAndCallable()
  77. {
  78. $commands = array();
  79. $callback = $this->getExecuteCallback(null, $commands);
  80. $tx = $this->getMockedTransaction($callback);
  81. $exception = null;
  82. try {
  83. $tx->echo('foo')->execute(function ($tx) {
  84. $tx->echo('bar');
  85. });
  86. } catch (\Exception $ex) {
  87. $exception = $ex;
  88. }
  89. $this->assertInstanceOf('Predis\ClientException', $exception);
  90. $this->assertSame(array('MULTI', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
  91. }
  92. /**
  93. * @group disconnected
  94. */
  95. public function testEmptyTransactionDoesNotSendMultiExecCommands()
  96. {
  97. $commands = array();
  98. $callback = $this->getExecuteCallback(null, $commands);
  99. $tx = $this->getMockedTransaction($callback);
  100. $replies = $tx->execute(function ($tx) {
  101. // NOOP
  102. });
  103. $this->assertNull($replies);
  104. $this->assertSame(array(), self::commandsToIDs($commands));
  105. }
  106. /**
  107. * @group disconnected
  108. * @expectedException Predis\ClientException
  109. * @expectedExceptionMessage Cannot invoke 'execute' or 'exec' inside an active client transaction block
  110. */
  111. public function testThrowsExceptionOnExecInsideTransactionBlock()
  112. {
  113. $commands = array();
  114. $callback = $this->getExecuteCallback(null, $commands);
  115. $tx = $this->getMockedTransaction($callback);
  116. $replies = $tx->execute(function ($tx) {
  117. $tx->exec();
  118. });
  119. $this->assertNull($replies);
  120. $this->assertSame(array(), self::commandsToIDs($commands));
  121. }
  122. /**
  123. * @group disconnected
  124. */
  125. public function testEmptyTransactionIgnoresDiscard()
  126. {
  127. $commands = array();
  128. $callback = $this->getExecuteCallback(null, $commands);
  129. $tx = $this->getMockedTransaction($callback);
  130. $replies = $tx->execute(function ($tx) {
  131. $tx->discard();
  132. });
  133. $this->assertNull($replies);
  134. $this->assertSame(array(), self::commandsToIDs($commands));
  135. }
  136. /**
  137. * @group disconnected
  138. */
  139. public function testTransactionWithCommandsSendsDiscard()
  140. {
  141. $commands = array();
  142. $callback = $this->getExecuteCallback(null, $commands);
  143. $tx = $this->getMockedTransaction($callback);
  144. $replies = $tx->execute(function ($tx) {
  145. $tx->set('foo', 'bar');
  146. $tx->get('foo');
  147. $tx->discard();
  148. });
  149. $this->assertNull($replies);
  150. $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
  151. }
  152. /**
  153. * @group disconnected
  154. */
  155. public function testSendMultiOnCommandsFollowingDiscard()
  156. {
  157. $commands = array();
  158. $expected = array('after DISCARD');
  159. $callback = $this->getExecuteCallback($expected, $commands);
  160. $tx = $this->getMockedTransaction($callback);
  161. $replies = $tx->execute(function ($tx) {
  162. $tx->echo('before DISCARD');
  163. $tx->discard();
  164. $tx->echo('after DISCARD');
  165. });
  166. $this->assertSame($replies, $expected);
  167. $this->assertSame(array('MULTI', 'ECHO', 'DISCARD', 'MULTI', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
  168. }
  169. /**
  170. * @group disconnected
  171. * @expectedException Predis\ClientException
  172. */
  173. public function testThrowsExceptionOnWatchInsideMulti()
  174. {
  175. $callback = $this->getExecuteCallback();
  176. $tx = $this->getMockedTransaction($callback);
  177. $tx->echo('foobar')->watch('foo')->execute();
  178. }
  179. /**
  180. * @group disconnected
  181. */
  182. public function testUnwatchInsideMulti()
  183. {
  184. $commands = array();
  185. $expected = array('foobar', true);
  186. $callback = $this->getExecuteCallback($expected, $commands);
  187. $tx = $this->getMockedTransaction($callback);
  188. $replies = $tx->echo('foobar')->unwatch('foo')->execute();
  189. $this->assertSame($replies, $expected);
  190. $this->assertSame(array('MULTI', 'ECHO', 'UNWATCH', 'EXEC'), self::commandsToIDs($commands));
  191. }
  192. /**
  193. * @group disconnected
  194. */
  195. public function testAutomaticWatchInOptions()
  196. {
  197. $txCommands = $casCommands = array();
  198. $expected = array('bar', 'piyo');
  199. $options = array('watch' => array('foo', 'hoge'));
  200. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  201. $tx = $this->getMockedTransaction($callback, $options);
  202. $replies = $tx->execute(function ($tx) {
  203. $tx->get('foo');
  204. $tx->get('hoge');
  205. });
  206. $this->assertSame($replies, $expected);
  207. $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
  208. $this->assertSame(array('foo', 'hoge'), $casCommands[0]->getArguments());
  209. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  210. }
  211. /**
  212. * @group disconnected
  213. */
  214. public function testCheckAndSetWithFluentInterface()
  215. {
  216. $txCommands = $casCommands = array();
  217. $expected = array('bar', 'piyo');
  218. $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
  219. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  220. $tx = $this->getMockedTransaction($callback, $options);
  221. $tx->watch('foobar');
  222. $this->assertSame('DUMMY_REPLY', $tx->get('foo'));
  223. $this->assertSame('DUMMY_REPLY', $tx->get('hoge'));
  224. $replies = $tx->multi()
  225. ->get('foo')
  226. ->get('hoge')
  227. ->execute();
  228. $this->assertSame($replies, $expected);
  229. $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
  230. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  231. }
  232. /**
  233. * @group disconnected
  234. */
  235. public function testCheckAndSetWithBlock()
  236. {
  237. $txCommands = $casCommands = array();
  238. $expected = array('bar', 'piyo');
  239. $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
  240. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  241. $tx = $this->getMockedTransaction($callback, $options);
  242. $test = $this;
  243. $replies = $tx->execute(function ($tx) use ($test) {
  244. $tx->watch('foobar');
  245. $reply1 = $tx->get('foo');
  246. $reply2 = $tx->get('hoge');
  247. $test->assertSame('DUMMY_REPLY', $reply1);
  248. $test->assertSame('DUMMY_REPLY', $reply2);
  249. $tx->multi();
  250. $tx->get('foo');
  251. $tx->get('hoge');
  252. });
  253. $this->assertSame($replies, $expected);
  254. $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
  255. $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  256. }
  257. /**
  258. * @group disconnected
  259. */
  260. public function testCheckAndSetWithEmptyBlock()
  261. {
  262. $txCommands = $casCommands = array();
  263. $options = array('cas' => true);
  264. $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
  265. $tx = $this->getMockedTransaction($callback, $options);
  266. $tx->execute(function ($tx) {
  267. $tx->multi();
  268. });
  269. $this->assertSame(array(), self::commandsToIDs($casCommands));
  270. $this->assertSame(array(), self::commandsToIDs($txCommands));
  271. }
  272. /**
  273. * @group disconnected
  274. */
  275. public function testCheckAndSetWithoutExec()
  276. {
  277. $txCommands = $casCommands = array();
  278. $options = array('cas' => true);
  279. $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
  280. $tx = $this->getMockedTransaction($callback, $options);
  281. $tx->execute(function ($tx) {
  282. $bar = $tx->get('foo');
  283. $tx->set('hoge', 'piyo');
  284. });
  285. $this->assertSame(array('GET', 'SET'), self::commandsToIDs($casCommands));
  286. $this->assertSame(array(), self::commandsToIDs($txCommands));
  287. }
  288. /**
  289. * @group disconnected
  290. * @expectedException InvalidArgumentException
  291. * @expectedExceptionMessage Automatic retries can be used only when a transaction block is provided
  292. */
  293. public function testThrowsExceptionOnAutomaticRetriesWithFluentInterface()
  294. {
  295. $options = array('retry' => 1);
  296. $callback = $this->getExecuteCallback();
  297. $tx = $this->getMockedTransaction($callback, $options);
  298. $tx->echo('message')->execute();
  299. }
  300. /**
  301. * @group disconnected
  302. */
  303. public function testAutomaticRetryOnServerSideTransactionAbort()
  304. {
  305. $casCommands = $txCommands = array();
  306. $expected = array('bar');
  307. $options = array('watch' => array('foo', 'bar'), 'retry' => ($attempts = 2) + 1);
  308. $sentinel = $this->getMock('stdClass', array('signal'));
  309. $sentinel->expects($this->exactly($attempts))->method('signal');
  310. $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
  311. $tx = $this->getMockedTransaction($callback, $options);
  312. $replies = $tx->execute(function ($tx) use ($sentinel, &$attempts) {
  313. $tx->get('foo');
  314. if ($attempts > 0) {
  315. $attempts -= 1;
  316. $sentinel->signal();
  317. $tx->echo('!!ABORT!!');
  318. }
  319. });
  320. $this->assertSame($replies, $expected);
  321. $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
  322. $this->assertSame(array('foo', 'bar'), $casCommands[0]->getArguments());
  323. $this->assertSame(array('MULTI', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
  324. }
  325. /**
  326. * @group disconnected
  327. * @expectedException Predis\Transaction\AbortedMultiExecException
  328. */
  329. public function testThrowsExceptionOnServerSideTransactionAbort()
  330. {
  331. $callback = $this->getExecuteCallback();
  332. $tx = $this->getMockedTransaction($callback);
  333. $replies = $tx->execute(function ($tx) {
  334. $tx->echo('!!ABORT!!');
  335. });
  336. }
  337. /**
  338. * @group disconnected
  339. */
  340. public function testHandlesStandardExceptionsInBlock()
  341. {
  342. $commands = array();
  343. $expected = array('foobar', true);
  344. $callback = $this->getExecuteCallback($expected, $commands);
  345. $tx = $this->getMockedTransaction($callback);
  346. $replies = null;
  347. try {
  348. $replies = $tx->execute(function ($tx) {
  349. $tx->set('foo', 'bar');
  350. $tx->get('foo');
  351. throw new \RuntimeException('TEST');
  352. });
  353. } catch (\Exception $ex) {
  354. // NOOP
  355. }
  356. $this->assertNull($replies, $expected);
  357. $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
  358. }
  359. /**
  360. * @group disconnected
  361. */
  362. public function testHandlesServerExceptionsInBlock()
  363. {
  364. $commands = array();
  365. $expected = array('foobar', true);
  366. $callback = $this->getExecuteCallback($expected, $commands);
  367. $tx = $this->getMockedTransaction($callback);
  368. $replies = null;
  369. try {
  370. $replies = $tx->execute(function ($tx) {
  371. $tx->set('foo', 'bar');
  372. $tx->echo('ERR Invalid operation');
  373. $tx->get('foo');
  374. });
  375. } catch (ServerException $ex) {
  376. $tx->discard();
  377. }
  378. $this->assertNull($replies);
  379. $this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
  380. }
  381. // ******************************************************************** //
  382. // ---- INTEGRATION TESTS --------------------------------------------- //
  383. // ******************************************************************** //
  384. /**
  385. * @group connected
  386. */
  387. public function testIntegrationHandlesStandardExceptionsInBlock()
  388. {
  389. $client = $this->getClient();
  390. $exception = null;
  391. try {
  392. $client->multiExec(function ($tx) {
  393. $tx->set('foo', 'bar');
  394. throw new \RuntimeException("TEST");
  395. });
  396. } catch (\Exception $ex) {
  397. $exception = $ex;
  398. }
  399. $this->assertInstanceOf('RuntimeException', $exception);
  400. $this->assertFalse($client->exists('foo'));
  401. }
  402. /**
  403. * @group connected
  404. */
  405. public function testIntegrationThrowsExceptionOnRedisErrorInBlock()
  406. {
  407. $client = $this->getClient();
  408. $exception = null;
  409. $value = (string) rand();
  410. try {
  411. $client->multiExec(function ($tx) use ($value) {
  412. $tx->set('foo', 'bar');
  413. $tx->lpush('foo', 'bar');
  414. $tx->set('foo', $value);
  415. });
  416. } catch (ServerException $ex) {
  417. $exception = $ex;
  418. }
  419. $this->assertInstanceOf('Predis\ResponseErrorInterface', $exception);
  420. $this->assertSame($value, $client->get('foo'));
  421. }
  422. /**
  423. * @group connected
  424. */
  425. public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock()
  426. {
  427. $client = $this->getClient(array(), array('exceptions' => false));
  428. $replies = $client->multiExec(function ($tx) {
  429. $tx->set('foo', 'bar');
  430. $tx->lpush('foo', 'bar');
  431. $tx->echo('foobar');
  432. });
  433. $this->assertTrue($replies[0]);
  434. $this->assertInstanceOf('Predis\ResponseErrorInterface', $replies[1]);
  435. $this->assertSame('foobar', $replies[2]);
  436. }
  437. /**
  438. * @group connected
  439. */
  440. public function testIntegrationSendMultiOnCommandsAfterDiscard()
  441. {
  442. $client = $this->getClient();
  443. $replies = $client->multiExec(function ($tx) {
  444. $tx->set('foo', 'bar');
  445. $tx->discard();
  446. $tx->set('hoge', 'piyo');
  447. });
  448. $this->assertSame(1, count($replies));
  449. $this->assertFalse($client->exists('foo'));
  450. $this->assertTrue($client->exists('hoge'));
  451. }
  452. /**
  453. * @group connected
  454. */
  455. public function testIntegrationWritesOnWatchedKeysAbortTransaction()
  456. {
  457. $exception = null;
  458. $client1 = $this->getClient();
  459. $client2 = $this->getClient();
  460. try {
  461. $client1->multiExec(array('watch' => 'sentinel'), function ($tx) use ($client2) {
  462. $tx->set('sentinel', 'client1');
  463. $tx->get('sentinel');
  464. $client2->set('sentinel', 'client2');
  465. });
  466. } catch (AbortedMultiExecException $ex) {
  467. $exception = $ex;
  468. }
  469. $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
  470. $this->assertSame('client2', $client1->get('sentinel'));
  471. }
  472. /**
  473. * @group connected
  474. */
  475. public function testIntegrationCheckAndSetWithDiscardAndRetry()
  476. {
  477. $client = $this->getClient();
  478. $client->set('foo', 'bar');
  479. $options = array('watch' => 'foo', 'cas' => true);
  480. $replies = $client->multiExec($options, function ($tx) {
  481. $tx->watch('foobar');
  482. $foo = $tx->get('foo');
  483. $tx->multi();
  484. $tx->set('foobar', $foo);
  485. $tx->discard();
  486. $tx->mget('foo', 'foobar');
  487. });
  488. $this->assertInternalType('array', $replies);
  489. $this->assertSame(array(array('bar', null)), $replies);
  490. $hijack = true;
  491. $client2 = $this->getClient();
  492. $client->set('foo', 'bar');
  493. $options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
  494. $replies = $client->multiExec($options, function ($tx) use ($client2, &$hijack) {
  495. $foo = $tx->get('foo');
  496. $tx->multi();
  497. $tx->set('foobar', $foo);
  498. $tx->discard();
  499. if ($hijack) {
  500. $hijack = false;
  501. $client2->set('foo', 'hijacked!');
  502. }
  503. $tx->mget('foo', 'foobar');
  504. });
  505. $this->assertInternalType('array', $replies);
  506. $this->assertSame(array(array('hijacked!', null)), $replies);
  507. }
  508. // ******************************************************************** //
  509. // ---- HELPER METHODS ------------------------------------------------ //
  510. // ******************************************************************** //
  511. /**
  512. * Returns a mocked instance of Predis\Connection\SingleConnectionInterface
  513. * usingthe specified callback to return values from executeCommand().
  514. *
  515. * @param \Closure $executeCallback
  516. * @return \Predis\Connection\SingleConnectionInterface
  517. */
  518. protected function getMockedConnection($executeCallback)
  519. {
  520. $connection = $this->getMock('Predis\Connection\SingleConnectionInterface');
  521. $connection->expects($this->any())
  522. ->method('executeCommand')
  523. ->will($this->returnCallback($executeCallback));
  524. return $connection;
  525. }
  526. /**
  527. * Returns a mocked instance of Predis\Transaction\MultiExecContext using
  528. * the specified callback to return values from the executeCommand method
  529. * of the underlying connection.
  530. *
  531. * @param \Closure $executeCallback
  532. * @return MultiExecContext
  533. */
  534. protected function getMockedTransaction($executeCallback, $options = array())
  535. {
  536. $connection = $this->getMockedConnection($executeCallback);
  537. $client = new Client($connection);
  538. $transaction = new MultiExecContext($client, $options);
  539. return $transaction;
  540. }
  541. /**
  542. * Returns a callback that emulates a server-side MULTI/EXEC transaction context.
  543. *
  544. * @param array $expected Expected replies.
  545. * @param array $commands Reference to an array that stores the whole flow of commands.
  546. * @return \Closure
  547. */
  548. protected function getExecuteCallback($expected = array(), &$commands = array(), &$cas = array())
  549. {
  550. $multi = $watch = $abort = false;
  551. return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {
  552. $cmd = $command->getId();
  553. if ($multi || $cmd === 'MULTI') {
  554. $commands[] = $command;
  555. } else {
  556. $cas[] = $command;
  557. }
  558. switch ($cmd) {
  559. case 'WATCH':
  560. if ($multi) {
  561. throw new ServerException("ERR $cmd inside MULTI is not allowed");
  562. }
  563. return $watch = true;
  564. case 'MULTI':
  565. if ($multi) {
  566. throw new ServerException("ERR MULTI calls can not be nested");
  567. }
  568. return $multi = true;
  569. case 'EXEC':
  570. if (!$multi) {
  571. throw new ServerException("ERR $cmd without MULTI");
  572. }
  573. $watch = $multi = false;
  574. if ($abort) {
  575. $commands = $cas = array();
  576. $abort = false;
  577. return null;
  578. }
  579. return $expected;
  580. case 'DISCARD':
  581. if (!$multi) {
  582. throw new ServerException("ERR $cmd without MULTI");
  583. }
  584. $watch = $multi = false;
  585. return true;
  586. case 'ECHO':
  587. @list($trigger) = $command->getArguments();
  588. if (strpos($trigger, 'ERR ') === 0) {
  589. throw new ServerException($trigger);
  590. }
  591. if ($trigger === '!!ABORT!!' && $multi) {
  592. $abort = true;
  593. }
  594. return new ResponseQueued();
  595. case 'UNWATCH':
  596. $watch = false;
  597. default:
  598. return $multi ? new ResponseQueued() : 'DUMMY_REPLY';
  599. }
  600. };
  601. }
  602. /**
  603. * Converts an array of instances of Predis\Command\CommandInterface and
  604. * returns an array containing their IDs.
  605. *
  606. * @param array $commands List of commands instances.
  607. * @return array
  608. */
  609. protected static function commandsToIDs($commands) {
  610. return array_map(function($cmd) { return $cmd->getId(); }, $commands);
  611. }
  612. /**
  613. * Returns a client instance connected to the specified Redis
  614. * server instance to perform integration tests.
  615. *
  616. * @param array Additional connection parameters.
  617. * @param array Additional client options.
  618. * @return Client client instance.
  619. */
  620. protected function getClient(Array $parameters = array(), Array $options = array())
  621. {
  622. $parameters = array_merge(array(
  623. 'scheme' => 'tcp',
  624. 'host' => REDIS_SERVER_HOST,
  625. 'port' => REDIS_SERVER_PORT,
  626. 'database' => REDIS_SERVER_DBNUM,
  627. ), $parameters);
  628. $options = array_merge(array(
  629. 'profile' => REDIS_SERVER_VERSION
  630. ), $options);
  631. $client = new Client($parameters, $options);
  632. $client->connect();
  633. $client->flushdb();
  634. return $client;
  635. }
  636. }