From 73ad66eb93ec7e06c888d747cadad13b7062684a Mon Sep 17 00:00:00 2001 From: Marcelo Zimbres Date: Fri, 30 Dec 2022 15:54:19 +0100 Subject: [PATCH] Adds example that does not user awaitable ops. --- CMakeLists.txt | 17 ++ README.md | 33 +-- examples/cpp17_intro.cpp | 2 +- examples/cpp20_intro.cpp | 15 +- examples/cpp20_intro_awaitable_ops.cpp | 35 +++ include/aedis/resp3/impl/request.ipp | 2 +- include/aedis/resp3/request.hpp | 4 +- tests/conn_exec.cpp | 120 --------- tests/conn_exec_retry.cpp | 148 +++++++++++ tests/low_level.cpp | 352 +++++++++++++------------ 10 files changed, 416 insertions(+), 312 deletions(-) create mode 100644 examples/cpp20_intro_awaitable_ops.cpp create mode 100644 tests/conn_exec_retry.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cce6f3da..35c17b0b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -82,6 +82,15 @@ if (MSVC) target_compile_definitions(cpp20_intro PRIVATE _WIN32_WINNT=0x0601) endif() +add_executable(cpp20_intro_awaitable_ops examples/cpp20_intro_awaitable_ops.cpp) +target_link_libraries(cpp20_intro_awaitable_ops common) +target_compile_features(cpp20_intro_awaitable_ops PUBLIC cxx_std_20) +add_test(cpp20_intro_awaitable_ops cpp20_intro_awaitable_ops) +if (MSVC) + target_compile_options(cpp20_intro_awaitable_ops PRIVATE /bigobj) + target_compile_definitions(cpp20_intro_awaitable_ops PRIVATE _WIN32_WINNT=0x0601) +endif() + add_executable(cpp17_intro examples/cpp17_intro.cpp) target_compile_features(cpp17_intro PUBLIC cxx_std_17) add_test(cpp17_intro cpp17_intro) @@ -199,6 +208,14 @@ if (MSVC) target_compile_definitions(test_conn_exec PRIVATE _WIN32_WINNT=0x0601) endif() +add_executable(test_conn_exec_retry tests/conn_exec_retry.cpp) +target_compile_features(test_conn_exec_retry PUBLIC cxx_std_20) +add_test(test_conn_exec_retry test_conn_exec_retry) +if (MSVC) + target_compile_options(test_conn_exec_retry PRIVATE /bigobj) + target_compile_definitions(test_conn_exec_retry PRIVATE _WIN32_WINNT=0x0601) +endif() + add_executable(test_conn_push tests/conn_push.cpp) target_compile_features(test_conn_push PUBLIC cxx_std_20) add_test(test_conn_push test_conn_push) diff --git a/README.md b/README.md index c9e08a23..4223ef2e 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ the cases will be concerned with only three library entities For example, the coroutine below uses a short-lived connection to read Redis [hashes](https://redis.io/docs/data-types/hashes/) -in a `std::map` (see cpp20_intro.cpp, cpp17_intro.cpp and containers.cpp) +in a `std::map` ```cpp auto async_main() -> net::awaitable @@ -41,30 +41,32 @@ auto async_main() -> net::awaitable } ``` +For different versions of this example using different styles see + +* cpp20_intro.cpp: Does not use awaitable operators +* cpp20_intro_awaitable_ops.cpp: The version above. +* cpp17_intro.cpp: Requires C++17 only. +* cpp20_intro_tls.cpp: Communicates over TLS. + The execution of `connection::async_exec` above is composed with `connection::async_run` with the aid of the Asio awaitable `operator ||` that ensures that one operation is cancelled as soon as the other completes, these functions play the following roles * `connection::async_exec`: Execute commands by queuing the request - for writing and wait for the response sent back by Redis. It can be - called from multiple places in your code concurrently. + for writing. It will wait for the response sent back by Redis and + can be called from multiple places in your code concurrently. * `connection::async_run`: Coordinate low-level read and write operations. More specifically, it will hand IO control to `async_exec` when a response arrives, to - `aedis::connection::async_receive` when a server-push is received + `async_receive` when a server-push is received and will trigger writes of pending requests when a reconnection occurs. The role played by `async_run` can be better understood in the context of long-lived connections, which we will cover in the next section. -Before that however, the reader might want to skim over the examples +Before that however, the reader might want to skim over some further examples -* cpp17_intro.cpp: The Aedis hello-world program. Sends one command and quits the connection. -* cpp17_intro_sync.cpp: Shows how to use the connection class synchronously. -* cpp17_low_level_sync.cpp: Sends a ping synchronously using the low-level API. -* cpp20_intro.cpp: Like cpp17_intro.cpp but uses awaitable operators. -* cpp20_intro_tls.cpp: Same as intro.cpp but over TLS. * cpp20_containers.cpp: Shows how to send and receive STL containers and how to use transactions. * cpp20_serialization.cpp: Shows how to serialize types using Boost.Json. * cpp20_resolve_with_sentinel.cpp: Shows how to resolve a master address using sentinels. @@ -72,6 +74,7 @@ Before that however, the reader might want to skim over the examples * cpp20_echo_server.cpp: A simple TCP echo server. * cpp20_chat_room.cpp: A command line chat built on Redis pubsub. * cpp20_low_level_async.cpp: Sends a ping asynchronously using the low-level API. +* cpp17_low_level_sync.cpp: Sends a ping synchronously using the low-level API. To avoid repetition code that is common to some examples has been grouped in common.hpp. The main function used in some async examples @@ -159,7 +162,7 @@ auto run(std::shared_ptr conn) -> net::awaitable ``` The definition of `receiver` and `healthy_checker` above can be found -in subscriber.cpp. Adding a loop around `async_run` produces a simple +in cpp20_subscriber.cpp. Adding a loop around `async_run` produces a simple way to support reconnection _while there are pending operations on the connection_, for example, to reconnect to the same address @@ -187,7 +190,7 @@ auto run(std::shared_ptr conn) -> net::awaitable } ``` -For failover with sentinels see `resolve_with_sentinel.cpp`. At +For failover with sentinels see `cpp20_resolve_with_sentinel.cpp`. At this point the reasons for why `async_run` was introduced in Aedis might have become apparent to the reader @@ -225,7 +228,7 @@ co_await (conn.async_exec(...) || time.async_wait(...)) * The cancellation will be ignored if the request has already been written to the socket. * NOTE: It is usually a better idea to have a healthy checker than adding - per request timeout, see subscriber.cpp for an example. + per request timeout, see cpp20_subscriber.cpp for an example. ```cpp co_await (conn.async_run(...) || time.async_wait(...)) @@ -302,7 +305,7 @@ std::map map {...}; req.push_range("HSET", "key", map); ``` -Example serialization.cpp shows how store json strings in Redis. +Example cpp20_serialization.cpp shows how store json strings in Redis. @@ -1006,7 +1009,7 @@ Acknowledgement to people that helped shape Aedis * `connection::async_receive_event` is now being used to communicate internal events to the user, such as resolve, connect, push etc. For - examples see subscriber.cpp and `connection::event`. + examples see cpp20_subscriber.cpp and `connection::event`. * The `aedis` directory has been moved to `include` to look more similar to Boost libraries. Users should now replace `-I/aedis-path` diff --git a/examples/cpp17_intro.cpp b/examples/cpp17_intro.cpp index 2210470c..05728e39 100644 --- a/examples/cpp17_intro.cpp +++ b/examples/cpp17_intro.cpp @@ -45,7 +45,7 @@ auto main() -> int auto on_run = [](auto ec) { if (ec) - return log(ec, "on_exec: "); + return log(ec, "on_run: "); }; // async_exec callback. diff --git a/examples/cpp20_intro.cpp b/examples/cpp20_intro.cpp index b825f22f..d28df47f 100644 --- a/examples/cpp20_intro.cpp +++ b/examples/cpp20_intro.cpp @@ -6,15 +6,19 @@ #include #if defined(BOOST_ASIO_HAS_CO_AWAIT) -#include #include #include "common/common.hpp" namespace net = boost::asio; namespace resp3 = aedis::resp3; -using namespace net::experimental::awaitable_operators; using aedis::adapt; +auto run(std::shared_ptr conn) -> net::awaitable +{ + co_await connect(conn, "127.0.0.1", "6379"); + co_await conn->async_run(); +} + // Called from the main function (see main.cpp) auto async_main() -> net::awaitable { @@ -25,9 +29,10 @@ auto async_main() -> net::awaitable std::tuple resp; - auto conn = std::make_shared(co_await net::this_coro::executor); - co_await connect(conn, "127.0.0.1", "6379"); - co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); + auto ex = co_await net::this_coro::executor; + auto conn = std::make_shared(ex); + net::co_spawn(ex, run(conn), net::detached); + co_await conn->async_exec(req, adapt(resp)); std::cout << "PING: " << std::get<1>(resp) << std::endl; } diff --git a/examples/cpp20_intro_awaitable_ops.cpp b/examples/cpp20_intro_awaitable_ops.cpp new file mode 100644 index 00000000..b825f22f --- /dev/null +++ b/examples/cpp20_intro_awaitable_ops.cpp @@ -0,0 +1,35 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#if defined(BOOST_ASIO_HAS_CO_AWAIT) +#include +#include +#include "common/common.hpp" + +namespace net = boost::asio; +namespace resp3 = aedis::resp3; +using namespace net::experimental::awaitable_operators; +using aedis::adapt; + +// Called from the main function (see main.cpp) +auto async_main() -> net::awaitable +{ + resp3::request req; + req.push("HELLO", 3); + req.push("PING", "Hello world"); + req.push("QUIT"); + + std::tuple resp; + + auto conn = std::make_shared(co_await net::this_coro::executor); + co_await connect(conn, "127.0.0.1", "6379"); + co_await (conn->async_run() || conn->async_exec(req, adapt(resp))); + + std::cout << "PING: " << std::get<1>(resp) << std::endl; +} + +#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/include/aedis/resp3/impl/request.ipp b/include/aedis/resp3/impl/request.ipp index 15bb7e1f..d46085a0 100644 --- a/include/aedis/resp3/impl/request.ipp +++ b/include/aedis/resp3/impl/request.ipp @@ -9,7 +9,7 @@ namespace aedis::resp3::detail { -auto has_push_response(std::string_view cmd) -> bool +auto has_response(std::string_view cmd) -> bool { if (cmd == "SUBSCRIBE") return true; if (cmd == "PSUBSCRIBE") return true; diff --git a/include/aedis/resp3/request.hpp b/include/aedis/resp3/request.hpp index e7e255b6..d0b6380c 100644 --- a/include/aedis/resp3/request.hpp +++ b/include/aedis/resp3/request.hpp @@ -62,7 +62,7 @@ void to_bulk(Request& to, T n) namespace detail { -auto has_push_response(std::string_view cmd) -> bool; +auto has_response(std::string_view cmd) -> bool; template struct add_bulk_impl { @@ -386,7 +386,7 @@ public: private: void check_cmd(std::string_view cmd) { - if (!detail::has_push_response(cmd)) + if (!detail::has_response(cmd)) ++commands_; if (cmd == "HELLO") diff --git a/tests/conn_exec.cpp b/tests/conn_exec.cpp index 710705d0..8e679dbb 100644 --- a/tests/conn_exec.cpp +++ b/tests/conn_exec.cpp @@ -122,123 +122,3 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) ioc.run(); } - -// TODO: This test is broken. -BOOST_AUTO_TEST_CASE(request_retry_false) -{ - resp3::request req0; - req0.get_config().coalesce = false; - req0.get_config().cancel_on_connection_lost = true; - req0.push("HELLO", 3); - - resp3::request req1; - req1.get_config().coalesce = true; - req1.get_config().cancel_on_connection_lost = true; - req1.push("BLPOP", "any", 0); - - resp3::request req2; - req2.get_config().coalesce = true; - req2.get_config().cancel_on_connection_lost = false; - req2.get_config().cancel_if_unresponded = true; - req2.push("PING"); - - net::io_context ioc; - connection conn{ioc}; - - net::steady_timer st{ioc}; - st.expires_after(std::chrono::seconds{1}); - st.async_wait([&](auto){ - // Cancels the request before receiving the response. This - // should cause the second request to complete with error - // although it has cancel_on_connection_lost = false. - conn.cancel(aedis::operation::run); - }); - - auto const endpoints = resolve(); - net::connect(conn.next_layer(), endpoints); - - conn.async_exec(req0, adapt(), [](auto ec, auto){ - BOOST_TEST(!ec); - }); - - conn.async_exec(req1, adapt(), [](auto ec, auto){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); - - conn.async_exec(req2, adapt(), [](auto ec, auto){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); - - conn.async_run([](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); - - ioc.run(); -} - -BOOST_AUTO_TEST_CASE(request_retry_true) -{ - resp3::request req0; - req0.get_config().coalesce = false; - req0.get_config().cancel_on_connection_lost = true; - req0.push("HELLO", 3); - - resp3::request req1; - req1.get_config().coalesce = true; - req1.get_config().cancel_on_connection_lost = true; - req1.push("BLPOP", "any", 0); - - resp3::request req2; - req2.get_config().coalesce = true; - req2.get_config().cancel_on_connection_lost = false; - req2.get_config().cancel_if_unresponded = false; - req2.push("PING"); - - resp3::request req3; - req3.get_config().coalesce = true; - req3.get_config().cancel_on_connection_lost = true; - req3.get_config().cancel_if_unresponded = true; - req3.push("QUIT"); - - net::io_context ioc; - connection conn{ioc}; - - net::steady_timer st{ioc}; - st.expires_after(std::chrono::seconds{1}); - st.async_wait([&](auto){ - // Cancels the request before receiving the response. This - // should cause the second request to complete with error - // although it has cancel_on_connection_lost = false. - conn.cancel(aedis::operation::run); - }); - - auto const endpoints = resolve(); - net::connect(conn.next_layer(), endpoints); - - conn.async_exec(req0, adapt(), [](auto ec, auto){ - BOOST_TEST(!ec); - }); - - conn.async_exec(req1, adapt(), [](auto ec, auto){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - }); - - conn.async_exec(req2, adapt(), [&](auto ec, auto){ - BOOST_TEST(!ec); - conn.async_exec(req3, adapt(), [&](auto ec, auto){ - BOOST_TEST(!ec); - }); - }); - - conn.async_run([&](auto ec){ - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - conn.reset_stream(); - net::connect(conn.next_layer(), endpoints); - conn.async_run([&](auto ec){ - std::cout << ec.message() << std::endl; - BOOST_TEST(!ec); - }); - }); - - ioc.run(); -} diff --git a/tests/conn_exec_retry.cpp b/tests/conn_exec_retry.cpp new file mode 100644 index 00000000..e110d328 --- /dev/null +++ b/tests/conn_exec_retry.cpp @@ -0,0 +1,148 @@ +/* Copyright (c) 2018-2022 Marcelo Zimbres Silva (mzimbres@gmail.com) + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE.txt) + */ + +#include +#include +#include + +#define BOOST_TEST_MODULE low level +#include + +#include +#include + +#include "common.hpp" + +namespace net = boost::asio; +namespace resp3 = aedis::resp3; +using error_code = boost::system::error_code; +using connection = aedis::connection; +using aedis::adapt; + +BOOST_AUTO_TEST_CASE(request_retry_false) +{ + resp3::request req0; + req0.get_config().coalesce = false; + req0.get_config().cancel_on_connection_lost = true; + req0.push("HELLO", 3); + + resp3::request req1; + req1.get_config().coalesce = true; + req1.get_config().cancel_on_connection_lost = true; + req1.push("BLPOP", "any", 0); + + resp3::request req2; + req2.get_config().coalesce = true; + req2.get_config().cancel_on_connection_lost = false; + req2.get_config().cancel_if_unresponded = true; + req2.push("PING"); + + net::io_context ioc; + connection conn{ioc}; + + net::steady_timer st{ioc}; + st.expires_after(std::chrono::seconds{1}); + st.async_wait([&](auto){ + // Cancels the request before receiving the response. This + // should cause the third request to complete with error + // although it has cancel_on_connection_lost = false. The reason + // being is has already been written so + // cancel_on_connection_lost does not apply. + conn.cancel(aedis::operation::run); + }); + + auto const endpoints = resolve(); + net::connect(conn.next_layer(), endpoints); + + conn.async_exec(req0, adapt(), [](auto ec, auto){ + BOOST_TEST(!ec); + }); + + conn.async_exec(req1, adapt(), [](auto ec, auto){ + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + }); + + conn.async_exec(req2, adapt(), [](auto ec, auto){ + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + }); + + conn.async_run([](auto ec){ + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + }); + + ioc.run(); +} + +BOOST_AUTO_TEST_CASE(request_retry_true) +{ + resp3::request req0; + req0.get_config().coalesce = false; + req0.get_config().cancel_on_connection_lost = true; + req0.push("HELLO", 3); + + resp3::request req1; + req1.get_config().coalesce = true; + req1.get_config().cancel_on_connection_lost = true; + req1.push("BLPOP", "any", 0); + + resp3::request req2; + req2.get_config().coalesce = true; + req2.get_config().cancel_on_connection_lost = false; + req2.get_config().cancel_if_unresponded = false; + req2.push("PING"); + + resp3::request req3; + req3.get_config().coalesce = true; + req3.get_config().cancel_on_connection_lost = true; + req3.get_config().cancel_if_unresponded = true; + req3.push("QUIT"); + + net::io_context ioc; + connection conn{ioc}; + + net::steady_timer st{ioc}; + st.expires_after(std::chrono::seconds{1}); + st.async_wait([&](auto){ + // Cancels the request before receiving the response. This + // should cause the thrid request to not complete with error + // since it has cancel_if_unresponded = true and cancellation commes + // after it was written. + conn.cancel(aedis::operation::run); + }); + + auto const endpoints = resolve(); + net::connect(conn.next_layer(), endpoints); + + conn.async_exec(req0, adapt(), [](auto ec, auto){ + BOOST_TEST(!ec); + }); + + conn.async_exec(req1, adapt(), [](auto ec, auto){ + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + }); + + conn.async_exec(req2, adapt(), [&](auto ec, auto){ + BOOST_TEST(!ec); + conn.async_exec(req3, adapt(), [&](auto ec, auto){ + BOOST_TEST(!ec); + }); + }); + + conn.async_run([&](auto ec){ + // The first cacellation. + BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + conn.reset_stream(); + + // Reconnects and runs again to test req3. + net::connect(conn.next_layer(), endpoints); + conn.async_run([&](auto ec){ + std::cout << ec.message() << std::endl; + BOOST_TEST(!ec); + }); + }); + + ioc.run(); +} diff --git a/tests/low_level.cpp b/tests/low_level.cpp index 33de70bf..5cb85bb5 100644 --- a/tests/low_level.cpp +++ b/tests/low_level.cpp @@ -295,173 +295,189 @@ std::vector const attr_e1a std::vector const attr_e1b { {resp3::type::attribute, 0UL, 0UL, {}} }; -#define S01 "#11\r\n" -#define S02 "#f\r\n" -#define S03 "#t\r\n" -#define S04 "$?\r\n;0\r\n" -#define S05 "%11\r\n" -#define S06 "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" -#define S07 "$?\r\n;b\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" -#define S08 "*1\r\n:11\r\n" -#define S09 ":-3\r\n" -#define S10 ":11\r\n" -#define S11 ":3\r\n" -#define S12 "_\r\n" -#define S13 ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n" -#define S14 ">0\r\n" -#define S15 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" -#define S16 "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n" -#define S17 "*1\r\n" S16 -#define S18 "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n" -#define S19 "|0\r\n" -#define S20 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" -#define S21 "*1\r\n*1\r\n$2\r\nab\r\n" -#define S22 "*1\r\n*1\r\n*1\r\n*1\r\n*1\r\n*1\r\na\r\n" -#define S23 "*0\r\n" -#define S24 "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" -#define S25 "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n" -#define S26 "*1\r\n" S25 -#define S27 "~0\r\n" -#define S28 "-Error\r\n" -#define S29 "-\r\n" -#define S30 "%0\r\n" -#define S31 ",1.23\r\n" -#define S32 ",inf\r\n" -#define S33 ",-inf\r\n" -#define S34 ",1.23\r\n" -#define S35 ",er\r\n" -#define S36 "!21\r\nSYNTAX invalid syntax\r\n" -#define S37 "!0\r\n\r\n" -#define S38 "!3\r\nfoo\r\n" -#define S39 "=15\r\ntxt:Some string\r\n" -#define S40 "=0\r\n\r\n" -#define S41 "(3492890328409238509324850943850943825024385\r\n" -#define S42 "(\r\n" -#define S43 "+OK\r\n" -#define S44 "+\r\n" -#define S45 "s11\r\n" -#define S46 ":adf\r\n" -#define S47 "%rt\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n" -#define S48 "$?\r\n;d\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" -#define S49 "$l\r\nhh\r\n" -#define S50 ":\r\n" -#define S51 "#\r\n" -#define S52 ",\r\n" -#define S53 "$2\r\nhh\r\n" -#define S54 "$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n" -#define S55 "$0\r\n\r\n" +#define S01a "#11\r\n" +#define S01b "#f\r\n" +#define S01c "#t\r\n" +#define S01d "#\r\n" + +#define S02a "$?\r\n;0\r\n" +#define S02b "$?\r\n;4\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" +#define S02c "$?\r\n;b\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" +#define S02d "$?\r\n;d\r\nHell\r\n;5\r\no wor\r\n;1\r\nd\r\n;0\r\n" + +#define S03a "%11\r\n" +#define S03b "%4\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n$4\r\nkey3\r\n$6\r\nvalue3\r\n" +#define S03c "%0\r\n" +#define S03d "%rt\r\n$4\r\nkey1\r\n$6\r\nvalue1\r\n$4\r\nkey2\r\n$6\r\nvalue2\r\n" + +#define S04a "*1\r\n:11\r\n" +#define S04b "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" +#define S04c "*1\r\n" S03b +#define S04d "*1\r\n" S09a +#define S04e "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" +#define S04f "*1\r\n*1\r\n$2\r\nab\r\n" +#define S04g "*1\r\n*1\r\n*1\r\n*1\r\n*1\r\n*1\r\na\r\n" +#define S04h "*0\r\n" +#define S04i "*3\r\n$2\r\n11\r\n$2\r\n22\r\n$1\r\n3\r\n" + +#define S05a ":-3\r\n" +#define S05b ":11\r\n" +#define s05c ":3\r\n" +#define S05d ":adf\r\n" +#define S05e ":\r\n" + +#define S06a "_\r\n" + +#define S07a ">4\r\n+pubsub\r\n+message\r\n+some-channel\r\n+some message\r\n" +#define S07b ">0\r\n" + +#define S08a "|1\r\n+key-popularity\r\n%2\r\n$1\r\na\r\n,0.1923\r\n$1\r\nb\r\n,0.0012\r\n" +#define S08b "|0\r\n" + +#define S09a "~6\r\n+orange\r\n+apple\r\n+one\r\n+two\r\n+three\r\n+orange\r\n" +#define S09b "~0\r\n" + +#define S10a "-Error\r\n" +#define S10b "-\r\n" + +#define S11a ",1.23\r\n" +#define S11b ",inf\r\n" +#define S11c ",-inf\r\n" +#define S11d ",1.23\r\n" +#define S11e ",er\r\n" +#define S11f ",\r\n" + +#define S12a "!21\r\nSYNTAX invalid syntax\r\n" +#define S12b "!0\r\n\r\n" +#define S12c "!3\r\nfoo\r\n" + +#define S13a "=15\r\ntxt:Some string\r\n" +#define S13b "=0\r\n\r\n" + +#define S14a "(3492890328409238509324850943850943825024385\r\n" +#define S14b "(\r\n" + +#define S15a "+OK\r\n" +#define S15b "+\r\n" + +#define S16a "s11\r\n" + +#define S17a "$l\r\nhh\r\n" +#define S17b "$2\r\nhh\r\n" +#define S18c "$26\r\nhhaa\aaaa\raaaaa\r\naaaaaaaaaa\r\n" +#define S18d "$0\r\n\r\n" #define NUMBER_TEST_CONDITIONS(test) \ - test(ex, make_expected(S01, std::optional{}, aedis::error::unexpected_bool_value)); \ - test(ex, make_expected(S02, bool{false})); \ - test(ex, make_expected(S02, node_type{resp3::type::boolean, 1UL, 0UL, {"f"}})); \ - test(ex, make_expected(S03, bool{true})); \ - test(ex, make_expected(S03, node_type{resp3::type::boolean, 1UL, 0UL, {"t"}})); \ - test(ex, make_expected(S03, op_bool_ok)); \ - test(ex, make_expected(S03, std::map{}, aedis::error::expects_resp3_map)); \ - test(ex, make_expected(S03, std::set{}, aedis::error::expects_resp3_set)); \ - test(ex, make_expected(S03, std::unordered_map{}, aedis::error::expects_resp3_map)); \ - test(ex, make_expected(S03, std::unordered_set{}, aedis::error::expects_resp3_set)); \ - test(ex, make_expected(S04, streamed_string_e2)); \ - test(ex, make_expected(S05, int{}, aedis::error::expects_resp3_simple_type));\ - test(ex, make_expected(S05, std::optional{}, aedis::error::expects_resp3_simple_type));; \ - test(ex, make_expected(S06, int{}, aedis::error::not_a_number)); \ - test(ex, make_expected(S06, std::string{"Hello word"})); \ - test(ex, make_expected(S06, streamed_string_e1)); \ - test(ex, make_expected(S07, std::string{}, aedis::error::not_a_number)); \ - test(ex, make_expected(S08, std::tuple{11})); \ - test(ex, make_expected(S09, node_type{resp3::type::number, 1UL, 0UL, {"-3"}})); \ - test(ex, make_expected(S10, int{11})); \ - test(ex, make_expected(S10, op_int_ok)); \ - test(ex, make_expected(S10, std::list{}, aedis::error::expects_resp3_aggregate)); \ - test(ex, make_expected(S10, std::map{}, aedis::error::expects_resp3_map)); \ - test(ex, make_expected(S10, std::set{}, aedis::error::expects_resp3_set)); \ - test(ex, make_expected(S10, std::unordered_map{}, aedis::error::expects_resp3_map)); \ - test(ex, make_expected(S10, std::unordered_set{}, aedis::error::expects_resp3_set)); \ - test(ex, make_expected(S11, array_type2{}, aedis::error::expects_resp3_aggregate));\ - test(ex, make_expected(S11, node_type{resp3::type::number, 1UL, 0UL, {"3"}})); \ - test(ex, make_expected(S12, array_type{}, aedis::error::resp3_null));\ - test(ex, make_expected(S12, int{0}, aedis::error::resp3_null)); \ - test(ex, make_expected(S12, map_type{}, aedis::error::resp3_null));\ - test(ex, make_expected(S12, op_type_01{}));\ - test(ex, make_expected(S12, op_type_02{}));\ - test(ex, make_expected(S12, op_type_03{}));\ - test(ex, make_expected(S12, op_type_04{}));\ - test(ex, make_expected(S12, op_type_05{}));\ - test(ex, make_expected(S12, op_type_06{}));\ - test(ex, make_expected(S12, op_type_07{}));\ - test(ex, make_expected(S12, op_type_08{}));\ - test(ex, make_expected(S12, op_type_09{}));\ - test(ex, make_expected(S12, std::list{}, aedis::error::resp3_null));\ - test(ex, make_expected(S12, std::vector{}, aedis::error::resp3_null));\ - test(ex, make_expected(S13, push_e1a)); \ - test(ex, make_expected(S14, push_e1b)); \ - test(ex, make_expected(S15, map_type{}, aedis::error::expects_resp3_map));\ - test(ex, make_expected(S16, map_e1f));\ - test(ex, make_expected(S16, map_e1g));\ - test(ex, make_expected(S16, map_e1k));\ - test(ex, make_expected(S16, map_e1l));\ - test(ex, make_expected(S16, map_expected_1a));\ - test(ex, make_expected(S16, map_expected_1b));\ - test(ex, make_expected(S16, map_expected_1c));\ - test(ex, make_expected(S16, map_expected_1d));\ - test(ex, make_expected(S16, map_expected_1e));\ - test(ex, make_expected(S17, std::tuple{map_expected_1d}));\ - test(ex, make_expected(S18, attr_e1a)); \ - test(ex, make_expected(S19, attr_e1b)); \ - test(ex, make_expected(S20, array_e1a));\ - test(ex, make_expected(S20, array_e1b));\ - test(ex, make_expected(S20, array_e1c));\ - test(ex, make_expected(S20, array_e1f));\ - test(ex, make_expected(S20, array_e1g));\ - test(ex, make_expected(S20, array_e1h));\ - test(ex, make_expected(S20, array_type2{}, aedis::error::incompatible_size));\ - test(ex, make_expected(S20, tuple_int_2{}, aedis::error::incompatible_size));\ - test(ex, make_expected(S21, array_type2{}, aedis::error::nested_aggregate_not_supported));\ - test(ex, make_expected(S22, vec_node_type{}, aedis::error::exceeeds_max_nested_depth));\ - test(ex, make_expected(S23, array_e1d));\ - test(ex, make_expected(S23, array_e1e));\ - test(ex, make_expected(S24, set_type{}, aedis::error::expects_resp3_set)); \ - test(ex, make_expected(S25, set_e1c)); \ - test(ex, make_expected(S25, set_e1d)); \ - test(ex, make_expected(S25, set_e1f)); \ - test(ex, make_expected(S25, set_e1g)); \ - test(ex, make_expected(S25, set_expected1a)); \ - test(ex, make_expected(S25, set_expected_1e)); \ - test(ex, make_expected(S25, set_type{"apple", "one", "orange", "three", "two"})); \ - test(ex, make_expected(S26, std::tuple{set_e1c})); \ - test(ex, make_expected(S27, std::vector{ {resp3::type::set, 0UL, 0UL, {}} })); \ - test(ex, make_expected(S28, aedis::ignore{}, aedis::error::resp3_simple_error)); \ - test(ex, make_expected(S28, node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, aedis::error::resp3_simple_error)); \ - test(ex, make_expected(S29, node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, aedis::error::resp3_simple_error)); \ - test(ex, make_expected(S30, map_type{}));\ - test(ex, make_expected(S31, node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}));\ - test(ex, make_expected(S32, node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}));\ - test(ex, make_expected(S33, node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}));\ - test(ex, make_expected(S34, double{1.23}));\ - test(ex, make_expected(S35, double{0}, aedis::error::not_a_double));\ - test(ex, make_expected(S36, node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, aedis::error::resp3_blob_error));\ - test(ex, make_expected(S37, node_type{resp3::type::blob_error, 1UL, 0UL, {}}, aedis::error::resp3_blob_error));\ - test(ex, make_expected(S38, aedis::ignore{}, aedis::error::resp3_blob_error));\ - test(ex, make_expected(S39, node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}));\ - test(ex, make_expected(S40, node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}));\ - test(ex, make_expected(S41, node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}));\ - test(ex, make_expected(S42, int{}, aedis::error::empty_field));\ - test(ex, make_expected(S43, std::optional{"OK"}));\ - test(ex, make_expected(S43, std::string{"OK"}));\ - test(ex, make_expected(S44, std::optional{""}));\ - test(ex, make_expected(S44, std::string{""}));\ - test(ex, make_expected(S45, int{}, aedis::error::invalid_data_type));\ - test(ex, make_expected(S46, int{11}, aedis::error::not_a_number));\ - test(ex, make_expected(S47, map_type{}, aedis::error::not_a_number));\ - test(ex, make_expected(S48, std::string{}, aedis::error::not_a_number));\ - test(ex, make_expected(S49, std::string{}, aedis::error::not_a_number));\ - test(ex, make_expected(S50, int{}, aedis::error::empty_field));\ - test(ex, make_expected(S51, std::optional{}, aedis::error::empty_field));\ - test(ex, make_expected(S52, std::string{}, aedis::error::empty_field));\ - test(ex, make_expected(S53, node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}));\ - test(ex, make_expected(S54, node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}));\ - test(ex, make_expected(S55, node_type{resp3::type::blob_string, 1UL, 0UL, {}}));\ + test(ex, make_expected(S01a, std::optional{}, aedis::error::unexpected_bool_value)); \ + test(ex, make_expected(S01b, bool{false})); \ + test(ex, make_expected(S01b, node_type{resp3::type::boolean, 1UL, 0UL, {"f"}})); \ + test(ex, make_expected(S01c, bool{true})); \ + test(ex, make_expected(S01c, node_type{resp3::type::boolean, 1UL, 0UL, {"t"}})); \ + test(ex, make_expected(S01c, op_bool_ok)); \ + test(ex, make_expected(S01c, std::map{}, aedis::error::expects_resp3_map)); \ + test(ex, make_expected(S01c, std::set{}, aedis::error::expects_resp3_set)); \ + test(ex, make_expected(S01c, std::unordered_map{}, aedis::error::expects_resp3_map)); \ + test(ex, make_expected(S01c, std::unordered_set{}, aedis::error::expects_resp3_set)); \ + test(ex, make_expected(S02a, streamed_string_e2)); \ + test(ex, make_expected(S03a, int{}, aedis::error::expects_resp3_simple_type));\ + test(ex, make_expected(S03a, std::optional{}, aedis::error::expects_resp3_simple_type));; \ + test(ex, make_expected(S02b, int{}, aedis::error::not_a_number)); \ + test(ex, make_expected(S02b, std::string{"Hello word"})); \ + test(ex, make_expected(S02b, streamed_string_e1)); \ + test(ex, make_expected(S02c, std::string{}, aedis::error::not_a_number)); \ + test(ex, make_expected(S04a, std::tuple{11})); \ + test(ex, make_expected(S05a, node_type{resp3::type::number, 1UL, 0UL, {"-3"}})); \ + test(ex, make_expected(S05b, int{11})); \ + test(ex, make_expected(S05b, op_int_ok)); \ + test(ex, make_expected(S05b, std::list{}, aedis::error::expects_resp3_aggregate)); \ + test(ex, make_expected(S05b, std::map{}, aedis::error::expects_resp3_map)); \ + test(ex, make_expected(S05b, std::set{}, aedis::error::expects_resp3_set)); \ + test(ex, make_expected(S05b, std::unordered_map{}, aedis::error::expects_resp3_map)); \ + test(ex, make_expected(S05b, std::unordered_set{}, aedis::error::expects_resp3_set)); \ + test(ex, make_expected(s05c, array_type2{}, aedis::error::expects_resp3_aggregate));\ + test(ex, make_expected(s05c, node_type{resp3::type::number, 1UL, 0UL, {"3"}})); \ + test(ex, make_expected(S06a, array_type{}, aedis::error::resp3_null));\ + test(ex, make_expected(S06a, int{0}, aedis::error::resp3_null)); \ + test(ex, make_expected(S06a, map_type{}, aedis::error::resp3_null));\ + test(ex, make_expected(S06a, op_type_01{}));\ + test(ex, make_expected(S06a, op_type_02{}));\ + test(ex, make_expected(S06a, op_type_03{}));\ + test(ex, make_expected(S06a, op_type_04{}));\ + test(ex, make_expected(S06a, op_type_05{}));\ + test(ex, make_expected(S06a, op_type_06{}));\ + test(ex, make_expected(S06a, op_type_07{}));\ + test(ex, make_expected(S06a, op_type_08{}));\ + test(ex, make_expected(S06a, op_type_09{}));\ + test(ex, make_expected(S06a, std::list{}, aedis::error::resp3_null));\ + test(ex, make_expected(S06a, std::vector{}, aedis::error::resp3_null));\ + test(ex, make_expected(S07a, push_e1a)); \ + test(ex, make_expected(S07b, push_e1b)); \ + test(ex, make_expected(S04b, map_type{}, aedis::error::expects_resp3_map));\ + test(ex, make_expected(S03b, map_e1f));\ + test(ex, make_expected(S03b, map_e1g));\ + test(ex, make_expected(S03b, map_e1k));\ + test(ex, make_expected(S03b, map_e1l));\ + test(ex, make_expected(S03b, map_expected_1a));\ + test(ex, make_expected(S03b, map_expected_1b));\ + test(ex, make_expected(S03b, map_expected_1c));\ + test(ex, make_expected(S03b, map_expected_1d));\ + test(ex, make_expected(S03b, map_expected_1e));\ + test(ex, make_expected(S04c, std::tuple{map_expected_1d}));\ + test(ex, make_expected(S08a, attr_e1a)); \ + test(ex, make_expected(S08b, attr_e1b)); \ + test(ex, make_expected(S04e, array_e1a));\ + test(ex, make_expected(S04e, array_e1b));\ + test(ex, make_expected(S04e, array_e1c));\ + test(ex, make_expected(S04e, array_e1f));\ + test(ex, make_expected(S04e, array_e1g));\ + test(ex, make_expected(S04e, array_e1h));\ + test(ex, make_expected(S04e, array_type2{}, aedis::error::incompatible_size));\ + test(ex, make_expected(S04e, tuple_int_2{}, aedis::error::incompatible_size));\ + test(ex, make_expected(S04f, array_type2{}, aedis::error::nested_aggregate_not_supported));\ + test(ex, make_expected(S04g, vec_node_type{}, aedis::error::exceeeds_max_nested_depth));\ + test(ex, make_expected(S04h, array_e1d));\ + test(ex, make_expected(S04h, array_e1e));\ + test(ex, make_expected(S04i, set_type{}, aedis::error::expects_resp3_set)); \ + test(ex, make_expected(S09a, set_e1c)); \ + test(ex, make_expected(S09a, set_e1d)); \ + test(ex, make_expected(S09a, set_e1f)); \ + test(ex, make_expected(S09a, set_e1g)); \ + test(ex, make_expected(S09a, set_expected1a)); \ + test(ex, make_expected(S09a, set_expected_1e)); \ + test(ex, make_expected(S09a, set_type{"apple", "one", "orange", "three", "two"})); \ + test(ex, make_expected(S04d, std::tuple{set_e1c})); \ + test(ex, make_expected(S09b, std::vector{ {resp3::type::set, 0UL, 0UL, {}} })); \ + test(ex, make_expected(S10a, aedis::ignore{}, aedis::error::resp3_simple_error)); \ + test(ex, make_expected(S10a, node_type{resp3::type::simple_error, 1UL, 0UL, {"Error"}}, aedis::error::resp3_simple_error)); \ + test(ex, make_expected(S10b, node_type{resp3::type::simple_error, 1UL, 0UL, {""}}, aedis::error::resp3_simple_error)); \ + test(ex, make_expected(S03c, map_type{}));\ + test(ex, make_expected(S11a, node_type{resp3::type::doublean, 1UL, 0UL, {"1.23"}}));\ + test(ex, make_expected(S11b, node_type{resp3::type::doublean, 1UL, 0UL, {"inf"}}));\ + test(ex, make_expected(S11c, node_type{resp3::type::doublean, 1UL, 0UL, {"-inf"}}));\ + test(ex, make_expected(S11d, double{1.23}));\ + test(ex, make_expected(S11e, double{0}, aedis::error::not_a_double));\ + test(ex, make_expected(S12a, node_type{resp3::type::blob_error, 1UL, 0UL, {"SYNTAX invalid syntax"}}, aedis::error::resp3_blob_error));\ + test(ex, make_expected(S12b, node_type{resp3::type::blob_error, 1UL, 0UL, {}}, aedis::error::resp3_blob_error));\ + test(ex, make_expected(S12c, aedis::ignore{}, aedis::error::resp3_blob_error));\ + test(ex, make_expected(S13a, node_type{resp3::type::verbatim_string, 1UL, 0UL, {"txt:Some string"}}));\ + test(ex, make_expected(S13b, node_type{resp3::type::verbatim_string, 1UL, 0UL, {}}));\ + test(ex, make_expected(S14a, node_type{resp3::type::big_number, 1UL, 0UL, {"3492890328409238509324850943850943825024385"}}));\ + test(ex, make_expected(S14b, int{}, aedis::error::empty_field));\ + test(ex, make_expected(S15a, std::optional{"OK"}));\ + test(ex, make_expected(S15a, std::string{"OK"}));\ + test(ex, make_expected(S15b, std::optional{""}));\ + test(ex, make_expected(S15b, std::string{""}));\ + test(ex, make_expected(S16a, int{}, aedis::error::invalid_data_type));\ + test(ex, make_expected(S05d, int{11}, aedis::error::not_a_number));\ + test(ex, make_expected(S03d, map_type{}, aedis::error::not_a_number));\ + test(ex, make_expected(S02d, std::string{}, aedis::error::not_a_number));\ + test(ex, make_expected(S17a, std::string{}, aedis::error::not_a_number));\ + test(ex, make_expected(S05e, int{}, aedis::error::empty_field));\ + test(ex, make_expected(S01d, std::optional{}, aedis::error::empty_field));\ + test(ex, make_expected(S11f, std::string{}, aedis::error::empty_field));\ + test(ex, make_expected(S17b, node_type{resp3::type::blob_string, 1UL, 0UL, {"hh"}}));\ + test(ex, make_expected(S18c, node_type{resp3::type::blob_string, 1UL, 0UL, {"hhaa\aaaa\raaaaa\r\naaaaaaaaaa"}}));\ + test(ex, make_expected(S18d, node_type{resp3::type::blob_string, 1UL, 0UL, {}}));\ test(ex, make_expected(make_blob_string(blob), node_type{resp3::type::blob_string, 1UL, 0UL, {blob}}));\ BOOST_AUTO_TEST_CASE(parser) @@ -489,7 +505,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_simple_error) boost::system::error_code ec; test_stream ts {ioc}; - ts.append(S28); + ts.append(S10a); resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec); BOOST_CHECK_EQUAL(ec, aedis::error::resp3_simple_error); BOOST_TEST(!rbuffer.empty()); @@ -502,7 +518,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_blob_error) boost::system::error_code ec; test_stream ts {ioc}; - ts.append(S36); + ts.append(S12a); resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec); BOOST_CHECK_EQUAL(ec, aedis::error::resp3_blob_error); BOOST_TEST(!rbuffer.empty()); @@ -515,7 +531,7 @@ BOOST_AUTO_TEST_CASE(ignore_adapter_no_error) boost::system::error_code ec; test_stream ts {ioc}; - ts.append(S10); + ts.append(S05b); resp3::read(ts, net::dynamic_buffer(rbuffer), adapt2(), ec); BOOST_TEST(!ec); BOOST_TEST(rbuffer.empty());