123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960 |
- <?php
- /*
- * This file is part of the Predis package.
- *
- * (c) Daniele Alessandri <suppakilla@gmail.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- namespace Predis\Transaction;
- use Predis\Client;
- use Predis\Command\CommandInterface;
- use Predis\Response;
- use PredisTestCase;
- /**
- * @group realm-transaction
- */
- class MultiExecTest extends PredisTestCase
- {
- /**
- * @group disconnected
- * @expectedException \Predis\NotSupportedException
- * @expectedExceptionMessage MULTI, EXEC and DISCARD are not supported by the current command factory.
- */
- public function testThrowsExceptionOnUnsupportedMultiExecInCommandFactory()
- {
- $commands = $this->getMock('Predis\Command\FactoryInterface');
- $commands
- ->expects($this->once())
- ->method('supportsCommands')
- ->with(array('MULTI', 'EXEC', 'DISCARD'))
- ->will($this->returnValue(false));
- $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
- $client = new Client($connection, array('commands' => $commands));
- new MultiExec($client);
- }
- /**
- * @group disconnected
- * @expectedException \Predis\NotSupportedException
- * @expectedExceptionMessage WATCH is not supported by the current command factory.
- */
- public function testThrowsExceptionOnUnsupportedWatchInCommandFactory()
- {
- $commands = $this->getMock('Predis\Command\FactoryInterface');
- $commands
- ->expects($this->once())
- ->method('supportsCommands')
- ->with(array('MULTI', 'EXEC', 'DISCARD'))
- ->will($this->returnValue(true));
- $commands
- ->expects($this->once())
- ->method('supportsCommand')
- ->with('WATCH')
- ->will($this->returnValue(false));
- $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
- $client = new Client($connection, array('commands' => $commands));
- $tx = new MultiExec($client, array('options' => 'cas'));
- $tx->watch('foo');
- }
- /**
- * @group disconnected
- * @expectedException \Predis\NotSupportedException
- * @expectedExceptionMessage UNWATCH is not supported by the current command factory.
- */
- public function testThrowsExceptionOnUnsupportedUnwatchInCommandFactory()
- {
- $commands = $this->getMock('Predis\Command\FactoryInterface');
- $commands
- ->expects($this->once())
- ->method('supportsCommands')
- ->with(array('MULTI', 'EXEC', 'DISCARD'))
- ->will($this->returnValue(true));
- $commands
- ->expects($this->once())
- ->method('supportsCommand')
- ->with('UNWATCH')
- ->will($this->returnValue(false));
- $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
- $client = new Client($connection, array('commands' => $commands));
- $tx = new MultiExec($client, array('options' => 'cas'));
- $tx->unwatch('foo');
- }
- /**
- * @group disconnected
- */
- public function testExecutionWithFluentInterface()
- {
- $commands = array();
- $expected = array('one', 'two', 'three');
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $this->assertSame($expected, $tx->echo('one')->echo('two')->echo('three')->execute());
- $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testExecutionWithCallable()
- {
- $commands = array();
- $expected = array('one', 'two', 'three');
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- $tx->echo('one');
- $tx->echo('two');
- $tx->echo('three');
- });
- $this->assertSame($expected, $responses);
- $this->assertSame(array('MULTI', 'ECHO', 'ECHO', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testCannotMixExecutionWithFluentInterfaceAndCallable()
- {
- $commands = array();
- $callback = $this->getExecuteCallback(null, $commands);
- $tx = $this->getMockedTransaction($callback);
- $exception = null;
- try {
- $tx->echo('foo')->execute(function ($tx) {
- $tx->echo('bar');
- });
- } catch (\Exception $ex) {
- $exception = $ex;
- }
- $this->assertInstanceOf('Predis\ClientException', $exception);
- $this->assertSame(array('MULTI', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testEmptyTransactionDoesNotSendMultiExecCommands()
- {
- $commands = array();
- $callback = $this->getExecuteCallback(null, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- // NOOP
- });
- $this->assertNull($responses);
- $this->assertSame(array(), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- * @expectedException \Predis\ClientException
- * @expectedExceptionMessage Cannot invoke "execute" or "exec" inside an active transaction context.
- */
- public function testThrowsExceptionOnExecInsideTransactionBlock()
- {
- $commands = array();
- $callback = $this->getExecuteCallback(null, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- $tx->exec();
- });
- $this->assertNull($responses);
- $this->assertSame(array(), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testEmptyTransactionIgnoresDiscard()
- {
- $commands = array();
- $callback = $this->getExecuteCallback(null, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- $tx->discard();
- });
- $this->assertNull($responses);
- $this->assertSame(array(), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testTransactionWithCommandsSendsDiscard()
- {
- $commands = array();
- $callback = $this->getExecuteCallback(null, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- $tx->set('foo', 'bar');
- $tx->get('foo');
- $tx->discard();
- });
- $this->assertNull($responses);
- $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testSendMultiOnCommandsFollowingDiscard()
- {
- $commands = array();
- $expected = array('after DISCARD');
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->execute(function ($tx) {
- $tx->echo('before DISCARD');
- $tx->discard();
- $tx->echo('after DISCARD');
- });
- $this->assertSame($responses, $expected);
- $this->assertSame(array('MULTI', 'ECHO', 'DISCARD', 'MULTI', 'ECHO', 'EXEC'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- * @expectedException \Predis\ClientException
- */
- public function testThrowsExceptionOnWatchInsideMulti()
- {
- $callback = $this->getExecuteCallback();
- $tx = $this->getMockedTransaction($callback);
- $tx->echo('foobar')->watch('foo')->execute();
- }
- /**
- * @group disconnected
- */
- public function testUnwatchInsideMulti()
- {
- $commands = array();
- $expected = array('foobar', true);
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = $tx->echo('foobar')->unwatch('foo')->execute();
- $this->assertSame($responses, $expected);
- $this->assertSame(array('MULTI', 'ECHO', 'UNWATCH', 'EXEC'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testAutomaticWatchInOptions()
- {
- $txCommands = $casCommands = array();
- $expected = array('bar', 'piyo');
- $options = array('watch' => array('foo', 'hoge'));
- $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $responses = $tx->execute(function ($tx) {
- $tx->get('foo');
- $tx->get('hoge');
- });
- $this->assertSame($responses, $expected);
- $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
- $this->assertSame(array('foo', 'hoge'), $casCommands[0]->getArguments());
- $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- */
- public function testCheckAndSetWithFluentInterface()
- {
- $txCommands = $casCommands = array();
- $expected = array('bar', 'piyo');
- $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
- $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $tx->watch('foobar');
- $this->assertSame('DUMMY_RESPONSE', $tx->get('foo'));
- $this->assertSame('DUMMY_RESPONSE', $tx->get('hoge'));
- $responses = $tx
- ->multi()
- ->get('foo')
- ->get('hoge')
- ->execute();
- $this->assertSame($responses, $expected);
- $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
- $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- */
- public function testCheckAndSetWithBlock()
- {
- $txCommands = $casCommands = array();
- $expected = array('bar', 'piyo');
- $options = array('cas' => true, 'watch' => array('foo', 'hoge'));
- $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $test = $this;
- $responses = $tx->execute(function ($tx) use ($test) {
- $tx->watch('foobar');
- $response1 = $tx->get('foo');
- $response2 = $tx->get('hoge');
- $test->assertSame('DUMMY_RESPONSE', $response1);
- $test->assertSame('DUMMY_RESPONSE', $response2);
- $tx->multi();
- $tx->get('foo');
- $tx->get('hoge');
- });
- $this->assertSame($responses, $expected);
- $this->assertSame(array('WATCH', 'WATCH', 'GET', 'GET'), self::commandsToIDs($casCommands));
- $this->assertSame(array('MULTI', 'GET', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- */
- public function testCheckAndSetWithEmptyBlock()
- {
- $txCommands = $casCommands = array();
- $options = array('cas' => true);
- $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $tx->execute(function ($tx) {
- $tx->multi();
- });
- $this->assertSame(array(), self::commandsToIDs($casCommands));
- $this->assertSame(array(), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- */
- public function testCheckAndSetWithoutExec()
- {
- $txCommands = $casCommands = array();
- $options = array('cas' => true);
- $callback = $this->getExecuteCallback(array(), $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $tx->execute(function ($tx) {
- $tx->get('foo');
- $tx->set('hoge', 'piyo');
- });
- $this->assertSame(array('GET', 'SET'), self::commandsToIDs($casCommands));
- $this->assertSame(array(), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- * @expectedException \Predis\ClientException
- * @expectedExceptionMessage Automatic retries are supported only when a callable block is provided.
- */
- public function testThrowsExceptionOnAutomaticRetriesWithFluentInterface()
- {
- $options = array('retry' => 1);
- $callback = $this->getExecuteCallback();
- $tx = $this->getMockedTransaction($callback, $options);
- $tx->echo('message')->execute();
- }
- /**
- * @group disconnected
- */
- public function testAutomaticRetryOnServerSideTransactionAbort()
- {
- $casCommands = $txCommands = array();
- $expected = array('bar');
- $options = array('watch' => array('foo', 'bar'), 'retry' => ($attempts = 2) + 1);
- $sentinel = $this->getMock('stdClass', array('signal'));
- $sentinel->expects($this->exactly($attempts))->method('signal');
- $callback = $this->getExecuteCallback($expected, $txCommands, $casCommands);
- $tx = $this->getMockedTransaction($callback, $options);
- $responses = $tx->execute(function ($tx) use ($sentinel, &$attempts) {
- $tx->get('foo');
- if ($attempts > 0) {
- $attempts -= 1;
- $sentinel->signal();
- $tx->echo('!!ABORT!!');
- }
- });
- $this->assertSame($responses, $expected);
- $this->assertSame(array('WATCH'), self::commandsToIDs($casCommands));
- $this->assertSame(array('foo', 'bar'), $casCommands[0]->getArguments());
- $this->assertSame(array('MULTI', 'GET', 'EXEC'), self::commandsToIDs($txCommands));
- }
- /**
- * @group disconnected
- * @expectedException \Predis\Transaction\AbortedMultiExecException
- */
- public function testThrowsExceptionOnServerSideTransactionAbort()
- {
- $callback = $this->getExecuteCallback();
- $tx = $this->getMockedTransaction($callback);
- $tx->execute(function ($tx) {
- $tx->echo('!!ABORT!!');
- });
- }
- /**
- * @group disconnected
- */
- public function testHandlesStandardExceptionsInBlock()
- {
- $commands = array();
- $expected = array('foobar', true);
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = null;
- try {
- $responses = $tx->execute(function ($tx) {
- $tx->set('foo', 'bar');
- $tx->get('foo');
- throw new \RuntimeException('TEST');
- });
- } catch (\Exception $ex) {
- // NOOP
- }
- $this->assertNull($responses, $expected);
- $this->assertSame(array('MULTI', 'SET', 'GET', 'DISCARD'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testHandlesServerExceptionsInBlock()
- {
- $commands = array();
- $expected = array('foobar', true);
- $callback = $this->getExecuteCallback($expected, $commands);
- $tx = $this->getMockedTransaction($callback);
- $responses = null;
- try {
- $responses = $tx->execute(function ($tx) {
- $tx->set('foo', 'bar');
- $tx->echo('ERR Invalid operation');
- $tx->get('foo');
- });
- } catch (Response\ServerException $ex) {
- $tx->discard();
- }
- $this->assertNull($responses);
- $this->assertSame(array('MULTI', 'SET', 'ECHO', 'DISCARD'), self::commandsToIDs($commands));
- }
- /**
- * @group disconnected
- */
- public function testProperlyDiscardsTransactionAfterServerExceptionInBlock()
- {
- $connection = $this->getMockedConnection(function (CommandInterface $command) {
- switch ($command->getId()) {
- case 'MULTI':
- return true;
- case 'ECHO':
- return new Response\Error('ERR simulated failure on ECHO');
- case 'EXEC':
- return new Response\Error('EXECABORT Transaction discarded because of previous errors.');
- default:
- return new Response\Status('QUEUED');
- }
- });
- $client = new Client($connection);
- // First attempt
- $tx = new MultiExec($client);
- try {
- $tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
- } catch (\Exception $exception) {
- $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
- $this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
- }
- // Second attempt
- $tx = new MultiExec($client);
- try {
- $tx->multi()->set('foo', 'bar')->echo('simulated failure')->exec();
- } catch (\Exception $exception) {
- $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
- $this->assertSame('ERR simulated failure on ECHO', $exception->getMessage());
- }
- }
- /**
- * @group disconnected
- */
- public function testExceptionsOptionTakesPrecedenceOverClientOptionsWhenFalse()
- {
- $expected = array('before', new Response\Error('ERR simulated error'), 'after');
- $connection = $this->getMockedConnection(function (CommandInterface $command) use ($expected) {
- switch ($command->getId()) {
- case 'MULTI':
- return true;
- case 'EXEC':
- return $expected;
- default:
- return new Response\Status('QUEUED');
- }
- });
- $client = new Client($connection, array('exceptions' => true));
- $tx = new MultiExec($client, array('exceptions' => false));
- $result = $tx
- ->multi()
- ->echo('before')
- ->echo('ERROR PLEASE!')
- ->echo('after')
- ->exec();
- $this->assertSame($expected, $result);
- }
- /**
- * @group disconnected
- * @expectedException \Predis\Response\ServerException
- * @expectedExceptionMessage ERR simulated error
- */
- public function testExceptionsOptionTakesPrecedenceOverClientOptionsWhenTrue()
- {
- $expected = array('before', new Response\Error('ERR simulated error'), 'after');
- $connection = $this->getMockedConnection(function (CommandInterface $command) use ($expected) {
- switch ($command->getId()) {
- case 'MULTI':
- return true;
- case 'EXEC':
- return $expected;
- default:
- return new Response\Status('QUEUED');
- }
- });
- $client = new Client($connection, array('exceptions' => false));
- $tx = new MultiExec($client, array('exceptions' => true));
- $tx->multi()->echo('before')->echo('ERROR PLEASE!')->echo('after')->exec();
- }
- /**
- * @group disconnected
- * @expectedException \Predis\Response\ServerException
- * @expectedExceptionMessage ERR simulated failure on EXEC
- */
- public function testExceptionsOptionDoesNotAffectTransactionControlCommands()
- {
- $connection = $this->getMockedConnection(function (CommandInterface $command) {
- switch ($command->getId()) {
- case 'MULTI':
- return true;
- case 'EXEC':
- return new Response\Error('ERR simulated failure on EXEC');
- default:
- return new Response\Status('QUEUED');
- }
- });
- $client = new Client($connection, array('exceptions' => false));
- $tx = new MultiExec($client);
- $tx->multi()->echo('test')->exec();
- }
- // ******************************************************************** //
- // ---- INTEGRATION TESTS --------------------------------------------- //
- // ******************************************************************** //
- /**
- * @group connected
- */
- public function testIntegrationHandlesStandardExceptionsInBlock()
- {
- $client = $this->getClient();
- $exception = null;
- try {
- $client->transaction(function ($tx) {
- $tx->set('foo', 'bar');
- throw new \RuntimeException('TEST');
- });
- } catch (\Exception $ex) {
- $exception = $ex;
- }
- $this->assertInstanceOf('RuntimeException', $exception);
- $this->assertSame(0, $client->exists('foo'));
- }
- /**
- * @group connected
- */
- public function testIntegrationThrowsExceptionOnRedisErrorInBlock()
- {
- $client = $this->getClient();
- $exception = null;
- $value = (string) rand();
- try {
- $client->transaction(function ($tx) use ($value) {
- $tx->set('foo', 'bar');
- $tx->lpush('foo', 'bar');
- $tx->set('foo', $value);
- });
- } catch (Response\ServerException $ex) {
- $exception = $ex;
- }
- $this->assertInstanceOf('Predis\Response\ErrorInterface', $exception);
- $this->assertSame($value, $client->get('foo'));
- }
- /**
- * @group connected
- */
- public function testIntegrationReturnsErrorObjectOnRedisErrorInBlock()
- {
- $client = $this->getClient(array(), array('exceptions' => false));
- $responses = $client->transaction(function ($tx) {
- $tx->set('foo', 'bar');
- $tx->lpush('foo', 'bar');
- $tx->echo('foobar');
- });
- $this->assertInstanceOf('Predis\Response\Status', $responses[0]);
- $this->assertInstanceOf('Predis\Response\Error', $responses[1]);
- $this->assertSame('foobar', $responses[2]);
- }
- /**
- * @group connected
- * @requiresRedisVersion >= 2.0.0
- */
- public function testIntegrationSendMultiOnCommandsAfterDiscard()
- {
- $client = $this->getClient();
- $responses = $client->transaction(function ($tx) {
- $tx->set('foo', 'bar');
- $tx->discard();
- $tx->set('hoge', 'piyo');
- });
- $this->assertSame(1, count($responses));
- $this->assertSame(0, $client->exists('foo'));
- $this->assertSame(1, $client->exists('hoge'));
- }
- /**
- * @group connected
- * @requiresRedisVersion >= 2.2.0
- */
- public function testIntegrationWritesOnWatchedKeysAbortTransaction()
- {
- $exception = null;
- $client1 = $this->getClient();
- $client2 = $this->getClient();
- try {
- $client1->transaction(array('watch' => 'sentinel'), function ($tx) use ($client2) {
- $tx->set('sentinel', 'client1');
- $tx->get('sentinel');
- $client2->set('sentinel', 'client2');
- });
- } catch (AbortedMultiExecException $ex) {
- $exception = $ex;
- }
- $this->assertInstanceOf('Predis\Transaction\AbortedMultiExecException', $exception);
- $this->assertSame('client2', $client1->get('sentinel'));
- }
- /**
- * @group connected
- * @requiresRedisVersion >= 2.2.0
- */
- public function testIntegrationCheckAndSetWithDiscardAndRetry()
- {
- $client = $this->getClient();
- $client->set('foo', 'bar');
- $options = array('watch' => 'foo', 'cas' => true);
- $responses = $client->transaction($options, function ($tx) {
- $tx->watch('foobar');
- $foo = $tx->get('foo');
- $tx->multi();
- $tx->set('foobar', $foo);
- $tx->discard();
- $tx->mget('foo', 'foobar');
- });
- $this->assertInternalType('array', $responses);
- $this->assertSame(array(array('bar', null)), $responses);
- $hijack = true;
- $client2 = $this->getClient();
- $client->set('foo', 'bar');
- $options = array('watch' => 'foo', 'cas' => true, 'retry' => 1);
- $responses = $client->transaction($options, function ($tx) use ($client2, &$hijack) {
- $foo = $tx->get('foo');
- $tx->multi();
- $tx->set('foobar', $foo);
- $tx->discard();
- if ($hijack) {
- $hijack = false;
- $client2->set('foo', 'hijacked!');
- }
- $tx->mget('foo', 'foobar');
- });
- $this->assertInternalType('array', $responses);
- $this->assertSame(array(array('hijacked!', null)), $responses);
- }
- // ******************************************************************** //
- // ---- HELPER METHODS ------------------------------------------------ //
- // ******************************************************************** //
- /**
- * Returns a mocked instance of Predis\Connection\NodeConnectionInterface
- * using the specified callback to return values from executeCommand().
- *
- * @param \Closure $executeCallback
- *
- * @return \Predis\Connection\NodeConnectionInterface
- */
- protected function getMockedConnection($executeCallback)
- {
- $connection = $this->getMock('Predis\Connection\NodeConnectionInterface');
- $connection
- ->expects($this->any())
- ->method('executeCommand')
- ->will($this->returnCallback($executeCallback));
- return $connection;
- }
- /**
- * Returns a mocked instance of Predis\Transaction\MultiExec using
- * the specified callback to return values from the executeCommand method
- * of the underlying connection.
- *
- * @param \Closure $executeCallback
- * @param array $txOpts
- * @param array $clientOpts
- *
- * @return MultiExec
- */
- protected function getMockedTransaction($executeCallback, $txOpts = null, $clientOpts = null)
- {
- $connection = $this->getMockedConnection($executeCallback);
- $client = new Client($connection, $clientOpts ?: array());
- $transaction = new MultiExec($client, $txOpts ?: array());
- return $transaction;
- }
- /**
- * Returns a callback that emulates a server-side MULTI/EXEC transaction context.
- *
- * @param array $expected Expected responses.
- * @param array $commands Reference to an array storing the whole flow of commands.
- * @param array $cas Check and set operations performed by the transaction.
- *
- * @return \Closure
- */
- protected function getExecuteCallback($expected = array(), &$commands = array(), &$cas = array())
- {
- $multi = $watch = $abort = false;
- return function (CommandInterface $command) use (&$expected, &$commands, &$cas, &$multi, &$watch, &$abort) {
- $cmd = $command->getId();
- if ($multi || $cmd === 'MULTI') {
- $commands[] = $command;
- } else {
- $cas[] = $command;
- }
- switch ($cmd) {
- case 'WATCH':
- if ($multi) {
- return new Response\Error("ERR $cmd inside MULTI is not allowed");
- }
- return $watch = true;
- case 'MULTI':
- if ($multi) {
- return new Response\Error('ERR MULTI calls can not be nested');
- }
- return $multi = true;
- case 'EXEC':
- if (!$multi) {
- return new Response\Error("ERR $cmd without MULTI");
- }
- $watch = $multi = false;
- if ($abort) {
- $commands = $cas = array();
- $abort = false;
- return;
- }
- return $expected;
- case 'DISCARD':
- if (!$multi) {
- return new Response\Error("ERR $cmd without MULTI");
- }
- $watch = $multi = false;
- return true;
- case 'ECHO':
- @list($trigger) = $command->getArguments();
- if (strpos($trigger, 'ERR ') === 0) {
- throw new Response\ServerException($trigger);
- }
- if ($trigger === '!!ABORT!!' && $multi) {
- $abort = true;
- }
- return new Response\Status('QUEUED');
- case 'UNWATCH':
- $watch = false;
- // no break
- default:
- return $multi ? new Response\Status('QUEUED') : 'DUMMY_RESPONSE';
- }
- };
- }
- /**
- * Converts an array of instances of Predis\Command\CommandInterface and
- * returns an array containing their IDs.
- *
- * @param array $commands List of commands instances.
- *
- * @return array
- */
- protected static function commandsToIDs($commands)
- {
- return array_map(function ($cmd) { return $cmd->getId(); }, $commands);
- }
- /**
- * Returns a client instance connected to the specified Redis
- * server instance to perform integration tests.
- *
- * @param array $parameters Additional connection parameters.
- * @param array $options Additional client options.
- *
- * @return Client
- */
- protected function getClient(array $parameters = array(), array $options = array())
- {
- return $this->createClient($parameters, $options);
- }
- }
|