diff --git a/README.md b/README.md index 74d63e5..033c5c0 100644 --- a/README.md +++ b/README.md @@ -318,9 +318,9 @@ Visual Studio 2019 (Win 10) If you build *redis-plus-plus* with `-DREDIS_PLUS_PLUS_BUILD_TEST=ON` (the default behavior, and you can disable building test with `-DREDIS_PLUS_PLUS_BUILD_TEST=OFF`), you'll get a test program in *build/test* directory: *build/test/test_redis++*. -In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version (by now, it's 5.0). Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. +In order to run the tests, you need to set up a Redis instance, and a Redis Cluster. Since the test program will send most of Redis commands to the server and cluster, you need to set up Redis of the latest version. Otherwise, the tests might fail. For example, if you set up Redis 4.0 for testing, the test program will fail when it tries to send the `ZPOPMAX` command (a Redis 5.0 command) to the server. If you want to run the tests with other Redis versions, you have to comment out commands that haven't been supported by your Redis, from test source files in *redis-plus-plus/test/src/sw/redis++/* directory. Sorry for the inconvenience, and I'll fix this problem to make the test program work with any version of Redis in the future. -**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, e.g. Redis 2.0, Redis 3.0, Redis 4.0, Redis 5.0. +**NOTE**: The latest version of Redis is only a requirement for running the tests. In fact, you can use *redis-plus-plus* with Redis of any version, i.e. Redis 2.0 and above. **NEVER** run the test program in production envronment, since the keys, which the test program reads or writes, might conflict with your application. diff --git a/src/sw/redis++/async_redis.h b/src/sw/redis++/async_redis.h index 8621f49..e53f385 100644 --- a/src/sw/redis++/async_redis.h +++ b/src/sw/redis++/async_redis.h @@ -811,6 +811,60 @@ class AsyncRedis { return rpush(key, il.begin(), il.end(), std::forward(cb)); } + template + Future>> lmpop(Input first, Input last, ListWhence whence, long long count = 1) { + range_check("LMPOP", first, last); + + return _command>>(fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, long long count, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + range_check("LMPOP", first, last); + + _callback_fmt_command>>(std::forward(cb), + fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + return lmpop(first, last, whence, 1, std::forward(cb)); + } + + Future lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + return _command(fmt::lmove, src, dest, src_whence, dest_whence); + } + + template + void lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::lmove, src, dest, src_whence, dest_whence); + } + + Future blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return _command(fmt::blmove, src, dest, src_whence, dest_whence, timeout.count()); + } + + template + void blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::blmove, src, dest, + src_whence, dest_whence, timeout.count()); + } + + template + auto blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) + -> typename std::enable_if::type, Future &&>::value, void>::type { + blmove(src, dest, src_whence, dest_whence, std::chrono::seconds{0}, std::forward(cb)); + } + // HASH commands. Future hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/async_redis_cluster.h b/src/sw/redis++/async_redis_cluster.h index 8c2a41f..285aaa8 100644 --- a/src/sw/redis++/async_redis_cluster.h +++ b/src/sw/redis++/async_redis_cluster.h @@ -493,6 +493,60 @@ class AsyncRedisCluster { return rpush(key, il.begin(), il.end()); } + template + Future>> lmpop(Input first, Input last, ListWhence whence, long long count = 1) { + range_check("LMPOP", first, last); + + return _command>>(fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, long long count, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + range_check("LMPOP", first, last); + + _callback_fmt_command>>(std::forward(cb), + fmt::lmpop, first, last, whence, count); + } + + template + auto lmpop(Input first, Input last, ListWhence whence, Callback &&cb) + -> typename std::enable_if::type, Future>> &&>::value, void>::type { + return lmpop(first, last, whence, 1, std::forward(cb)); + } + + Future lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + return _command(fmt::lmove, src, dest, src_whence, dest_whence); + } + + template + void lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::lmove, src, dest, src_whence, dest_whence); + } + + Future blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}) { + return _command(fmt::blmove, src, dest, src_whence, dest_whence, timeout.count()); + } + + template + void blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout, Callback &&cb) { + _callback_fmt_command(std::forward(cb), fmt::blmove, src, dest, + src_whence, dest_whence, timeout.count()); + } + + template + auto blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, Callback &&cb) + -> typename std::enable_if::type, Future &&>::value, void>::type { + blmove(src, dest, src_whence, dest_whence, std::chrono::seconds{0}, std::forward(cb)); + } + // HASH commands. Future hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/cmd_formatter.h b/src/sw/redis++/cmd_formatter.h index 2467c86..01b69d7 100644 --- a/src/sw/redis++/cmd_formatter.h +++ b/src/sw/redis++/cmd_formatter.h @@ -377,6 +377,39 @@ FormattedCommand rpush_range(const StringView &key, Input first, Input last) { return format_cmd(args); } +template +FormattedCommand lmpop(Input first, Input last, ListWhence whence, long long count) { + assert(first != last); + + CmdArgs args; + + auto keys_num = std::distance(first, last); + + args << "LMPOP" << keys_num << std::make_pair(first, last) << to_string(whence) << "COUNT" << count; + + return format_cmd(args); +} + +inline FormattedCommand lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + return format_cmd("LMOVE %b %b %s %s", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data()); +} + +inline FormattedCommand blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, long long timeout) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + return format_cmd("BLMOVE %b %b %s %s %lld", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data(), timeout); +} + // HASH commands. inline FormattedCommand hdel(const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/command.cpp b/src/sw/redis++/command.cpp index 68baa15..f5fa017 100644 --- a/src/sw/redis++/command.cpp +++ b/src/sw/redis++/command.cpp @@ -107,7 +107,7 @@ void linsert(Connection &connection, break; default: - assert(false); + throw Error("unknown insert position"); } connection.send("LINSERT %b %s %b %b", diff --git a/src/sw/redis++/command.h b/src/sw/redis++/command.h index 5ee80e6..76e96fb 100644 --- a/src/sw/redis++/command.h +++ b/src/sw/redis++/command.h @@ -636,6 +636,39 @@ inline void rpushx(Connection &connection, const StringView &key, const StringVi val.data(), val.size()); } +template +inline void lmpop(Connection &connection, Input first, Input last, ListWhence whence, long long count) { + assert(first != last); + + CmdArgs args; + + auto keys_num = std::distance(first, last); + + args << "LMPOP" << keys_num << std::make_pair(first, last) << to_string(whence) << "COUNT" << count; + + connection.send(args); +} + +inline void lmove(Connection &connection, const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + connection.send("LMOVE %b %b %s %s", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data()); +} + +inline void blmove(Connection &connection, const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, long long timeout) { + auto src_whence_str = to_string(src_whence); + auto dest_whence_str = to_string(dest_whence); + connection.send("BLMOVE %b %b %s %s %lld", + src.data(), src.size(), + dest.data(), dest.size(), + src_whence_str.data(), dest_whence_str.data(), timeout); +} + // HASH commands. inline void hdel(Connection &connection, const StringView &key, const StringView &field) { diff --git a/src/sw/redis++/command_options.cpp b/src/sw/redis++/command_options.cpp index eda34d0..df236bd 100644 --- a/src/sw/redis++/command_options.cpp +++ b/src/sw/redis++/command_options.cpp @@ -184,6 +184,22 @@ const std::string& RightBoundedInterval::lower() const { return NEGATIVE_INFINITY_STRING; } +std::string to_string(ListWhence whence) { + std::string str; + switch (whence) { + case ListWhence::LEFT: + str = "LEFT"; + break; + case ListWhence::RIGHT: + str = "RIGHT"; + break; + default: + throw Error("unknown list whence"); + } + + return str; +} + } } diff --git a/src/sw/redis++/command_options.h b/src/sw/redis++/command_options.h index aa779fd..cc524be 100644 --- a/src/sw/redis++/command_options.h +++ b/src/sw/redis++/command_options.h @@ -35,6 +35,11 @@ enum class InsertPosition { AFTER }; +enum class ListWhence { + LEFT, + RIGHT +}; + enum class BoundType { CLOSED, OPEN, @@ -209,6 +214,8 @@ struct WithDist : TupleWithType {}; template struct WithHash : TupleWithType {}; +std::string to_string(ListWhence whence); + } } diff --git a/src/sw/redis++/redis.cpp b/src/sw/redis++/redis.cpp index 761103f..8496b67 100644 --- a/src/sw/redis++/redis.cpp +++ b/src/sw/redis++/redis.cpp @@ -515,6 +515,20 @@ long long Redis::rpushx(const StringView &key, const StringView &val) { return reply::parse(*reply); } +OptionalString Redis::lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto reply = command(cmd::lmove, src, dest, src_whence, dest_whence); + + return reply::parse(*reply); +} + +OptionalString Redis::blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, const std::chrono::seconds &timeout) { + auto reply = command(cmd::blmove, src, dest, src_whence, dest_whence, timeout.count()); + + return reply::parse(*reply); +} + long long Redis::hdel(const StringView &key, const StringView &field) { auto reply = command(cmd::hdel, key, field); diff --git a/src/sw/redis++/redis.h b/src/sw/redis++/redis.h index 15070db..9311e42 100644 --- a/src/sw/redis++/redis.h +++ b/src/sw/redis++/redis.h @@ -1314,6 +1314,81 @@ class Redis { /// @see https://redis.io/commands/rpushx long long rpushx(const StringView &key, const StringView &val); + /// @brief Pop one or more elements from the first non-empty list. + /// + /// Example: + /// @code{.cpp} + /// auto lists = {"l1", "l2"}; + /// auto val = redis.lmpop>(lists.begin(), lists.end(), ListWhence::LEFT, 2); + /// if (val) + /// std::cout << "list: " << val->first << ", size: " << val->second.size() << std::endl; + /// else + /// std::cout << "all lists are empty" << std::endl; + /// @endcode + /// @param first Iterator to the first list. + /// @param last Off-the-end iterator to the given list range. + /// @param whence ListWhence::LEFT or ListWhence::RIGHT. + /// @param count Number of elements to be popped. + /// @return Elements popped from list. + /// @note If key does not exist, `lmpop` returns `Optional>{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmpop + template + Optional> lmpop(Input first, Input last, ListWhence whence, long long count = 1); + + /// @brief Pop one or more elements from the first non-empty list. + /// @param il Initializer list of Redis lists. + /// @param pos ListWhence::LEFT or ListWhence::RIGHT. + /// @param count Number of elements to be popped. + /// @return Elements popped from list. + /// @note If key does not exist, `lmpop` returns `Optional>{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmpop + template + Optional> lmpop(std::initializer_list il, ListWhence pos, long long count = 1) { + return lmpop(il.begin(), il.end(), pos, count); + } + + /// @brief Move element from src list to dest list. + /// + /// Example: + /// @code{.cpp} + /// auto val = redis.lmove("src", "dest", ListWhence::LEFT, ListWhence::RIGHT); + /// if (val) + /// std::cout << "moved " << *val << " from src to dest" << std::endl; + /// else + /// std::cout << "src list does not exist" << std::endl; + /// @endcode + /// @param src Source list. + /// @param dest Destination list. + /// @param src_whence From where of the source list. + /// @param dest_whence To where of the dest list. + /// @return The moved element. + /// @note If source list does not exist, `lmove` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/lmove + OptionalString lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence); + + /// @brief The block version of lmove. + /// + /// Example: + /// @code{.cpp} + /// auto val = redis.blmove("src", "dest", ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seonds(2)); + /// if (val) + /// std::cout << "moved " << *val << " from src to dest" << std::endl; + /// else + /// std::cout << "src list does not exist" << std::endl; + /// @endcode + /// @param src Source list. + /// @param dest Destination list. + /// @param src_whence From where of the source list. + /// @param dest_whence To where of the dest list. + /// @param timeout Timeout in seconds. 0 means block forever. + /// @return The moved element. + /// @note If source list does not exist, `blmove` returns `OptionalString{}` (`std::nullopt`). + /// @see https://redis.io/commands/blmove + OptionalString blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + // HASH commands. /// @brief Remove the given field from hash. diff --git a/src/sw/redis++/redis.hpp b/src/sw/redis++/redis.hpp index 5d40f50..9714ff6 100644 --- a/src/sw/redis++/redis.hpp +++ b/src/sw/redis++/redis.hpp @@ -339,6 +339,16 @@ inline long long Redis::rpush(const StringView &key, Input first, Input last) { return reply::parse(*reply); } +template +Optional> Redis::lmpop(Input first, Input last, ListWhence whence, long long count) { + range_check("LMPOP", first, last); + + auto reply = command(cmd::lmpop, first, last, whence, count); + + return reply::parse>>(*reply); +} + + // HASH commands. template diff --git a/src/sw/redis++/redis_cluster.cpp b/src/sw/redis++/redis_cluster.cpp index bd1d3bd..7eebc1a 100644 --- a/src/sw/redis++/redis_cluster.cpp +++ b/src/sw/redis++/redis_cluster.cpp @@ -426,6 +426,20 @@ long long RedisCluster::rpushx(const StringView &key, const StringView &val) { return reply::parse(*reply); } +OptionalString RedisCluster::lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence) { + auto reply = command(cmd::lmove, src, dest, src_whence, dest_whence); + + return reply::parse(*reply); +} + +OptionalString RedisCluster::blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, const std::chrono::seconds &timeout) { + auto reply = command(cmd::blmove, src, dest, src_whence, dest_whence, timeout.count()); + + return reply::parse(*reply); +} + long long RedisCluster::hdel(const StringView &key, const StringView &field) { auto reply = command(cmd::hdel, key, field); diff --git a/src/sw/redis++/redis_cluster.h b/src/sw/redis++/redis_cluster.h index cef7665..644bf93 100644 --- a/src/sw/redis++/redis_cluster.h +++ b/src/sw/redis++/redis_cluster.h @@ -403,6 +403,21 @@ class RedisCluster { long long rpushx(const StringView &key, const StringView &val); + template + Optional> lmpop(Input first, Input last, ListWhence whence, long long count = 1); + + template + Optional> lmpop(std::initializer_list il, ListWhence whence, long long count = 1) { + return lmpop(il.begin(), il.end(), whence, count); + } + + OptionalString lmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence); + + OptionalString blmove(const StringView &src, const StringView &dest, + ListWhence src_whence, ListWhence dest_whence, + const std::chrono::seconds &timeout = std::chrono::seconds{0}); + // HASH commands. long long hdel(const StringView &key, const StringView &field); diff --git a/src/sw/redis++/redis_cluster.hpp b/src/sw/redis++/redis_cluster.hpp index c0e8188..cff89e1 100644 --- a/src/sw/redis++/redis_cluster.hpp +++ b/src/sw/redis++/redis_cluster.hpp @@ -310,6 +310,15 @@ inline long long RedisCluster::rpush(const StringView &key, Input first, Input l return reply::parse(*reply); } +template +Optional> RedisCluster::lmpop(Input first, Input last, ListWhence whence, long long count) { + range_check("LMPOP", first, last); + + auto reply = command(cmd::lmpop, first, last, whence, count); + + return reply::parse>>(*reply); +} + // HASH commands. template diff --git a/src/sw/redis++/reply.h b/src/sw/redis++/reply.h index fe4dafe..32ea9a9 100644 --- a/src/sw/redis++/reply.h +++ b/src/sw/redis++/reply.h @@ -184,6 +184,9 @@ std::string to_status(redisReply &reply); template void to_array(redisReply &reply, Output output); +template +void to_optional_array(redisReply &reply, Output output); + // Parse set reply to bool type bool parse_set_reply(redisReply &reply); @@ -494,6 +497,15 @@ void to_array(redisReply &reply, Output output) { detail::to_array(typename IsKvPairIter::type(), reply, output); } +template +void to_optional_array(redisReply &reply, Output output) { + if (is_nil(reply)) { + return; + } + + to_array(reply, output); +} + template auto parse_xpending_reply(redisReply &reply, Output output) -> std::tuple { diff --git a/test/src/sw/redis++/async_test.h b/test/src/sw/redis++/async_test.h index 2932a9f..7d2bbb7 100644 --- a/test/src/sw/redis++/async_test.h +++ b/test/src/sw/redis++/async_test.h @@ -33,6 +33,16 @@ namespace redis { namespace test { +template <> +inline void delete_keys(AsyncRedis &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()).get(); +} + +template <> +inline void delete_keys(AsyncRedisCluster &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()).get(); +} + template class AsyncTest { public: @@ -47,6 +57,8 @@ class AsyncTest { private: void _test_str(); + void _test_list(); + void _test_hash(); void _test_set(); @@ -73,6 +85,8 @@ template void AsyncTest::run() { _test_str(); + _test_list(); + _test_hash(); _test_set(); @@ -150,6 +164,57 @@ void AsyncTest::_test_str() { _wait(); } +template +void AsyncTest::_test_list() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + auto num = _redis.lpush(src, {"a", "b", "c"}).get(); + REDIS_ASSERT(num == 3, "failed to test async list: lpush"); + + num = _redis.lpush(dest, {"e", "f", "g"}).get(); + REDIS_ASSERT(num == 3, "failed to test async list: lpush"); + + auto val = _redis.lmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT).get(); + REDIS_ASSERT(val && *val == "c", "failed to test async list: lmove"); + + set_ready(false); + _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT, + [this](Future &&fut) { + auto val = fut.get(); + REDIS_ASSERT(val && *val == "b", "failed to test async list: blmove"); + + this->set_ready(); + }); + _wait(); + + set_ready(false); + _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seconds{1}, + [this](Future &&fut) { + auto val = fut.get(); + REDIS_ASSERT(val && *val == "a", "failed to test async list: blmove"); + + this->set_ready(); + }); + _wait(); + + auto keys = std::initializer_list{src, dest}; + auto lmpop_res = _redis.template lmpop>(keys.begin(), keys.end(), ListWhence::LEFT).get(); + REDIS_ASSERT(lmpop_res && lmpop_res->first == dest && lmpop_res->second.size() == 1, "failed to test async list: lmpop"); + + set_ready(false); + _redis.template lmpop>(keys.begin(), keys.end(), ListWhence::LEFT, 2, + [this, dest](Future>>> && fut) { + auto val = fut.get(); + REDIS_ASSERT(val && val->first == dest && val->second.size() == 2, "failed to test async list: lmpop"); + + this->set_ready(); + }); + _wait(); +} + template void AsyncTest::_test_hash() { auto key = test_key("hash"); diff --git a/test/src/sw/redis++/list_cmds_test.h b/test/src/sw/redis++/list_cmds_test.h index 2092fe9..458fefe 100644 --- a/test/src/sw/redis++/list_cmds_test.h +++ b/test/src/sw/redis++/list_cmds_test.h @@ -41,6 +41,12 @@ class ListCmdTest { void _test_blocking(); + void _test_lmove(); + + void _test_blmove(); + + void _test_lmpop(); + RedisInstance &_redis; }; diff --git a/test/src/sw/redis++/list_cmds_test.hpp b/test/src/sw/redis++/list_cmds_test.hpp index fd26d8f..6740677 100644 --- a/test/src/sw/redis++/list_cmds_test.hpp +++ b/test/src/sw/redis++/list_cmds_test.hpp @@ -34,6 +34,12 @@ void ListCmdTest::run() { _test_list(); _test_blocking(); + + _test_lmove(); + + _test_blmove(); + + _test_lmpop(); } template @@ -145,6 +151,63 @@ void ListCmdTest::_test_blocking() { REDIS_ASSERT(str && *str == val, "failed to test rpoplpush"); } +template +void ListCmdTest::_test_lmove() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + _redis.lpush(src, {"a", "b", "c"}); + _redis.lpush(dest, {"e", "f", "d"}); + + auto val = _redis.lmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(val && *val == "c", "failed to test lmove"); + auto src_len = _redis.llen(src); + auto dest_len = _redis.llen(dest); + REDIS_ASSERT(src_len == 2 && dest_len == 4, "failed to test lmove"); + + val = _redis.lmove(test_key("not_exist_list"), dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(!val, "failed to test lmove"); +} + +template +void ListCmdTest::_test_blmove() { + auto src = test_key("src"); + auto dest = test_key("dest"); + + KeyDeleter deleter(_redis, {src, dest}); + + _redis.lpush(src, {"a", "b", "c"}); + _redis.lpush(dest, {"e", "f", "d"}); + + auto val = _redis.blmove(src, dest, ListWhence::LEFT, ListWhence::RIGHT); + REDIS_ASSERT(val && *val == "c", "failed to test lmove"); + auto src_len = _redis.llen(src); + auto dest_len = _redis.llen(dest); + REDIS_ASSERT(src_len == 2 && dest_len == 4, "failed to test lmove"); + + val = _redis.blmove(test_key("not_exist_list"), dest, ListWhence::LEFT, ListWhence::RIGHT, std::chrono::seconds(1)); + REDIS_ASSERT(!val, "failed to test lmove"); +} + +template +void ListCmdTest::_test_lmpop() { + auto k1 = test_key("k1"); + auto k2 = test_key("k2"); + + KeyDeleter deleter(_redis, {k1, k2}); + + _redis.lpush(k1, {"a", "b"}); + _redis.lpush(k2, {"c"}); + + auto res = _redis.template lmpop>({k1, k2}, ListWhence::LEFT, 2); + REDIS_ASSERT(res && res->first == k1 && res->second.size() == 2, "failed to test lmpop"); + + res = _redis.template lmpop>({k1, k2}, ListWhence::LEFT, 2); + REDIS_ASSERT(res && res->first == k2 && res->second.size() == 1, "failed to test lmpop"); +} + } } diff --git a/test/src/sw/redis++/utils.h b/test/src/sw/redis++/utils.h index da4e1fd..8c90909 100644 --- a/test/src/sw/redis++/utils.h +++ b/test/src/sw/redis++/utils.h @@ -68,6 +68,11 @@ void cluster_specializing_test(Test &test, (test.*func)(instance); } +template +void delete_keys(RedisInstance &r, const std::vector &keys) { + r.del(keys.begin(), keys.end()); +} + template class KeyDeleter { public: @@ -88,7 +93,7 @@ class KeyDeleter { private: void _delete() { if (!_keys.empty()) { - _redis.del(_keys.begin(), _keys.end()); + delete_keys(_redis, _keys); } }