From 9318232e8d3c1c7cf070e907f51adfc23cfa8adb Mon Sep 17 00:00:00 2001 From: Vladyslav Vildanov <117659936+vladvildanov@users.noreply.github.com> Date: Tue, 25 Jul 2023 10:34:02 +0300 Subject: [PATCH] Merge main into 3.x (#1342) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changed command arguments (#1330) * Re-implement CLIENT command as container command (#1337) * Resolve merge conflicts * Merge 2.x into main (#1341) * Codestyle changes related to php-cs-fixer update (#1311) * Codestyle changes * Added missing type-hints * Added GETDEL command to KeyPrefixProcessor (#1306) * Added GETDEL command to KeyPrefixProcessor * Added test coverage * Codestyle fixes * Added timeout after FT.CREATE call * Added support for JSON.MERGE command (#1304) * Added support for JSON.MSET command (#1307) * Fixed subcommand test bug (#1313) * Update CHANGELOG.md * Update CHANGELOG.md * Fixed bug with incorrect multiple words processing (#1325) * Fixed bug with incorrect multiple words processing * Convert subcommand string to lower case * Update SubcommandStrategyResolver.php * Added test coverage * Codestyle fixes --------- Co-authored-by: Till Krüss * Added support for FUNCTION DUMP, FUNCTION FLUSH, FUNCTION RESTORE commands (#1332) * Added support for CLIENT NO-EVICT command (#1335) * Added support for FUNCTION KILL, FUNCTION LIST, FUNCTION STATS commands (#1334) * Added support for FUNCTION KILL, FUNCTION LIST, FUNCTION STATS commands * Marked tests as relay-incompatible * Added support for tests running against redis cluster (#1236) * Added support for tests running against redis cluster * Test coverage * Added comment about master nodes * Codestyle fix * Revert changes * Revert DBNUM * Added cluster endpoints to relay tests env configuration * Exclude cluster tests from relay tests environment * Removed TODO comment * Changed cluster image version to unstable * Updated configuration to match unstable cluster * Fixed path * Updated cluster CI configuration * Removed redundant flag * Removed backslash * Updated file path * Updated file path variable * Added docker cluster initialization as additional step * Run cluster tests as separate workflow * Codestyle fixes * Updated exported files * Added additional timeout so cluster image could be settled * Added support for different cluster image, use docker compose for cluster tests CI * Remove unused flag * Removed variable from volume path * Added sleep timeout to allow docker setup after running * Added timeout before tests run * Updated linter settings * Include indent changes for.sh files * Added missing coverage * Revert expected files and mark docker folder as exclusion * Specify folder itself as excluded * Moved cluster tests as separate job in tests.yml * Updated name to contain cluster word --------- Co-authored-by: Chayim --------- Co-authored-by: Till Krüss Co-authored-by: Chayim --------- Co-authored-by: Till Krüss Co-authored-by: Chayim --- .codespellrc | 2 +- .editorconfig | 2 +- .github/workflows/linters.yml | 2 +- .github/workflows/tests.yml | 43 +++ CHANGELOG.md | 18 +- docker/unstable_cluster/Dockerfile | 7 + docker/unstable_cluster/create_cluster.sh | 47 ++++ docker/unstable_cluster/docker-compose.yml | 17 ++ docker/unstable_cluster/redis.conf | 9 + phpunit.relay.xml | 5 + phpunit.xml.dist | 5 + src/ClientContextInterface.php | 33 ++- src/ClientInterface.php | 33 ++- src/Cluster/ClusterStrategy.php | 14 + src/Command/Container/AbstractContainer.php | 2 +- src/Command/Container/CLIENT.php | 31 +++ src/Command/Container/FUNCTIONS.php | 8 +- src/Command/Redis/CLIENT.php | 67 +++++ src/Command/Redis/FUNCTIONS.php | 2 +- .../Functions/DumpStrategy.php | 26 ++ .../Functions/FlushStrategy.php | 32 +++ .../Functions/KillStrategy.php | 26 ++ .../Functions/ListStrategy.php | 36 +++ .../Functions/RestoreStrategy.php | 32 +++ .../Functions/StatsStrategy.php | 26 ++ .../Strategy/SubcommandStrategyResolver.php | 19 +- tests/PHPUnit/PredisCommandTestCase.php | 6 +- tests/PHPUnit/PredisTestCase.php | 45 +++ tests/Predis/Cluster/PredisStrategyTest.php | 19 ++ tests/Predis/Cluster/RedisStrategyTest.php | 19 ++ tests/Predis/Command/Redis/CLIENT_Test.php | 154 ++++++++--- tests/Predis/Command/Redis/FUNCTIONS_Test.php | 260 +++++++++++++++++- tests/Predis/Command/Redis/SET_Test.php | 13 + .../Functions/DumpStrategyTest.php | 37 +++ .../Functions/FlushStrategyTest.php | 54 ++++ .../Functions/KillStrategyTest.php | 37 +++ .../Functions/ListStrategyTest.php | 62 +++++ .../Functions/RestoreStrategyTest.php | 54 ++++ .../Functions/StatsStrategyTest.php | 37 +++ .../SubcommandStrategyResolverTest.php | 30 +- 40 files changed, 1271 insertions(+), 100 deletions(-) create mode 100644 docker/unstable_cluster/Dockerfile create mode 100644 docker/unstable_cluster/create_cluster.sh create mode 100644 docker/unstable_cluster/docker-compose.yml create mode 100644 docker/unstable_cluster/redis.conf create mode 100644 src/Command/Container/CLIENT.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/DumpStrategy.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/FlushStrategy.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/KillStrategy.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/ListStrategy.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/RestoreStrategy.php create mode 100644 src/Command/Strategy/ContainerCommands/Functions/StatsStrategy.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/DumpStrategyTest.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/FlushStrategyTest.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/KillStrategyTest.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/ListStrategyTest.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/RestoreStrategyTest.php create mode 100644 tests/Predis/Command/Strategy/ContainerCommands/Functions/StatsStrategyTest.php diff --git a/.codespellrc b/.codespellrc index 0e95e105..70f77356 100644 --- a/.codespellrc +++ b/.codespellrc @@ -3,4 +3,4 @@ skip=./.git check-hidden= check-filenames= builtin=clear,rare,informal,usage,code,names -ignore-words-list=master,masters,slave,slaves,whitelist,cas,exat,smove,SUGGET,sugget +ignore-words-list=master,masters,slave,slaves,whitelist,cas,exat,smove,SUGGET,sugget,ro diff --git a/.editorconfig b/.editorconfig index 0764720b..9cffd80e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,7 +14,7 @@ block_comment_end = */ [*.php] max_line_length = 150 -[*.{md,yml,yaml,neon}] +[*.{md,yml,yaml,neon,sh}] indent_size = 2 [tests/**.php] diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index b6d481b0..1e22c899 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/*" \ + | tar --list --exclude="src" --exclude="src/*" --exclude="bin" --exclude="bin/*" --exclude="docker" --exclude="docker/*" \ | paste --serial --delimiters="," )" echo "CURRENT =${CURRENT}" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b5259142..44f3b7f3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -78,3 +78,46 @@ jobs: run: | wget "https://github.com/php-coveralls/php-coveralls/releases/download/v2.5.3/php-coveralls.phar" php ./php-coveralls.phar -v + + predis-cluster: + + name: PHP ${{ matrix.php }} (Redis Cluster latest) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + php: + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Run redis cluster + uses: isbang/compose-action@v1.4.1 + with: + compose-file: "./docker/unstable_cluster/docker-compose.yml" + + - name: Setup PHP with Composer and extensions + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: relay + coverage: ${{ (matrix.php == '8.1') && 'xdebug' || 'none' }} + + - name: Install Composer dependencies + uses: ramsey/composer-install@v2 + with: + dependency-versions: highest + composer-options: ${{ matrix.php == '8.0' && '--ignore-platform-reqs' || '' }} + + - name: Run tests against cluster + run: | + sleep 5 # Timeout to make sure that docker image is setup + vendor/bin/phpunit --group cluster diff --git a/CHANGELOG.md b/CHANGELOG.md index fd468fff..bfb2d3c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,8 @@ ## Changelog -## Unreleased +## v2.2.0 (2023-06-14) -### Changed -- Added `PrefixableCommandInterface` (#1262) - -### Removed -- Removed Webdis and Phpiredis support (#1291) +Predis v2.2.0 introduces official support for [Redis Stack](https://redis.io/docs/stack/) as well as a [Relay](https://github.com/cachewerk/relay) integration for substantially [faster read performance](https://github.com/predis/predis/wiki/Using-Relay). ## v2.2.0-RC1 (2023-05-09) @@ -16,11 +12,15 @@ - Added support for Redis `JSON`, `Bloom`, `Search` and `TimeSeries` module (#1253) - Added support for `ACL SETUSER, GETUSER, DRYRUN` commands (#1193) +### Changed +- Minor code style and type-hint changes (#1311) + ### Fixed - Fixed prefixes for `XTRIM` and `XREVRANGE` commands (#1230) -- Fix `fclose()` being called on invalid stream resource (#1199) -- Fix `BitByte` and `ExpireOptions` traits skip processing on null values (#1169) -- Fix missing `@return` annotations (#1265) +- Fixed `fclose()` being called on invalid stream resource (#1199) +- Fixed `BitByte` and `ExpireOptions` traits skip processing on null values (#1169) +- Fixed missing `@return` annotations (#1265) +- Fixed `GETDEL` prefixing (#1306) ## v2.1.2 (2023-03-02) diff --git a/docker/unstable_cluster/Dockerfile b/docker/unstable_cluster/Dockerfile new file mode 100644 index 00000000..a434dddc --- /dev/null +++ b/docker/unstable_cluster/Dockerfile @@ -0,0 +1,7 @@ +FROM redis/redis-stack-server:latest as rss + +COPY create_cluster.sh /create_cluster.sh +RUN ls -R /opt/redis-stack +RUN chmod a+x /create_cluster.sh + +ENTRYPOINT [ "/create_cluster.sh"] diff --git a/docker/unstable_cluster/create_cluster.sh b/docker/unstable_cluster/create_cluster.sh new file mode 100644 index 00000000..b000a2ad --- /dev/null +++ b/docker/unstable_cluster/create_cluster.sh @@ -0,0 +1,47 @@ +#! /bin/bash + +mkdir -p /nodes +touch /nodes/nodemap +if [ -z ${START_PORT} ]; then + START_PORT=6372 +fi +if [ -z ${END_PORT} ]; then + END_PORT=6377 +fi +if [ ! -z "$3" ]; then + START_PORT=$2 + START_PORT=$3 +fi +echo "STARTING: ${START_PORT}" +echo "ENDING: ${END_PORT}" + +for PORT in `seq ${START_PORT} ${END_PORT}`; do + mkdir -p /nodes/$PORT + if [[ -e /redis.conf ]]; then + cp /redis.conf /nodes/$PORT/redis.conf + else + touch /nodes/$PORT/redis.conf + fi + cat << EOF >> /nodes/$PORT/redis.conf +port ${PORT} +cluster-enabled yes +daemonize yes +logfile /redis.log +dir /nodes/$PORT +EOF + + set -x + /opt/redis-stack/bin/redis-server /nodes/$PORT/redis.conf + sleep 1 + if [ $? -ne 0 ]; then + echo "Redis failed to start, exiting." + continue + fi + echo 127.0.0.1:$PORT >> /nodes/nodemap +done +if [ -z "${REDIS_PASSWORD}" ]; then + echo yes | /opt/redis-stack/bin/redis-cli --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1 +else + echo yes | opt/redis-stack/bin/redis-cli -a ${REDIS_PASSWORD} --cluster create `seq -f 127.0.0.1:%g ${START_PORT} ${END_PORT}` --cluster-replicas 1 +fi +tail -f /redis.log diff --git a/docker/unstable_cluster/docker-compose.yml b/docker/unstable_cluster/docker-compose.yml new file mode 100644 index 00000000..2829da39 --- /dev/null +++ b/docker/unstable_cluster/docker-compose.yml @@ -0,0 +1,17 @@ +version: "3.9" +services: + cluster: + container_name: redis-cluster + build: + context: . + dockerfile: Dockerfile + ports: + - "6372:6372" + - "6373:6373" + - "6374:6374" + - "6375:6375" + - "6376:6376" + - "6377:6378" + volumes: + - "./redis.conf:/redis.conf:ro" + diff --git a/docker/unstable_cluster/redis.conf b/docker/unstable_cluster/redis.conf new file mode 100644 index 00000000..71928253 --- /dev/null +++ b/docker/unstable_cluster/redis.conf @@ -0,0 +1,9 @@ +# Redis Cluster config file will be shared across all nodes. +# Do not change the following configurations that are already set: +# port, cluster-enabled, daemonize, logfile, dir +protected-mode no +loadmodule /opt/redis-stack/lib/redisearch.so +loadmodule /opt/redis-stack/lib/redisgraph.so +loadmodule /opt/redis-stack/lib/redistimeseries.so +loadmodule /opt/redis-stack/lib/rejson.so +loadmodule /opt/redis-stack/lib/redisbloom.so diff --git a/phpunit.relay.xml b/phpunit.relay.xml index bfe4f52a..81d4cc49 100644 --- a/phpunit.relay.xml +++ b/phpunit.relay.xml @@ -22,6 +22,7 @@ relay-incompatible realm-stack ext-curl + cluster @@ -36,5 +37,9 @@ + + + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 662e1fd4..a9332630 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -31,6 +31,7 @@ realm-stack ext-relay ext-curl + cluster @@ -49,5 +50,9 @@ + + + + diff --git a/src/ClientContextInterface.php b/src/ClientContextInterface.php index f2ed60b0..4ce72a5c 100644 --- a/src/ClientContextInterface.php +++ b/src/ClientContextInterface.php @@ -38,11 +38,12 @@ use Predis\Command\Argument\TimeSeries\MGetArguments; use Predis\Command\Argument\TimeSeries\MRangeArguments; use Predis\Command\Argument\TimeSeries\RangeArguments; use Predis\Command\CommandInterface; -use Predis\Command\Redis\Container\ACL; -use Predis\Command\Redis\Container\FunctionContainer; -use Predis\Command\Redis\Container\Json\JSONDEBUG; -use Predis\Command\Redis\Container\Search\FTCONFIG; -use Predis\Command\Redis\Container\Search\FTCURSOR; +use Predis\Command\Container\ACL; +use Predis\Command\Container\CLIENT; +use Predis\Command\Container\FUNCTIONS; +use Predis\Command\Container\Json\JSONDEBUG; +use Predis\Command\Container\Search\FTCONFIG; +use Predis\Command\Container\Search\FTCURSOR; /** * Interface defining a client-side context such as a pipeline or transaction. @@ -58,8 +59,8 @@ use Predis\Command\Redis\Container\Search\FTCURSOR; * @method $this move($key, $db) * @method $this object($subcommand, $key) * @method $this persist($key) - * @method $this pexpire($key, $milliseconds) - * @method $this pexpireat($key, $timestamp) + * @method $this pexpire($key, $milliseconds, string $option = null) + * @method $this pexpireat($key, $timestamp, string $option = null) * @method $this pttl($key) * @method $this randomkey() * @method $this rename($key, $target) @@ -320,21 +321,18 @@ use Predis\Command\Redis\Container\Search\FTCURSOR; * @method $this select($database) * @method $this bgrewriteaof() * @method $this bgsave() - * @method $this client($subcommand, ...$argument = null) * @method $this config($subcommand, $argument = null) * @method $this dbsize() * @method $this flushall() * @method $this flushdb() - * @method $this info($section = null) + * @method $this info(string ...$section = null) * @method $this lastsave() * @method $this save() * @method $this slaveof($host, $port) * @method $this slowlog($subcommand, $argument = null) * @method $this spublish(string $shardChannel, string $message) - * @method $this ssubscribe(string ...$shardChannels) - * @method $this sunsubscribe(string ...$shardChannels) * @method $this time() - * @method $this command() + * @method $this command($subcommand, $argument = null) * @method $this geoadd($key, $longitude, $latitude, $member) * @method $this geohash($key, array $members) * @method $this geopos($key, array $members) @@ -345,11 +343,12 @@ use Predis\Command\Redis\Container\Search\FTCURSOR; * @method $this geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false) * * Container commands - * @property FunctionContainer $function - * @property FTCONFIG $ftconfig - * @property FTCURSOR $ftcursor - * @property JSONDEBUG $jsondebug - * @property ACL $acl + * @property CLIENT $client + * @property FUNCTIONS $function + * @property FTCONFIG $ftconfig + * @property FTCURSOR $ftcursor + * @property JSONDEBUG $jsondebug + * @property ACL $acl */ interface ClientContextInterface { diff --git a/src/ClientInterface.php b/src/ClientInterface.php index 785c7e53..6e8762a7 100644 --- a/src/ClientInterface.php +++ b/src/ClientInterface.php @@ -38,12 +38,13 @@ use Predis\Command\Argument\TimeSeries\MGetArguments; use Predis\Command\Argument\TimeSeries\MRangeArguments; use Predis\Command\Argument\TimeSeries\RangeArguments; use Predis\Command\CommandInterface; +use Predis\Command\Container\ACL; +use Predis\Command\Container\CLIENT; +use Predis\Command\Container\FUNCTIONS; +use Predis\Command\Container\Json\JSONDEBUG; +use Predis\Command\Container\Search\FTCONFIG; +use Predis\Command\Container\Search\FTCURSOR; use Predis\Command\FactoryInterface; -use Predis\Command\Redis\Container\ACL; -use Predis\Command\Redis\Container\FunctionContainer; -use Predis\Command\Redis\Container\Json\JSONDEBUG; -use Predis\Command\Redis\Container\Search\FTCONFIG; -use Predis\Command\Redis\Container\Search\FTCURSOR; use Predis\Configuration\OptionsInterface; use Predis\Connection\ConnectionInterface; use Predis\Response\Status; @@ -67,8 +68,8 @@ use Predis\Response\Status; * @method int move(string $key, int $db) * @method mixed object($subcommand, string $key) * @method int persist(string $key) - * @method int pexpire(string $key, int $milliseconds) - * @method int pexpireat(string $key, int $timestamp) + * @method int pexpire(string $key, int $milliseconds, string $option = null) + * @method int pexpireat(string $key, int $timestamp, string $option = null) * @method int pttl(string $key) * @method string|null randomkey() * @method mixed rename(string $key, string $target) @@ -338,21 +339,18 @@ use Predis\Response\Status; * @method mixed select(int $database) * @method mixed bgrewriteaof() * @method mixed bgsave() - * @method mixed client($subcommand, ...$argument = null) * @method mixed config($subcommand, $argument = null) * @method int dbsize() * @method mixed flushall() * @method mixed flushdb() - * @method array info($section = null) + * @method array info(string ...$section = null) * @method int lastsave() * @method mixed save() * @method mixed slaveof(string $host, int $port) * @method mixed slowlog($subcommand, $argument = null) * @method int spublish(string $shardChannel, string $message) - * @method array ssubscribe(string ...$shardChannels) - * @method array sunsubscribe(string ...$shardChannels) * @method array time() - * @method array command() + * @method array command($subcommand, $argument = null) * @method int geoadd(string $key, $longitude, $latitude, $member) * @method array geohash(string $key, array $members) * @method array geopos(string $key, array $members) @@ -363,11 +361,12 @@ use Predis\Response\Status; * @method int geosearchstore(string $destination, string $source, FromInterface $from, ByInterface $by, ?string $sorting = null, int $count = -1, bool $any = false, bool $storeDist = false) * * Container commands - * @property FunctionContainer $function - * @property FTCONFIG $ftconfig - * @property FTCURSOR $ftcursor - * @property JSONDEBUG $jsondebug - * @property ACL $acl + * @property CLIENT $client + * @property FUNCTIONS $function + * @property FTCONFIG $ftconfig + * @property FTCURSOR $ftcursor + * @property JSONDEBUG $jsondebug + * @property ACL $acl */ interface ClientInterface { diff --git a/src/Cluster/ClusterStrategy.php b/src/Cluster/ClusterStrategy.php index 7ff365f3..2791ea95 100644 --- a/src/Cluster/ClusterStrategy.php +++ b/src/Cluster/ClusterStrategy.php @@ -53,6 +53,7 @@ abstract class ClusterStrategy implements StrategyInterface 'SORT' => [$this, 'getKeyFromSortCommand'], 'DUMP' => $getKeyFromFirstArgument, 'RESTORE' => $getKeyFromFirstArgument, + 'FLUSHDB' => [$this, 'getFakeKey'], /* commands operating on string values */ 'APPEND' => $getKeyFromFirstArgument, @@ -163,6 +164,9 @@ abstract class ClusterStrategy implements StrategyInterface 'EVAL' => [$this, 'getKeyFromScriptingCommands'], 'EVALSHA' => [$this, 'getKeyFromScriptingCommands'], + /* server */ + 'INFO' => [$this, 'getFakeKey'], + /* commands performing geospatial operations */ 'GEOADD' => $getKeyFromFirstArgument, 'GEOHASH' => $getKeyFromFirstArgument, @@ -221,6 +225,16 @@ abstract class ClusterStrategy implements StrategyInterface $this->commands[$commandID] = $callback; } + /** + * Get fake key for commands with no key argument. + * + * @return string + */ + protected function getFakeKey(): string + { + return 'key'; + } + /** * Extracts the key from the first argument of a command instance. * diff --git a/src/Command/Container/AbstractContainer.php b/src/Command/Container/AbstractContainer.php index 950346ba..703a26ed 100644 --- a/src/Command/Container/AbstractContainer.php +++ b/src/Command/Container/AbstractContainer.php @@ -29,7 +29,7 @@ abstract class AbstractContainer implements ContainerInterface /** * {@inheritDoc} */ - public function __call($subcommandID, $arguments) + public function __call(string $subcommandID, array $arguments) { array_unshift($arguments, strtoupper($subcommandID)); diff --git a/src/Command/Container/CLIENT.php b/src/Command/Container/CLIENT.php new file mode 100644 index 00000000..46acbfd1 --- /dev/null +++ b/src/Command/Container/CLIENT.php @@ -0,0 +1,31 @@ +setListArguments($arguments); + break; + case 'NOTOUCH': + $arguments[0] = 'NO-TOUCH'; + $this->setNoTouchArguments($arguments); + break; + case 'SETINFO': + $this->setSetInfoArguments($arguments); + break; + default: + parent::setArguments($arguments); + } + } + + private function setListArguments(array $arguments): void + { + $processedArguments = [$arguments[0]]; + + if (array_key_exists(1, $arguments) && null !== $arguments[1]) { + array_push($processedArguments, 'TYPE', strtoupper($arguments[1])); + } + + if (array_key_exists(2, $arguments)) { + array_push($processedArguments, 'ID', $arguments[2]); + } + + if (count($arguments) > 3) { + for ($i = 3, $iMax = count($arguments); $i < $iMax; $i++) { + $processedArguments[] = $arguments[$i]; + } + } + + parent::setArguments($processedArguments); + } + + private function setNoTouchArguments(array $arguments): void + { + $processedArguments = [$arguments[0]]; + + if (array_key_exists(1, $arguments) && null !== $arguments[1]) { + $modifier = ($arguments[1]) ? 'ON' : 'OFF'; + $processedArguments[] = $modifier; + } + + parent::setArguments($processedArguments); + } + + private function setSetInfoArguments(array $arguments): void + { + $processedArguments = [$arguments[0]]; + + if ( + array_key_exists(1, $arguments) + && null !== $arguments[1] + && array_key_exists(2, $arguments) + && null !== $arguments[2] + ) { + array_push($processedArguments, strtoupper($arguments[1]), $arguments[2]); + } + + parent::setArguments($processedArguments); + } + /** * {@inheritdoc} */ diff --git a/src/Command/Redis/FUNCTIONS.php b/src/Command/Redis/FUNCTIONS.php index 69465901..7f4fde79 100644 --- a/src/Command/Redis/FUNCTIONS.php +++ b/src/Command/Redis/FUNCTIONS.php @@ -41,7 +41,7 @@ class FUNCTIONS extends RedisCommand public function setArguments(array $arguments) { - $strategy = $this->strategyResolver->resolve('functions', $arguments[0]); + $strategy = $this->strategyResolver->resolve('functions', strtolower($arguments[0])); $arguments = $strategy->processArguments($arguments); parent::setArguments($arguments); diff --git a/src/Command/Strategy/ContainerCommands/Functions/DumpStrategy.php b/src/Command/Strategy/ContainerCommands/Functions/DumpStrategy.php new file mode 100644 index 00000000..13f49ab0 --- /dev/null +++ b/src/Command/Strategy/ContainerCommands/Functions/DumpStrategy.php @@ -0,0 +1,26 @@ +separator = $separator; + } + /** * {@inheritDoc} */ public function resolve(string $commandId, string $subcommandId): SubcommandStrategyInterface { - $subcommandStrategyClass = ucfirst(strtolower($subcommandId)) . 'Strategy'; - $commandDirectoryName = ucfirst(strtolower($commandId)); + $subcommandStrategyClass = ucwords($subcommandId) . 'Strategy'; + $commandDirectoryName = ucwords($commandId); + + if (!is_null($this->separator)) { + $subcommandStrategyClass = str_replace($this->separator, '', $subcommandStrategyClass); + $commandDirectoryName = str_replace($this->separator, '', $commandDirectoryName); + } if (class_exists( $containerCommandClass = self::CONTAINER_COMMANDS_NAMESPACE . '\\' . $commandDirectoryName . '\\' . $subcommandStrategyClass diff --git a/tests/PHPUnit/PredisCommandTestCase.php b/tests/PHPUnit/PredisCommandTestCase.php index ddf41632..7080173f 100644 --- a/tests/PHPUnit/PredisCommandTestCase.php +++ b/tests/PHPUnit/PredisCommandTestCase.php @@ -61,7 +61,11 @@ abstract class PredisCommandTestCase extends PredisTestCase ); } - $client = $this->createClient(null, null, $flushdb); + if ($this->isClusterTest()) { + $client = $this->createClient(null, ['cluster' => 'redis'], $flushdb); + } else { + $client = $this->createClient(null, null, $flushdb); + } return $client; } diff --git a/tests/PHPUnit/PredisTestCase.php b/tests/PHPUnit/PredisTestCase.php index 1287a694..4ad148c1 100644 --- a/tests/PHPUnit/PredisTestCase.php +++ b/tests/PHPUnit/PredisTestCase.php @@ -168,6 +168,10 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase */ protected function getDefaultParametersArray(): array { + if ($this->isClusterTest()) { + return $this->prepareClusterEndpoints(); + } + return [ 'scheme' => 'tcp', 'host' => constant('REDIS_SERVER_HOST'), @@ -252,6 +256,15 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase getenv('USE_RELAY') ? ['connections' => 'relay'] : [] ); + if ($this->isClusterTest()) { + $options = array_merge( + [ + 'cluster' => 'redis', + ], + $options + ); + } + $client = new Client($parameters, $options); $client->connect(); @@ -552,4 +565,36 @@ abstract class PredisTestCase extends \PHPUnit\Framework\TestCase $this->markTestSkipped($message); } } + + /** + * Check annotations if it's matches to cluster test scenario. + * + * @return bool + */ + protected function isClusterTest(): bool + { + $annotations = TestUtil::parseTestMethodAnnotations( + get_class($this), + $this->getName(false) + ); + + return isset($annotations['method']['requiresRedisVersion'], $annotations['method']['group']) + && !empty($annotations['method']['requiresRedisVersion']) + && in_array('connected', $annotations['method']['group'], true) + && in_array('cluster', $annotations['method']['group'], true); + } + + /** + * Parse comma-separated cluster endpoints and convert them into tcp strings. + * + * @return array + */ + protected function prepareClusterEndpoints(): array + { + $endpoints = explode(',', constant('REDIS_CLUSTER_ENDPOINTS')); + + return array_map(static function (string $elem) { + return 'tcp://' . $elem; + }, $endpoints); + } } diff --git a/tests/Predis/Cluster/PredisStrategyTest.php b/tests/Predis/Cluster/PredisStrategyTest.php index ed63ab51..82a74b7c 100644 --- a/tests/Predis/Cluster/PredisStrategyTest.php +++ b/tests/Predis/Cluster/PredisStrategyTest.php @@ -106,6 +106,21 @@ class PredisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testFakeKeyCommandsWithOneKey(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = []; + + foreach ($this->getExpectedCommands('keys-fake') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + } + /** * @group disconnected */ @@ -342,6 +357,7 @@ class PredisStrategyTest extends PredisTestCase 'SORT' => 'variable', 'DUMP' => 'keys-first', 'RESTORE' => 'keys-first', + 'FLUSHDB' => 'keys-fake', /* commands operating on string values */ 'APPEND' => 'keys-first', @@ -452,6 +468,9 @@ class PredisStrategyTest extends PredisTestCase 'EVAL' => 'keys-script', 'EVALSHA' => 'keys-script', + /* server */ + 'INFO' => 'keys-fake', + /* commands performing geospatial operations */ 'GEOADD' => 'keys-first', 'GEOHASH' => 'keys-first', diff --git a/tests/Predis/Cluster/RedisStrategyTest.php b/tests/Predis/Cluster/RedisStrategyTest.php index b67b8cc4..b69eab5f 100644 --- a/tests/Predis/Cluster/RedisStrategyTest.php +++ b/tests/Predis/Cluster/RedisStrategyTest.php @@ -134,6 +134,21 @@ class RedisStrategyTest extends PredisTestCase } } + /** + * @group disconnected + */ + public function testFakeKeyCommandsWithOneKey(): void + { + $strategy = $this->getClusterStrategy(); + $commands = $this->getCommandFactory(); + $arguments = []; + + foreach ($this->getExpectedCommands('keys-fake') as $commandID) { + $command = $commands->create($commandID, $arguments); + $this->assertNotNull($strategy->getSlot($command), $commandID); + } + } + /** * @group disconnected */ @@ -365,6 +380,7 @@ class RedisStrategyTest extends PredisTestCase 'SORT' => 'keys-first', // TODO 'DUMP' => 'keys-first', 'RESTORE' => 'keys-first', + 'FLUSHDB' => 'keys-fake', /* commands operating on string values */ 'APPEND' => 'keys-first', @@ -475,6 +491,9 @@ class RedisStrategyTest extends PredisTestCase 'EVAL' => 'keys-script', 'EVALSHA' => 'keys-script', + /* server */ + 'INFO' => 'keys-fake', + /* commands performing geospatial operations */ 'GEOADD' => 'keys-first', 'GEOHASH' => 'keys-first', diff --git a/tests/Predis/Command/Redis/CLIENT_Test.php b/tests/Predis/Command/Redis/CLIENT_Test.php index 0fc13d6a..ec84ff8b 100644 --- a/tests/Predis/Command/Redis/CLIENT_Test.php +++ b/tests/Predis/Command/Redis/CLIENT_Test.php @@ -39,8 +39,8 @@ class CLIENT_Test extends PredisCommandTestCase */ public function testFilterArgumentsOfClientKill(): void { - $arguments = ['kill', '127.0.0.1:45393']; - $expected = ['kill', '127.0.0.1:45393']; + $arguments = ['KILL', '127.0.0.1:45393']; + $expected = ['KILL', '127.0.0.1:45393']; $command = $this->getCommand(); $command->setArguments($arguments); @@ -49,17 +49,15 @@ class CLIENT_Test extends PredisCommandTestCase } /** + * @dataProvider listArgumentsProvider * @group disconnected */ - public function testFilterArgumentsOfClientList(): void + public function testFilterArgumentsOfClientList(array $actualArguments, array $expectedArguments): void { - $arguments = ['list']; - $expected = ['list']; - $command = $this->getCommand(); - $command->setArguments($arguments); + $command->setArguments($actualArguments); - $this->assertSame($expected, $command->getArguments()); + $this->assertSame($expectedArguments, $command->getArguments()); } /** @@ -67,7 +65,7 @@ class CLIENT_Test extends PredisCommandTestCase */ public function testFilterArgumentsOfClientGetname(): void { - $arguments = $expected = ['getname']; + $arguments = $expected = ['GETNAME']; $command = $this->getCommand(); $command->setArguments($arguments); @@ -80,7 +78,7 @@ class CLIENT_Test extends PredisCommandTestCase */ public function testFilterArgumentsOfClientSetname(): void { - $arguments = $expected = ['setname', 'connection-a']; + $arguments = $expected = ['SETNAME', 'connection-a']; $command = $this->getCommand(); $command->setArguments($arguments); @@ -88,6 +86,30 @@ class CLIENT_Test extends PredisCommandTestCase $this->assertSame($expected, $command->getArguments()); } + /** + * @dataProvider noTouchArgumentsProvider + * @group disconnected + */ + public function testFilterArgumentsNoTouch(array $actualArguments, array $expectedArguments): void + { + $command = $this->getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + + /** + * @dataProvider setInfoArgumentsProvider + * @group disconnected + */ + public function testFilterArgumentsSetInfo(array $actualArguments, array $expectedArguments): void + { + $command = $this->getCommand(); + $command->setArguments($actualArguments); + + $this->assertSame($expectedArguments, $command->getArguments()); + } + /** * @group disconnected */ @@ -131,7 +153,7 @@ BUFFER; { $redis = $this->getClient(); - $this->assertIsArray($clients = $redis->client('LIST')); + $this->assertIsArray($clients = $redis->client->list()); $this->assertGreaterThanOrEqual(1, count($clients)); $this->assertIsArray($clients[0]); $this->assertArrayHasKey('addr', $clients[0]); @@ -171,12 +193,12 @@ BUFFER; public function testGetsNameOfConnection(): void { $redis = $this->getClient(); - $clientName = $redis->client('GETNAME'); + $clientName = $redis->client->getName(); $this->assertNull($clientName); $expectedConnectionName = 'foo-bar'; - $this->assertEquals('OK', $redis->client('SETNAME', $expectedConnectionName)); - $this->assertEquals($expectedConnectionName, $redis->client('GETNAME')); + $this->assertEquals('OK', $redis->client->setName($expectedConnectionName)); + $this->assertEquals($expectedConnectionName, $redis->client->getName()); } /** @@ -203,8 +225,33 @@ BUFFER; $redis = $this->getClient(); $expectedConnectionName = 'foo-baz'; - $this->assertEquals('OK', $redis->client('SETNAME', $expectedConnectionName)); - $this->assertEquals($expectedConnectionName, $redis->client('GETNAME')); + $this->assertEquals('OK', $redis->client->setName($expectedConnectionName)); + $this->assertEquals($expectedConnectionName, $redis->client->getName()); + } + + /** + * @group connected + * @requiresRedisVersion >= 7.2.0 + */ + public function testNoTouchTurnOnControlOnKeys(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->client->noTouch(true)); + } + + /** + * @group connected + * @requiresRedisVersion >= 7.2.0 + */ + public function testSetInfoToCurrentClientConnection(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->client->setInfo('LIB-NAME', 'lib')); + $this->assertEquals('OK', $redis->client->setInfo('LIB-VER', '1.0.0')); + $this->assertSame('lib', $redis->client->list()[0]['lib-name']); + $this->assertSame('1.0.0', $redis->client->list()[0]['lib-ver']); } /** @@ -231,20 +278,7 @@ BUFFER; $this->expectException('Predis\Response\ServerException'); $redis = $this->getClient(); - $redis->client('SETNAME', $invalidConnectionName); - } - - /** - * @group connected - * @requiresRedisVersion >= 2.4.0 - */ - public function testThrowsExceptioOnWrongModifier(): void - { - $this->expectException('Predis\Response\ServerException'); - - $redis = $this->getClient(); - - $redis->client('FOO'); + $redis->client->setName($invalidConnectionName); } /** @@ -258,6 +292,64 @@ BUFFER; $redis = $this->getClient(); - $redis->client('KILL', '127.0.0.1:65535'); + $redis->client->kill('127.0.0.1:65535'); + } + + public function listArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['LIST'], + ['LIST'], + ], + 'with TYPE modifier' => [ + ['LIST', 'MASTER'], + ['LIST', 'TYPE', 'MASTER'], + ], + 'with ID modifier' => [ + ['LIST', null, 1, 2, 3], + ['LIST', 'ID', 1, 2, 3], + ], + ]; + } + + public function noTouchArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['NOTOUCH'], + ['NO-TOUCH'], + ], + 'with enabled modifier' => [ + ['NOTOUCH', true], + ['NO-TOUCH', 'ON'], + ], + 'with disabled modifier' => [ + ['NOTOUCH', false], + ['NO-TOUCH', 'OFF'], + ], + ]; + } + + public function setInfoArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['SETINFO'], + ['SETINFO'], + ], + 'with LIB-NAME modifier' => [ + ['SETINFO', 'LIB-NAME', 'lib'], + ['SETINFO', 'LIB-NAME', 'lib'], + ], + 'with LIB-VER modifier' => [ + ['SETINFO', 'LIB-VER', '1.0.0'], + ['SETINFO', 'LIB-VER', '1.0.0'], + ], + 'with only modifier given' => [ + ['SETINFO', 'LIB-VER'], + ['SETINFO'], + ], + ]; } } diff --git a/tests/Predis/Command/Redis/FUNCTIONS_Test.php b/tests/Predis/Command/Redis/FUNCTIONS_Test.php index ad79f0ef..b13fd80e 100644 --- a/tests/Predis/Command/Redis/FUNCTIONS_Test.php +++ b/tests/Predis/Command/Redis/FUNCTIONS_Test.php @@ -70,6 +70,84 @@ class FUNCTIONS_Test extends PredisCommandTestCase $this->assertSameValues($expected, $command->getArguments()); } + /** + * @group disconnected + */ + public function testDumpFilterArguments(): void + { + $arguments = ['DUMP']; + $expected = ['DUMP']; + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSameValues($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testKillFilterArguments(): void + { + $arguments = ['KILL']; + $expected = ['KILL']; + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSameValues($expected, $command->getArguments()); + } + + /** + * @group disconnected + */ + public function testStatsFilterArguments(): void + { + $arguments = ['STATS']; + $expected = ['STATS']; + + $command = $this->getCommand(); + $command->setArguments($arguments); + + $this->assertSameValues($expected, $command->getArguments()); + } + + /** + * @dataProvider flushArgumentsProvider + * @group disconnected + */ + public function testFlushFilterArguments(array $actualArguments, array $expectedResponse): void + { + $command = $this->getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedResponse, $command->getArguments()); + } + + /** + * @dataProvider restoreArgumentsProvider + * @group disconnected + */ + public function testRestoreFilterArguments(array $actualArguments, array $expectedResponse): void + { + $command = $this->getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedResponse, $command->getArguments()); + } + + /** + * @dataProvider listArgumentsProvider + * @group disconnected + */ + public function testListFilterArguments(array $actualArguments, array $expectedResponse): void + { + $command = $this->getCommand(); + $command->setArguments($actualArguments); + + $this->assertSameValues($expectedResponse, $command->getArguments()); + } + /** * @group disconnected */ @@ -86,7 +164,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionAddFunctionIntoGivenLibrary(): void { $redis = $this->getClient(); - $redis->executeRaw(['FUNCTION', 'FLUSH']); + $redis->function->flush(); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -123,7 +201,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionOverridesExistingFunctionWithReplaceArgumentGiven(): void { $redis = $this->getClient(); - $redis->executeRaw(['FUNCTION', 'FLUSH']); + $redis->function->flush(); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -150,7 +228,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testLoadFunctionThrowsErrorOnAlreadyExistingLibraryGiven(): void { $redis = $this->getClient(); - $redis->executeRaw(['FUNCTION', 'FLUSH']); + $redis->function->flush(); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -178,7 +256,7 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testDeleteFunctionRemovesAlreadyExistingLibrary(): void { $redis = $this->getClient(); - $redis->executeRaw(['FUNCTION', 'FLUSH']); + $redis->function->flush(); $actualResponse = $redis->function->load( "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" @@ -188,6 +266,112 @@ class FUNCTIONS_Test extends PredisCommandTestCase $this->assertEquals('OK', $redis->function->delete($this->libName)); } + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testDumpReturnsSerializedPayloadOfLibrary(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + + $libName = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertSame($this->libName, $libName); + $this->assertStringContainsString($libName, $redis->function->dump()); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testFlushRemovesAllLibraries(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + + $libName = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertEquals($this->libName, $libName); + $this->assertEquals('OK', $redis->function->flush()); + } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testRestoresLibraryFromSerializedPayload(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + + $libName = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + $this->assertEquals($this->libName, $libName); + + $serializedPayload = $redis->function->dump(); + $this->assertStringContainsString($libName, $serializedPayload); + + $redis->function->flush(); + + $this->assertEquals('OK', $redis->function->restore($serializedPayload)); + } + + /** + * @group connected + * @group relay-incompatible + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testListReturnsListOfAvailableFunctions(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + $expectedResponse = [ + [ + 'library_name', 'mylib', 'engine', 'LUA', 'functions', + [ + ['name', 'myfunc', 'description', null, 'flags', []], + ], + ], + ]; + + $libName = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertEquals($this->libName, $libName); + $this->assertSame($expectedResponse, $redis->function->list()); + } + + /** + * @group connected + * @group relay-incompatible + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testStatsReturnsInformationAboutRunningScript(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + $expectedResponse = ['running_script', null, 'engines', ['LUA', ['libraries_count', 1, 'functions_count', 1]]]; + + $libName = $redis->function->load( + "#!lua name={$this->libName} \n redis.register_function('myfunc', function(keys, args) return args[1] end)" + ); + + $this->assertEquals($this->libName, $libName); + $this->assertSame($expectedResponse, $redis->function->stats()); + } + /** * @group connected * @return void @@ -196,11 +380,77 @@ class FUNCTIONS_Test extends PredisCommandTestCase public function testDeleteFunctionThrowsErrorOnNonExistingLibrary(): void { $redis = $this->getClient(); - $redis->executeRaw(['FUNCTION', 'FLUSH']); + $redis->function->flush(); $this->expectException(ServerException::class); $this->expectExceptionMessage('ERR Library not found'); $redis->function->delete($this->libName); } + + /** + * @group connected + * @return void + * @requiresRedisVersion >= 7.0.0 + */ + public function testKillThrowsExceptionOnNonExistingRunningScript(): void + { + $redis = $this->getClient(); + $redis->function->flush(); + + $this->expectException(ServerException::class); + $this->expectExceptionMessage('NOTBUSY No scripts in execution right now.'); + + $redis->function->kill(); + } + + public function flushArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['FLUSH', null], + ['FLUSH'], + ], + 'with mode argument' => [ + ['FLUSH', 'sync'], + ['FLUSH', 'SYNC'], + ], + ]; + } + + public function restoreArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['RESTORE', 'value', null], + ['RESTORE', 'value'], + ], + 'with mode argument' => [ + ['RESTORE', 'value', 'append'], + ['RESTORE', 'value', 'APPEND'], + ], + ]; + } + + public function listArgumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['LIST', null, false], + ['LIST'], + ], + 'with LIBRARYNAME modifier' => [ + ['LIST', 'libraryname', false], + ['LIST', 'LIBRARYNAME', 'libraryname'], + ], + 'with WITHCODE modifier' => [ + ['LIST', null, true], + ['LIST', 'WITHCODE'], + ], + 'with all arguments' => [ + ['LIST', 'libraryname', true], + ['LIST', 'LIBRARYNAME', 'libraryname', 'WITHCODE'], + ], + ]; + } } diff --git a/tests/Predis/Command/Redis/SET_Test.php b/tests/Predis/Command/Redis/SET_Test.php index f82da5a4..61d470d5 100644 --- a/tests/Predis/Command/Redis/SET_Test.php +++ b/tests/Predis/Command/Redis/SET_Test.php @@ -166,4 +166,17 @@ class SET_Test extends PredisCommandTestCase $this->assertEquals('OK', $redis->set('foo', 'barbar', 'XX')); $this->assertNull($redis->set('foofoo', 'barbar', 'XX')); } + + /** + * @group connected + * @group cluster + * @requiresRedisVersion >= 3.0.0 + * @return void + */ + public function testSetStringValueInClusterMode(): void + { + $redis = $this->getClient(); + + $this->assertEquals('OK', $redis->set('foo', 'bar')); + } } diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/DumpStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/DumpStrategyTest.php new file mode 100644 index 00000000..20b8fb96 --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/DumpStrategyTest.php @@ -0,0 +1,37 @@ +strategy = new DumpStrategy(); + } + + /** + * @group disconnected + * @return void + */ + public function testProcessArguments(): void + { + $this->assertSame(['arg1', 'arg2'], $this->strategy->processArguments(['arg1', 'arg2'])); + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/FlushStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/FlushStrategyTest.php new file mode 100644 index 00000000..d5d8ba46 --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/FlushStrategyTest.php @@ -0,0 +1,54 @@ +strategy = new FlushStrategy(); + } + + /** + * @dataProvider argumentsProvider + * @group disconnected + * @param array $actualArguments + * @param array $expectedResponse + * @return void + */ + public function testProcessArguments(array $actualArguments, array $expectedResponse): void + { + $this->assertSame($expectedResponse, $this->strategy->processArguments($actualArguments)); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['FLUSH', null], + ['FLUSH'], + ], + 'with mode argument' => [ + ['FLUSH', 'sync'], + ['FLUSH', 'SYNC'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/KillStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/KillStrategyTest.php new file mode 100644 index 00000000..96da8c5f --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/KillStrategyTest.php @@ -0,0 +1,37 @@ +strategy = new KillStrategy(); + } + + /** + * @group disconnected + * @return void + */ + public function testProcessArguments(): void + { + $this->assertSame(['arg1', 'arg2'], $this->strategy->processArguments(['arg1', 'arg2'])); + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/ListStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/ListStrategyTest.php new file mode 100644 index 00000000..8e30c129 --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/ListStrategyTest.php @@ -0,0 +1,62 @@ +strategy = new ListStrategy(); + } + + /** + * @dataProvider argumentsProvider + * @group disconnected + * @param array $actualArguments + * @param array $expectedResponse + * @return void + */ + public function testProcessArguments(array $actualArguments, array $expectedResponse): void + { + $this->assertSame($expectedResponse, $this->strategy->processArguments($actualArguments)); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['LIST', null, false], + ['LIST'], + ], + 'with LIBRARYNAME modifier' => [ + ['LIST', 'libraryname', false], + ['LIST', 'LIBRARYNAME', 'libraryname'], + ], + 'with WITHCODE modifier' => [ + ['LIST', null, true], + ['LIST', 'WITHCODE'], + ], + 'with all arguments' => [ + ['LIST', 'libraryname', true], + ['LIST', 'LIBRARYNAME', 'libraryname', 'WITHCODE'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/RestoreStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/RestoreStrategyTest.php new file mode 100644 index 00000000..839f14e8 --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/RestoreStrategyTest.php @@ -0,0 +1,54 @@ +strategy = new RestoreStrategy(); + } + + /** + * @dataProvider argumentsProvider + * @group disconnected + * @param array $actualArguments + * @param array $expectedResponse + * @return void + */ + public function testProcessArguments(array $actualArguments, array $expectedResponse): void + { + $this->assertSame($expectedResponse, $this->strategy->processArguments($actualArguments)); + } + + public function argumentsProvider(): array + { + return [ + 'with default arguments' => [ + ['RESTORE', 'value', null], + ['RESTORE', 'value'], + ], + 'with mode argument' => [ + ['RESTORE', 'value', 'append'], + ['RESTORE', 'value', 'APPEND'], + ], + ]; + } +} diff --git a/tests/Predis/Command/Strategy/ContainerCommands/Functions/StatsStrategyTest.php b/tests/Predis/Command/Strategy/ContainerCommands/Functions/StatsStrategyTest.php new file mode 100644 index 00000000..6d68539b --- /dev/null +++ b/tests/Predis/Command/Strategy/ContainerCommands/Functions/StatsStrategyTest.php @@ -0,0 +1,37 @@ +strategy = new StatsStrategy(); + } + + /** + * @group disconnected + * @return void + */ + public function testProcessArguments(): void + { + $this->assertSame(['arg1', 'arg2'], $this->strategy->processArguments(['arg1', 'arg2'])); + } +} diff --git a/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php b/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php index 3532809c..e4497d49 100644 --- a/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php +++ b/tests/Predis/Command/Strategy/SubcommandStrategyResolverTest.php @@ -19,23 +19,27 @@ use Predis\Command\Strategy\ContainerCommands\Functions\LoadStrategy; class SubcommandStrategyResolverTest extends TestCase { /** - * @var StrategyResolverInterface - */ - private $resolver; - - protected function setUp(): void - { - $this->resolver = new SubcommandStrategyResolver(); - } - - /** + * @group disconnected * @return void */ public function testResolveCorrectStrategy(): void { + $resolver = new SubcommandStrategyResolver(); $expectedStrategy = new LoadStrategy(); - $this->assertEquals($expectedStrategy, $this->resolver->resolve('functions', 'load')); + $this->assertEquals($expectedStrategy, $resolver->resolve('functions', 'load')); + } + + /** + * @group disconnected + * @return void + */ + public function testResolveCorrectlyResolvesStrategyWithGivenWordSeparator(): void + { + $resolver = new SubcommandStrategyResolver('_'); + $expectedStrategy = new LoadStrategy(); + + $this->assertEquals($expectedStrategy, $resolver->resolve('functions_', 'load_')); } /** @@ -43,9 +47,11 @@ class SubcommandStrategyResolverTest extends TestCase */ public function testResolveThrowsExceptionOnNonExistingStrategy(): void { + $resolver = new SubcommandStrategyResolver(); + $this->expectException(InvalidArgumentException::class); $this->expectExceptionMessage('Non-existing container command given'); - $this->resolver->resolve('foo', 'bar'); + $resolver->resolve('foo', 'bar'); } }