diff --git a/include/boost/redis/impl/multiplexer.ipp b/include/boost/redis/impl/multiplexer.ipp index 5dae6c08..4c118836 100644 --- a/include/boost/redis/impl/multiplexer.ipp +++ b/include/boost/redis/impl/multiplexer.ipp @@ -16,7 +16,7 @@ multiplexer::elem::elem(request const& req, pipeline_adapter_type adapter) , adapter_{} , remaining_responses_{req.get_expected_responses()} , status_{status::waiting} -, ec_{{}} +, ec_{} , read_size_{0} { adapter_ = [this, adapter](resp3::node_view const& nd, system::error_code& ec) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b6b22b2a..66a00f04 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -20,7 +20,7 @@ add_library(boost_redis_tests_common STATIC common.cpp) target_compile_features(boost_redis_tests_common PRIVATE cxx_std_17) target_link_libraries(boost_redis_tests_common PRIVATE boost_redis_project_options) -macro(make_test TEST_NAME STANDARD) +macro(make_test TEST_NAME) set(EXE_NAME "boost_redis_${TEST_NAME}") add_executable(${EXE_NAME} ${TEST_NAME}.cpp) target_link_libraries(${EXE_NAME} PRIVATE @@ -29,31 +29,32 @@ macro(make_test TEST_NAME STANDARD) boost_redis_project_options Boost::unit_test_framework ) - target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD}) add_test(${EXE_NAME} ${EXE_NAME}) endmacro() -make_test(test_conn_quit 17) -# TODO: Configure a Redis server with TLS in the CI and reenable this test. -#make_test(test_conn_tls 17) -make_test(test_low_level 17) -make_test(test_conn_exec_retry 17) -make_test(test_conn_exec_error 17) -make_test(test_request 17) -make_test(test_run 17) -make_test(test_low_level_sync_sans_io 17) -make_test(test_conn_check_health 17) +# Unit tests +make_test(test_low_level) +make_test(test_request) +make_test(test_low_level_sync_sans_io) +make_test(test_any_adapter) -make_test(test_conn_exec 20) -make_test(test_conn_push 20) -make_test(test_conn_reconnect 20) -make_test(test_conn_exec_cancel 20) -make_test(test_conn_exec_cancel2 20) -make_test(test_conn_echo_stress 20) -make_test(test_any_adapter 17) -make_test(test_conversions 17) -make_test(test_issue_50 20) -make_test(test_issue_181 17) +# Tests that require a real Redis server +make_test(test_conn_quit) +# TODO: Configure a Redis server with TLS in the CI and reenable this test. +#make_test(test_conn_tls) +make_test(test_conn_exec_retry) +make_test(test_conn_exec_error) +make_test(test_run) +make_test(test_conn_check_health) +make_test(test_conn_exec) +make_test(test_conn_push) +make_test(test_conn_reconnect) +make_test(test_conn_exec_cancel) +make_test(test_conn_exec_cancel2) +make_test(test_conn_echo_stress) +make_test(test_issue_50) +make_test(test_issue_181) +make_test(test_conversions) # Coverage set( diff --git a/test/Jamfile b/test/Jamfile index c0435408..e0f06c3a 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -48,10 +48,9 @@ lib redis_test_common # B2 runs tests in parallel, and some tests rely on having exclusive # access to a Redis server, so we only run the ones that don't require a DB server. local tests = - test_low_level_sync_sans_io test_low_level test_request - test_run + test_low_level_sync_sans_io test_any_adapter ; diff --git a/test/common.cpp b/test/common.cpp index 53399c8e..649cbb89 100644 --- a/test/common.cpp +++ b/test/common.cpp @@ -5,6 +5,7 @@ #include #include +#include namespace net = boost::asio; @@ -55,22 +56,17 @@ boost::redis::config make_test_config() } #ifdef BOOST_ASIO_HAS_CO_AWAIT -auto start(net::awaitable op) -> int +void run_coroutine_test(net::awaitable op, std::chrono::steady_clock::duration timeout) { - try { - net::io_context ioc; - net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { - if (p) - std::rethrow_exception(p); - }); - ioc.run(); - - return 0; - - } catch (std::exception const& e) { - std::cerr << "start> " << e.what() << std::endl; - } - - return 1; + net::io_context ioc; + bool finished = false; + net::co_spawn(ioc, std::move(op), [&finished](std::exception_ptr p) { + if (p) + std::rethrow_exception(p); + finished = true; + }); + ioc.run_for(timeout); + if (!finished) + throw std::runtime_error("Coroutine test did not finish"); } #endif // BOOST_ASIO_HAS_CO_AWAIT diff --git a/test/common.hpp b/test/common.hpp index 46d46aa1..6cd51c31 100644 --- a/test/common.hpp +++ b/test/common.hpp @@ -8,14 +8,22 @@ #include #include +#include #include +// The timeout for tests involving communication to a real server. +// Some tests use a longer timeout by multiplying this value by some +// integral number. +inline constexpr std::chrono::seconds test_timeout{30}; + #ifdef BOOST_ASIO_HAS_CO_AWAIT inline auto redir(boost::system::error_code& ec) { return boost::asio::redirect_error(boost::asio::use_awaitable, ec); } -auto start(boost::asio::awaitable op) -> int; +void run_coroutine_test( + boost::asio::awaitable, + std::chrono::steady_clock::duration timeout = test_timeout); #endif // BOOST_ASIO_HAS_CO_AWAIT boost::redis::config make_test_config(); diff --git a/test/test_conn_check_health.cpp b/test/test_conn_check_health.cpp index 65347877..78efa82c 100644 --- a/test/test_conn_check_health.cpp +++ b/test/test_conn_check_health.cpp @@ -7,7 +7,7 @@ #include #include -#include +#include #define BOOST_TEST_MODULE check_health #include @@ -25,9 +25,12 @@ using boost::redis::ignore; using boost::redis::operation; using boost::redis::generic_response; using boost::redis::consume_one; +using namespace std::chrono_literals; // TODO: Test cancel(health_check) +namespace { + struct push_callback { connection* conn1; connection* conn2; @@ -82,10 +85,13 @@ BOOST_AUTO_TEST_CASE(check_health) auto cfg1 = make_test_config(); cfg1.health_check_id = "conn1"; cfg1.reconnect_wait_interval = std::chrono::seconds::zero(); - error_code res1; - conn1.async_run(cfg1, {}, [&](auto ec) { + + bool run1_finished = false, run2_finished = false, exec_finished = false; + + conn1.async_run(cfg1, {}, [&](error_code ec) { + run1_finished = true; std::cout << "async_run 1 completed: " << ec.message() << std::endl; - res1 = ec; + BOOST_TEST(ec != error_code()); }); //-------------------------------- @@ -96,10 +102,10 @@ BOOST_AUTO_TEST_CASE(check_health) auto cfg2 = make_test_config(); cfg2.health_check_id = "conn2"; - error_code res2; - conn2.async_run(cfg2, {}, [&](auto ec) { + conn2.async_run(cfg2, {}, [&](error_code ec) { + run2_finished = true; std::cout << "async_run 2 completed: " << ec.message() << std::endl; - res2 = ec; + BOOST_TEST(ec != error_code()); }); request req2; @@ -107,21 +113,25 @@ BOOST_AUTO_TEST_CASE(check_health) generic_response resp2; conn2.set_receive_response(resp2); - conn2.async_exec(req2, ignore, [](auto ec, auto) { + conn2.async_exec(req2, ignore, [&exec_finished](error_code ec, std::size_t) { + exec_finished = true; std::cout << "async_exec: " << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); }); //-------------------------------- push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes. - ioc.run(); + ioc.run_for(2 * test_timeout); - BOOST_TEST(!!res1); - BOOST_TEST(!!res2); + BOOST_TEST(run1_finished); + BOOST_TEST(run2_finished); + BOOST_TEST(exec_finished); // Waits before exiting otherwise it might cause subsequent tests // to fail. std::this_thread::sleep_for(std::chrono::seconds{10}); } + +} // namespace \ No newline at end of file diff --git a/test/test_conn_echo_stress.cpp b/test/test_conn_echo_stress.cpp index 7cb341e5..51e1ab39 100644 --- a/test/test_conn_echo_stress.cpp +++ b/test/test_conn_echo_stress.cpp @@ -5,11 +5,14 @@ */ #include +#include #include -#include -#include -#include +#include +#include + +#include +#include #define BOOST_TEST_MODULE echo_stress #include @@ -30,6 +33,9 @@ using boost::redis::logger; using boost::redis::connection; using boost::redis::usage; using boost::redis::error; +using namespace std::chrono_literals; + +namespace boost::redis { std::ostream& operator<<(std::ostream& os, usage const& u) { @@ -43,90 +49,106 @@ std::ostream& operator<<(std::ostream& os, usage const& u) return os; } -auto push_consumer(std::shared_ptr conn, int expected) -> net::awaitable +} // namespace boost::redis + +namespace { + +auto push_consumer(connection& conn, int expected) -> net::awaitable { int c = 0; for (error_code ec;;) { - conn->receive(ec); + conn.receive(ec); if (ec == error::sync_receive_push_failed) { ec = {}; - co_await conn->async_receive(redirect_error(net::use_awaitable, ec)); + co_await conn.async_receive(net::redirect_error(ec)); } else if (!ec) { //std::cout << "Skipping suspension." << std::endl; } if (ec) { - BOOST_TEST(false); - std::cout << "push_consumer error: " << ec.message() << std::endl; + BOOST_TEST(false, "push_consumer error: " << ec.message()); co_return; } if (++c == expected) break; } - conn->cancel(); + conn.cancel(); } -auto echo_session(std::shared_ptr conn, std::shared_ptr pubs, int n) - -> net::awaitable +auto echo_session(connection& conn, const request& pubs, int n) -> net::awaitable { for (auto i = 0; i < n; ++i) - co_await conn->async_exec(*pubs, ignore, net::deferred); + co_await conn.async_exec(pubs, ignore); +} + +void rethrow_on_error(std::exception_ptr exc) +{ + if (exc) + std::rethrow_exception(exc); } -auto async_echo_stress(std::shared_ptr conn) -> net::awaitable +BOOST_AUTO_TEST_CASE(echo_stress) { - auto ex = co_await net::this_coro::executor; + // Setup + net::io_context ctx; + connection conn{ctx}; auto cfg = make_test_config(); cfg.health_check_interval = std::chrono::seconds::zero(); - run( - conn, - cfg, - boost::asio::error::operation_aborted, - boost::redis::operation::receive, - boost::redis::logger::level::crit); - - request req; - req.push("SUBSCRIBE", "channel"); - co_await conn->async_exec(req, ignore, net::deferred); // Number of coroutines that will send pings sharing the same // connection to redis. - int const sessions = 150; + constexpr int sessions = 150; // The number of pings that will be sent by each session. - int const msgs = 200; + constexpr int msgs = 200; // The number of publishes that will be sent by each session with // each message. - int const n_pubs = 25; + constexpr int n_pubs = 25; // This is the total number of pushes we will receive. - int total_pushes = sessions * msgs * n_pubs + 1; + constexpr int total_pushes = sessions * msgs * n_pubs + 1; - auto pubs = std::make_shared(); - pubs->push("PING"); + request pubs; + pubs.push("PING"); for (int i = 0; i < n_pubs; ++i) - pubs->push("PUBLISH", "channel", "payload"); + pubs.push("PUBLISH", "channel", "payload"); - // Op that will consume the pushes counting down until all expected - // pushes have been received. - net::co_spawn(ex, push_consumer(conn, total_pushes), net::detached); + // Run the connection + bool run_finished = false, subscribe_finished = false; + conn.async_run(cfg, logger{logger::level::crit}, [&run_finished](error_code ec) { + run_finished = true; + BOOST_TEST(ec == net::error::operation_aborted); + std::clog << "async_run finished" << std::endl; + }); - for (int i = 0; i < sessions; ++i) - net::co_spawn(ex, echo_session(conn, pubs, msgs), net::detached); -} + // Subscribe, then launch the coroutines + request req; + req.push("SUBSCRIBE", "channel"); + conn.async_exec(req, ignore, [&](error_code ec, std::size_t) { + subscribe_finished = true; + BOOST_TEST(ec == error_code()); -BOOST_AUTO_TEST_CASE(echo_stress) -{ - net::io_context ioc; - auto conn = std::make_shared(ioc); - net::co_spawn(ioc, async_echo_stress(conn), net::detached); - ioc.run(); + // Op that will consume the pushes counting down until all expected + // pushes have been received. + net::co_spawn(ctx, push_consumer(conn, total_pushes), rethrow_on_error); + + for (int i = 0; i < sessions; ++i) + net::co_spawn(ctx, echo_session(conn, pubs, msgs), rethrow_on_error); + }); - std::cout << "-------------------\n" << conn->get_usage() << std::endl; + // Run the test + ctx.run_for(2 * test_timeout); + BOOST_TEST(run_finished); + BOOST_TEST(subscribe_finished); + + // Print statistics + std::cout << "-------------------\n" << conn.get_usage() << std::endl; } +} // namespace + #else -BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } +BOOST_AUTO_TEST_CASE(dummy) { } #endif diff --git a/test/test_conn_exec.cpp b/test/test_conn_exec.cpp index f81fcd58..e02c3bce 100644 --- a/test/test_conn_exec.cpp +++ b/test/test_conn_exec.cpp @@ -8,8 +8,8 @@ #include #include -#include +#include #include #define BOOST_TEST_MODULE conn_exec #include @@ -18,8 +18,8 @@ #include -// TODO: Test whether HELLO won't be inserted passt commands that have -// been already writen. +// TODO: Test whether HELLO won't be inserted past commands that have +// been already written. // TODO: Test async_exec with empty request e.g. hgetall with an empty // container. @@ -31,6 +31,10 @@ using boost::redis::ignore; using boost::redis::operation; using boost::redis::request; using boost::redis::response; +using boost::system::error_code; +using namespace std::chrono_literals; + +namespace { // Sends three requests where one of them has a hello with a priority // set, which means it should be executed first. @@ -57,19 +61,19 @@ BOOST_AUTO_TEST_CASE(hello_priority) bool seen2 = false; bool seen3 = false; - conn->async_exec(req1, ignore, [&](auto ec, auto) { + conn->async_exec(req1, ignore, [&](error_code ec, std::size_t) { // Second callback to the called. std::cout << "req1" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); BOOST_TEST(!seen2); BOOST_TEST(seen3); seen1 = true; }); - conn->async_exec(req2, ignore, [&](auto ec, auto) { + conn->async_exec(req2, ignore, [&](error_code ec, std::size_t) { // Last callback to the called. std::cout << "req2" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); BOOST_TEST(seen1); BOOST_TEST(seen3); seen2 = true; @@ -77,17 +81,20 @@ BOOST_AUTO_TEST_CASE(hello_priority) conn->cancel(operation::reconnection); }); - conn->async_exec(req3, ignore, [&](auto ec, auto) { + conn->async_exec(req3, ignore, [&](error_code ec, std::size_t) { // Callback that will be called first. std::cout << "req3" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); BOOST_TEST(!seen1); BOOST_TEST(!seen2); seen3 = true; }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(seen1); + BOOST_TEST(seen2); + BOOST_TEST(seen3); } // Tries to receive a string in an int and gets an error. @@ -101,14 +108,17 @@ BOOST_AUTO_TEST_CASE(wrong_response_data_type) net::io_context ioc; auto conn = std::make_shared(ioc); + bool finished = false; - conn->async_exec(req, resp, [conn](auto ec, auto) { - BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); + conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) { + BOOST_TEST(ec == boost::redis::error::not_a_number); conn->cancel(operation::reconnection); + finished = true; }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); } BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) @@ -119,12 +129,15 @@ BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) net::io_context ioc; auto conn = std::make_shared(ioc); - conn->async_exec(req, ignore, [conn](auto ec, auto) { - BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected); + bool finished = false; + conn->async_exec(req, ignore, [conn, &finished](error_code ec, std::size_t) { + BOOST_TEST(ec, boost::redis::error::not_connected); conn->cancel(); + finished = true; }); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); } BOOST_AUTO_TEST_CASE(correct_database) @@ -141,19 +154,25 @@ BOOST_AUTO_TEST_CASE(correct_database) generic_response resp; - conn->async_exec(req, resp, [&](auto ec, auto n) { - BOOST_TEST(!ec); + bool exec_finished = false, run_finished = false; + + conn->async_exec(req, resp, [&](error_code ec, std::size_t n) { + BOOST_TEST(ec == error_code()); std::clog << "async_exec has completed: " << n << std::endl; conn->cancel(); + exec_finished = true; }); - conn->async_run(cfg, {}, [](auto) { + conn->async_run(cfg, {}, [&run_finished](error_code) { std::clog << "async_run has exited." << std::endl; + run_finished = true; }); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST_REQUIRE(exec_finished); + BOOST_TEST_REQUIRE(run_finished); - assert(!resp.value().empty()); + BOOST_TEST_REQUIRE(!resp.value().empty()); auto const& value = resp.value().front().value; auto const pos = value.find("db="); auto const index_str = value.substr(pos + 3, 1); @@ -179,22 +198,22 @@ BOOST_AUTO_TEST_CASE(large_number_of_concurrent_requests_issue_170) cfg.health_check_interval = std::chrono::seconds(0); conn->async_run(cfg, {}, net::detached); - int counter = 0; - int const repeat = 8000; + constexpr int repeat = 8000; + int remaining = repeat; for (int i = 0; i < repeat; ++i) { auto req = std::make_shared(); req->push("PING", payload); - conn->async_exec(*req, ignore, [req, &counter, conn](auto ec, auto) { - BOOST_TEST(!ec); - if (++counter == repeat) + conn->async_exec(*req, ignore, [req, &remaining, conn](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); + if (--remaining == 0) conn->cancel(); }); } - ioc.run(); + ioc.run_for(test_timeout); - BOOST_CHECK_EQUAL(counter, repeat); + BOOST_TEST(remaining == 0); } BOOST_AUTO_TEST_CASE(exec_any_adapter) @@ -208,13 +227,19 @@ BOOST_AUTO_TEST_CASE(exec_any_adapter) auto conn = std::make_shared(ioc); - conn->async_exec(req, boost::redis::any_adapter(res), [&](auto ec, auto) { - BOOST_TEST(!ec); + bool finished = false; + + conn->async_exec(req, boost::redis::any_adapter(res), [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->cancel(); + finished = true; }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST_REQUIRE(finished); BOOST_TEST(std::get<0>(res).value() == "PONG"); } + +} // namespace \ No newline at end of file diff --git a/test/test_conn_exec_cancel.cpp b/test/test_conn_exec_cancel.cpp index 99173f4e..5618c2e8 100644 --- a/test/test_conn_exec_cancel.cpp +++ b/test/test_conn_exec_cancel.cpp @@ -7,19 +7,19 @@ #include #include + +#include #define BOOST_TEST_MODULE conn_exec_cancel #include #include #include "common.hpp" -#include - #ifdef BOOST_ASIO_HAS_CO_AWAIT #include // NOTE1: I have observed that if hello and -// blpop are sent toguether, Redis will send the response of hello +// blpop are sent together, Redis will send the response of hello // right away, not waiting for blpop. namespace net = boost::asio; @@ -36,6 +36,8 @@ using boost::redis::logger; using boost::redis::connection; using namespace std::chrono_literals; +namespace { + auto implicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; @@ -48,7 +50,7 @@ auto implicit_cancel_of_req_written() -> net::awaitable // See NOTE1. request req0; req0.push("PING"); - co_await conn->async_exec(req0, ignore, net::use_awaitable); + co_await conn->async_exec(req0, ignore); // Will be cancelled after it has been written but before the // response arrives. @@ -66,15 +68,13 @@ auto implicit_cancel_of_req_written() -> net::awaitable // I have observed this produces terminal cancellation so it can't // be ignored, an error is expected. - BOOST_CHECK_EQUAL(ec1, net::error::operation_aborted); - BOOST_TEST(!ec2); + BOOST_TEST(ec1 == net::error::operation_aborted); + BOOST_TEST(ec2 == error_code()); } BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) { - net::io_context ioc; - net::co_spawn(ioc, implicit_cancel_of_req_written(), net::detached); - ioc.run(); + run_coroutine_test(implicit_cancel_of_req_written()); } BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) @@ -92,12 +92,15 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) req1.get_config().cancel_if_unresponded = true; req1.push("BLPOP", "any", 0); - auto c1 = [&](auto ec, auto) { + bool finished = false; + + auto c1 = [&](error_code ec, std::size_t) { BOOST_CHECK_EQUAL(ec, net::error::operation_aborted); + finished = true; }; - auto c0 = [&](auto ec, auto) { - BOOST_TEST(!ec); + auto c0 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c1); }; @@ -109,15 +112,18 @@ BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) net::steady_timer st{ioc}; st.expires_after(std::chrono::seconds{1}); - st.async_wait([&](auto ec) { - BOOST_TEST(!ec); + st.async_wait([&](error_code ec) { + BOOST_TEST(ec == error_code()); conn->cancel(operation::run); conn->cancel(operation::reconnection); }); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); } +} // namespace + #else -BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } +BOOST_AUTO_TEST_CASE(dummy) { } #endif diff --git a/test/test_conn_exec_cancel2.cpp b/test/test_conn_exec_cancel2.cpp index ba464443..7b745fc6 100644 --- a/test/test_conn_exec_cancel2.cpp +++ b/test/test_conn_exec_cancel2.cpp @@ -6,9 +6,8 @@ #include -#include +#include #define BOOST_TEST_MODULE conn_exec_cancel -#include #include #include "common.hpp" @@ -16,7 +15,6 @@ #include #ifdef BOOST_ASIO_HAS_CO_AWAIT -#include // NOTE1: Sends hello separately. I have observed that if hello and // blpop are sent toguether, Redis will send the response of hello @@ -25,7 +23,6 @@ namespace net = boost::asio; using error_code = boost::system::error_code; -using namespace net::experimental::awaitable_operators; using boost::redis::operation; using boost::redis::request; using boost::redis::response; @@ -37,6 +34,8 @@ using boost::redis::logger; using boost::redis::connection; using namespace std::chrono_literals; +namespace { + auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable { auto ex = co_await net::this_coro::executor; @@ -52,46 +51,45 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable // See NOTE1. request req0; req0.push("PING", "async_ignore_explicit_cancel_of_req_written"); - co_await conn->async_exec(req0, gresp, net::use_awaitable); + co_await conn->async_exec(req0, gresp); request req1; req1.push("BLPOP", "any", 3); bool seen = false; - conn->async_exec(req1, gresp, [&](auto ec, auto) mutable { - // No error should occur since the cancelation should be - // ignored. + conn->async_exec(req1, gresp, [&](error_code ec, std::size_t) { + // No error should occur since the cancellation should be ignored std::cout << "async_exec (1): " << ec.message() << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); seen = true; }); // Will complete while BLPOP is pending. - boost::system::error_code ec1; - co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1)); + error_code ec; + co_await st.async_wait(net::redirect_error(ec)); conn->cancel(operation::exec); - BOOST_TEST(!ec1); + BOOST_TEST(ec == error_code()); - request req3; - req3.push("PING"); + request req2; + req2.push("PING"); // Test whether the connection remains usable after a call to // cancel(exec). - co_await conn->async_exec(req3, gresp, net::redirect_error(net::use_awaitable, ec1)); + co_await conn->async_exec(req2, gresp, net::redirect_error(ec)); conn->cancel(); - BOOST_TEST(!ec1); + BOOST_TEST(ec == error_code()); BOOST_TEST(seen); } BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) { - net::io_context ioc; - net::co_spawn(ioc, async_ignore_explicit_cancel_of_req_written(), net::detached); - ioc.run(); + run_coroutine_test(async_ignore_explicit_cancel_of_req_written()); } +} // namespace + #else -BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } +BOOST_AUTO_TEST_CASE(dummy) { } #endif diff --git a/test/test_conn_exec_error.cpp b/test/test_conn_exec_error.cpp index 96a3214e..183ce509 100644 --- a/test/test_conn_exec_error.cpp +++ b/test/test_conn_exec_error.cpp @@ -5,31 +5,31 @@ */ #include -#include -#include #define BOOST_TEST_MODULE conn_exec_error #include #include "common.hpp" +#include #include namespace net = boost::asio; namespace redis = boost::redis; namespace resp3 = redis::resp3; using error_code = boost::system::error_code; -using connection = boost::redis::connection; +using boost::redis::connection; using boost::redis::request; using boost::redis::response; using boost::redis::generic_response; using boost::redis::ignore; using boost::redis::ignore_t; using boost::redis::error; -using boost::redis::logger; using boost::redis::operation; using namespace std::chrono_literals; +namespace { + BOOST_AUTO_TEST_CASE(no_ignore_error) { request req; @@ -41,15 +41,20 @@ BOOST_AUTO_TEST_CASE(no_ignore_error) auto conn = std::make_shared(ioc); - conn->async_exec(req, ignore, [&](auto ec, auto) { - BOOST_CHECK_EQUAL(ec, error::resp3_simple_error); + bool exec_finished = false; + + conn->async_exec(req, ignore, [&](error_code ec, std::size_t) { + exec_finished = true; + BOOST_TEST(ec == error::resp3_simple_error); conn->cancel(operation::run); conn->cancel(operation::reconnection); }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(exec_finished); } BOOST_AUTO_TEST_CASE(has_diagnostic) @@ -68,19 +73,22 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) auto conn = std::make_shared(ioc); response resp; - conn->async_exec(req, resp, [&](auto ec, auto) { - BOOST_TEST(!ec); + bool exec_finished = false; + conn->async_exec(req, resp, [&](error_code ec, std::size_t) { + exec_finished = true; + + BOOST_TEST(ec == error_code()); // HELLO BOOST_TEST(std::get<0>(resp).has_error()); - BOOST_CHECK_EQUAL(std::get<0>(resp).error().data_type, resp3::type::simple_error); + BOOST_TEST(std::get<0>(resp).error().data_type == resp3::type::simple_error); auto const diag = std::get<0>(resp).error().diagnostic; BOOST_TEST(!std::empty(diag)); std::cout << "has_diagnostic: " << diag << std::endl; // PING BOOST_TEST(std::get<1>(resp).has_value()); - BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "Barra do Una"); + BOOST_TEST(std::get<1>(resp).value() == "Barra do Una"); conn->cancel(operation::run); conn->cancel(operation::reconnection); @@ -88,7 +96,9 @@ BOOST_AUTO_TEST_CASE(has_diagnostic) run(conn); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(exec_finished); } BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) @@ -109,24 +119,28 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) net::io_context ioc; auto conn = std::make_shared(ioc); - auto c2 = [&](auto ec, auto) { - BOOST_TEST(!ec); + bool c2_called = false, c1_called = false; + + auto c2 = [&](error_code ec, std::size_t) { + c2_called = true; + BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<0>(resp2).has_value()); - BOOST_CHECK_EQUAL(std::get<0>(resp2).value(), "req2-msg1"); + BOOST_TEST(std::get<0>(resp2).value() == "req2-msg1"); conn->cancel(operation::run); conn->cancel(operation::reconnection); }; - auto c1 = [&](auto ec, auto) { - BOOST_TEST(!ec); + auto c1 = [&](error_code ec, std::size_t) { + c1_called = true; + BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<2>(resp1).has_error()); - BOOST_CHECK_EQUAL(std::get<2>(resp1).error().data_type, resp3::type::simple_error); + BOOST_TEST(std::get<2>(resp1).error().data_type == resp3::type::simple_error); auto const diag = std::get<2>(resp1).error().diagnostic; BOOST_TEST(!std::empty(diag)); std::cout << "resp3_error_in_cmd_pipeline: " << diag << std::endl; BOOST_TEST(std::get<3>(resp1).has_value()); - BOOST_CHECK_EQUAL(std::get<3>(resp1).value(), "req1-msg3"); + BOOST_TEST(std::get<3>(resp1).value() == "req1-msg3"); conn->async_exec(req2, resp2, c2); }; @@ -134,7 +148,10 @@ BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) conn->async_exec(req1, resp1, c1); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); } BOOST_AUTO_TEST_CASE(error_in_transaction) @@ -163,8 +180,11 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) auto conn = std::make_shared(ioc); - conn->async_exec(req, resp, [&](auto ec, auto) { - BOOST_TEST(!ec); + bool finished = false; + + conn->async_exec(req, resp, [&](error_code ec, std::size_t) { + finished = true; + BOOST_TEST(ec == error_code()); BOOST_TEST(std::get<0>(resp).has_value()); BOOST_TEST(std::get<1>(resp).has_value()); @@ -175,23 +195,22 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) // Test errors in the pipeline commands. BOOST_TEST(std::get<0>(std::get<5>(resp).value()).has_value()); - BOOST_CHECK_EQUAL(std::get<0>(std::get<5>(resp).value()).value(), "PONG"); + BOOST_TEST(std::get<0>(std::get<5>(resp).value()).value() == "PONG"); // The ping in the transaction that should be an error. BOOST_TEST(std::get<1>(std::get<5>(resp).value()).has_error()); - BOOST_CHECK_EQUAL( - std::get<1>(std::get<5>(resp).value()).error().data_type, - resp3::type::simple_error); + BOOST_TEST( + std::get<1>(std::get<5>(resp).value()).error().data_type == resp3::type::simple_error); auto const diag = std::get<1>(std::get<5>(resp).value()).error().diagnostic; BOOST_TEST(!std::empty(diag)); // The ping thereafter in the transaction should not be an error. BOOST_TEST(std::get<2>(std::get<5>(resp).value()).has_value()); - //BOOST_CHECK_EQUAL(std::get<2>(std::get<4>(resp).value()).value(), "PONG"); + BOOST_TEST(std::get<2>(std::get<5>(resp).value()).value() == "PONG"); // The command right after the pipeline should be successful. BOOST_TEST(std::get<6>(resp).has_value()); - BOOST_CHECK_EQUAL(std::get<6>(resp).value(), "PONG"); + BOOST_TEST(std::get<6>(resp).value() == "PONG"); conn->cancel(operation::run); conn->cancel(operation::reconnection); @@ -199,7 +218,9 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) run(conn); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(finished); } // This test is important because a SUBSCRIBE command has no response @@ -212,8 +233,8 @@ BOOST_AUTO_TEST_CASE(error_in_transaction) // even more complex. For example, without a ping, we might get the // sequence HELLO + SUBSCRIBE + PING where the hello and ping are // automatically sent by the implementation. In this case, if the -// subscribe synthax is wrong, redis will send a response, which does -// not exist on success. That response will be interprested as the +// subscribe syntax is wrong, redis will send a response, which does +// not exist on success. That response will be interpreted as the // response to the PING command that comes thereafter and won't be // forwarded to the receive_op, resulting in a difficult to handle // error. @@ -223,19 +244,23 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) req1.push("PING"); request req2; - req2.push("SUBSCRIBE"); // Wrong command synthax. + req2.push("SUBSCRIBE"); // Wrong command syntax. net::io_context ioc; auto conn = std::make_shared(ioc); - auto c2 = [&](auto ec, auto) { + bool c1_called = false, c2_called = false, c3_called = false; + + auto c2 = [&](error_code ec, std::size_t) { + c2_called = true; std::cout << "async_exec: subscribe" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); }; - auto c1 = [&](auto ec, auto) { + auto c1 = [&](error_code ec, std::size_t) { + c1_called = true; std::cout << "async_exec: hello" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c2); }; @@ -244,7 +269,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) generic_response gresp; conn->set_receive_response(gresp); - auto c3 = [&](auto ec, auto) { + auto c3 = [&](error_code ec, std::size_t) { + c3_called = true; std::cout << "async_receive" << std::endl; BOOST_TEST(!ec); BOOST_TEST(gresp.has_error()); @@ -259,5 +285,11 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax) run(conn); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); + BOOST_TEST(c3_called); } + +} // namespace \ No newline at end of file diff --git a/test/test_conn_exec_retry.cpp b/test/test_conn_exec_retry.cpp index 10708d66..a01c63dd 100644 --- a/test/test_conn_exec_retry.cpp +++ b/test/test_conn_exec_retry.cpp @@ -6,8 +6,9 @@ #include -#include -#include +#include + +#include #define BOOST_TEST_MODULE conn_exec_retry #include @@ -27,6 +28,8 @@ using boost::redis::logger; using boost::redis::config; using namespace std::chrono_literals; +namespace { + BOOST_AUTO_TEST_CASE(request_retry_false) { request req0; @@ -46,31 +49,40 @@ BOOST_AUTO_TEST_CASE(request_retry_false) auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; + + bool timer_finished = false, c2_called = false, c1_called = false, c0_called = false, + run_finished = false; + st.expires_after(std::chrono::seconds{1}); - st.async_wait([&](auto) { + st.async_wait([&](error_code ec) { // 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 it has already been written so // cancel_on_connection_lost does not apply. + timer_finished = true; + BOOST_TEST(ec == error_code()); conn->cancel(operation::run); conn->cancel(operation::reconnection); std::cout << "async_wait" << std::endl; }); - auto c2 = [&](auto ec, auto) { + auto c2 = [&](error_code ec, std::size_t) { + c2_called = true; std::cout << "c2" << std::endl; - BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted); + BOOST_TEST(ec == net::error::operation_aborted); }; - auto c1 = [&](auto ec, auto) { + auto c1 = [&](error_code ec, std::size_t) { + c1_called = true; std::cout << "c1" << std::endl; - BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted); + BOOST_TEST(ec == net::error::operation_aborted); }; - auto c0 = [&](auto ec, auto) { + auto c0 = [&](error_code ec, std::size_t) { + c0_called = true; std::cout << "c0" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c1); conn->async_exec(req2, ignore, c2); }; @@ -78,15 +90,19 @@ BOOST_AUTO_TEST_CASE(request_retry_false) conn->async_exec(req0, ignore, c0); auto cfg = make_test_config(); - conn->async_run( - cfg, - {boost::redis::logger::level::debug}, - [&](boost::system::error_code const& ec) { - std::cout << "async_run: " << ec.message() << std::endl; - conn->cancel(); - }); - - ioc.run(); + conn->async_run(cfg, {boost::redis::logger::level::debug}, [&](error_code ec) { + run_finished = true; + std::cout << "async_run: " << ec.message() << std::endl; + conn->cancel(); + }); + + ioc.run_for(test_timeout); + + BOOST_TEST(timer_finished); + BOOST_TEST(c0_called); + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); + BOOST_TEST(run_finished); } BOOST_AUTO_TEST_CASE(request_retry_true) @@ -113,32 +129,42 @@ BOOST_AUTO_TEST_CASE(request_retry_true) auto conn = std::make_shared(ioc); net::steady_timer st{ioc}; + + bool timer_finished = false, c0_called = false, c1_called = false, c2_called = false, + c3_called = false, run_finished = false; + st.expires_after(std::chrono::seconds{1}); - st.async_wait([&](auto) { + st.async_wait([&](error_code ec) { // Cancels the request before receiving the response. This // should cause the third request to not complete with error // since it has cancel_if_unresponded = true and cancellation // comes after it was written. + timer_finished = true; + BOOST_TEST(ec == error_code()); conn->cancel(operation::run); }); - auto c3 = [&](auto ec, auto) { + auto c3 = [&](error_code ec, std::size_t) { + c3_called = true; std::cout << "c3: " << ec.message() << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); conn->cancel(); }; - auto c2 = [&](auto ec, auto) { - BOOST_TEST(!ec); + auto c2 = [&](error_code ec, std::size_t) { + c2_called = true; + BOOST_TEST(ec == error_code()); conn->async_exec(req3, ignore, c3); }; - auto c1 = [](auto ec, auto) { - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + auto c1 = [&](error_code ec, std::size_t) { + c1_called = true; + BOOST_TEST(ec == net::error::operation_aborted); }; - auto c0 = [&](auto ec, auto) { - BOOST_TEST(!ec); + auto c0 = [&](error_code ec, std::size_t) { + c0_called = true; + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c1); conn->async_exec(req2, ignore, c2); }; @@ -147,10 +173,20 @@ BOOST_AUTO_TEST_CASE(request_retry_true) auto cfg = make_test_config(); cfg.health_check_interval = 5s; - conn->async_run(cfg, {}, [&](auto ec) { + conn->async_run(cfg, {}, [&](error_code ec) { + run_finished = true; std::cout << ec.message() << std::endl; - BOOST_TEST(!!ec); + BOOST_TEST(ec != error_code()); }); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(timer_finished); + BOOST_TEST(c0_called); + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); + BOOST_TEST(c3_called); + BOOST_TEST(run_finished); } + +} // namespace \ No newline at end of file diff --git a/test/test_conn_push.cpp b/test/test_conn_push.cpp index 397184d2..e4eae755 100644 --- a/test/test_conn_push.cpp +++ b/test/test_conn_push.cpp @@ -7,24 +7,23 @@ #include #include -#include -#include -#include +#include #include -#define BOOST_TEST_MODULE conn - push + +#define BOOST_TEST_MODULE conn_push #include #include "common.hpp" +#include #include namespace net = boost::asio; namespace redis = boost::redis; using boost::redis::operation; -using connection = boost::redis::connection; -using error_code = boost::system::error_code; -using net::as_tuple; +using boost::redis::connection; +using boost::system::error_code; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; @@ -33,6 +32,8 @@ using boost::system::error_code; using boost::redis::logger; using namespace std::chrono_literals; +namespace { + BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) { request req1; @@ -50,17 +51,22 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) auto conn = std::make_shared(ioc); - auto c3 = [](auto ec, auto...) { + bool push_received = false, c1_called = false, c2_called = false, c3_called = false; + + auto c3 = [&](error_code ec, std::size_t) { + c3_called = true; std::cout << "c3: " << ec.message() << std::endl; }; - auto c2 = [&, conn](auto ec, auto...) { - BOOST_TEST(!ec); + auto c2 = [&, conn](error_code ec, std::size_t) { + c2_called = true; + BOOST_TEST(ec == error_code()); conn->async_exec(req3, ignore, c3); }; - auto c1 = [&, conn](auto ec, auto...) { - BOOST_TEST(!ec); + auto c1 = [&, conn](error_code ec, std::size_t) { + c1_called = true; + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c2); }; @@ -68,17 +74,19 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) run(conn, make_test_config(), {}); - bool push_received = false; - conn->async_receive([&, conn](auto ec, auto) { + conn->async_receive([&, conn](error_code ec, std::size_t) { std::cout << "async_receive" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); push_received = true; conn->cancel(); }); - ioc.run(); + ioc.run_for(test_timeout); BOOST_TEST(push_received); + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); + BOOST_TEST(c3_called); } BOOST_AUTO_TEST_CASE(push_received1) @@ -94,17 +102,19 @@ BOOST_AUTO_TEST_CASE(push_received1) req.push("SUBSCRIBE", "channel1"); req.push("SUBSCRIBE", "channel2"); - conn->async_exec(req, ignore, [conn](auto ec, auto) { + bool push_received = false, exec_finished = false; + + conn->async_exec(req, ignore, [&, conn](error_code ec, std::size_t) { + exec_finished = true; std::cout << "async_exec" << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); }); - bool push_async_received = false; - conn->async_receive([&, conn](auto ec, auto) { + conn->async_receive([&, conn](error_code ec, std::size_t) { + push_received = true; std::cout << "(1) async_receive" << std::endl; - BOOST_TEST(!ec); - push_async_received = true; + BOOST_TEST(ec == error_code()); // Receives the second push synchronously. error_code ec2; @@ -124,9 +134,10 @@ BOOST_AUTO_TEST_CASE(push_received1) }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); - BOOST_TEST(push_async_received); + BOOST_TEST(exec_finished); + BOOST_TEST(push_received); } BOOST_AUTO_TEST_CASE(push_filtered_out) @@ -141,39 +152,30 @@ BOOST_AUTO_TEST_CASE(push_filtered_out) req.push("QUIT"); response resp; - conn->async_exec(req, resp, [conn](auto ec, auto) { - BOOST_TEST(!ec); + + bool exec_finished = false, push_received = false; + + conn->async_exec(req, resp, [conn, &exec_finished](error_code ec, std::size_t) { + exec_finished = true; + BOOST_TEST(ec == error_code()); }); - conn->async_receive([&, conn](auto ec, auto) { - BOOST_TEST(!ec); + conn->async_receive([&, conn](error_code ec, std::size_t) { + push_received = true; + BOOST_TEST(ec == error_code()); conn->cancel(operation::reconnection); }); run(conn); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(exec_finished); + BOOST_TEST(push_received); BOOST_CHECK_EQUAL(std::get<1>(resp).value(), "PONG"); BOOST_CHECK_EQUAL(std::get<2>(resp).value(), "OK"); } -#ifdef BOOST_ASIO_HAS_CO_AWAIT -net::awaitable push_consumer1(std::shared_ptr conn, bool& push_received) -{ - { - auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable)); - BOOST_TEST(!ec); - } - - { - auto [ec, ev] = co_await conn->async_receive(as_tuple(net::use_awaitable)); - BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); - } - - push_received = true; -} - struct response_error_tag { }; response_error_tag error_tag_obj; @@ -208,31 +210,43 @@ BOOST_AUTO_TEST_CASE(test_push_adapter) conn->set_receive_response(error_tag_obj); - conn->async_receive([&, conn](auto ec, auto) { + bool push_received = false, exec_finished = false, run_finished = false; + + conn->async_receive([&, conn](error_code ec, std::size_t) { BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_cancelled); conn->cancel(operation::reconnection); + push_received = true; }); - conn->async_exec(req, ignore, [](auto ec, auto) { + conn->async_exec(req, ignore, [&exec_finished](error_code ec, std::size_t) { BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); + exec_finished = true; }); auto cfg = make_test_config(); - conn->async_run(cfg, {}, [](auto ec) { + conn->async_run(cfg, {}, [&run_finished](error_code ec) { BOOST_CHECK_EQUAL(ec, redis::error::incompatible_size); + run_finished = true; }); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(push_received); + BOOST_TEST(exec_finished); + BOOST_TEST(run_finished); // TODO: Reset the ioc reconnect and send a quit to ensure // reconnection is possible after an error. } -net::awaitable push_consumer3(std::shared_ptr conn) +void launch_push_consumer(std::shared_ptr conn) { - for (;;) { - co_await conn->async_receive(net::use_awaitable); - } + conn->async_receive([conn](error_code ec, std::size_t) { + if (ec) { + BOOST_TEST(ec == net::experimental::error::channel_cancelled); + return; + } + launch_push_consumer(conn); + }); } BOOST_AUTO_TEST_CASE(many_subscribers) @@ -256,60 +270,65 @@ BOOST_AUTO_TEST_CASE(many_subscribers) net::io_context ioc; auto conn = std::make_shared(ioc); - auto c11 = [&](auto ec, auto...) { - std::cout << "quit sent: " << ec.message() << std::endl; + bool finished = false; + + auto c11 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->cancel(operation::reconnection); + finished = true; }; - auto c10 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c10 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req3, ignore, c11); }; - auto c9 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c9 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c10); }; - auto c8 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c8 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c9); }; - auto c7 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c7 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c8); }; - auto c6 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c6 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c7); }; - auto c5 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c5 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c6); }; - auto c4 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c4 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c5); }; - auto c3 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c3 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c4); }; - auto c2 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c2 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c3); }; - auto c1 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c1 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c2); }; - auto c0 = [&](auto ec, auto...) { - BOOST_TEST(!ec); + auto c0 = [&](error_code ec, std::size_t) { + BOOST_TEST(ec == error_code()); conn->async_exec(req1, ignore, c1); }; conn->async_exec(req0, ignore, c0); + launch_push_consumer(conn); run(conn, make_test_config(), {}); - net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached); - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); } -#endif + +} // namespace \ No newline at end of file diff --git a/test/test_conn_quit.cpp b/test/test_conn_quit.cpp index ca345f57..efad6d7b 100644 --- a/test/test_conn_quit.cpp +++ b/test/test_conn_quit.cpp @@ -6,7 +6,9 @@ #include -#include +#include + +#include #define BOOST_TEST_MODULE conn_quit #include @@ -43,20 +45,25 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits) req3.get_config().cancel_if_not_connected = true; req3.push("PING"); - auto c3 = [](auto ec, auto) { + bool c1_called = false, c2_called = false, c3_called = false; + + auto c3 = [&](error_code ec, std::size_t) { + c3_called = true; std::clog << "c3: " << ec.message() << std::endl; - BOOST_CHECK_EQUAL(ec, boost::asio::error::operation_aborted); + BOOST_TEST(ec == net::error::operation_aborted); }; - auto c2 = [&](auto ec, auto) { + auto c2 = [&](error_code ec, std::size_t) { + c2_called = true; std::clog << "c2: " << ec.message() << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); conn->async_exec(req3, ignore, c3); }; - auto c1 = [&](auto ec, auto) { + auto c1 = [&](error_code ec, std::size_t) { + c1_called = true; std::cout << "c1: " << ec.message() << std::endl; - BOOST_TEST(!ec); + BOOST_TEST(ec == error_code()); conn->async_exec(req2, ignore, c2); }; @@ -69,5 +76,9 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits) cfg.reconnect_wait_interval = 0s; run(conn, cfg); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(c1_called); + BOOST_TEST(c2_called); + BOOST_TEST(c3_called); } diff --git a/test/test_conn_reconnect.cpp b/test/test_conn_reconnect.cpp index eda7ddde..f88f650c 100644 --- a/test/test_conn_reconnect.cpp +++ b/test/test_conn_reconnect.cpp @@ -4,9 +4,11 @@ * accompanying file LICENSE.txt) */ +#include #include -#include +#include + #define BOOST_TEST_MODULE conn_reconnect #include @@ -26,41 +28,52 @@ using boost::redis::logger; using boost::redis::operation; using boost::redis::connection; using namespace std::chrono_literals; - using namespace boost::asio::experimental::awaitable_operators; +namespace { + net::awaitable test_reconnect_impl() { auto ex = co_await net::this_coro::executor; - request req; - req.push("QUIT"); + request quit_req; + quit_req.push("QUIT"); + + // cancel_on_connection_lost is required because async_run might detect the failure + // after the 2nd async_exec is issued + request regular_req; + regular_req.push("GET", "mykey"); + regular_req.get_config().cancel_on_connection_lost = false; auto conn = std::make_shared(ex); - run(conn); - - int i = 0; - for (; i < 5; ++i) { - error_code ec1, ec2; - auto cfg = make_test_config(); - logger l; - co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)); - //BOOST_TEST(!ec); - std::cout << "test_reconnect: " << i << " " << ec2.message() << " " << ec1.message() - << std::endl; + auto cfg = make_test_config(); + cfg.reconnect_wait_interval = 100ms; // make the test run faster + run(conn, std::move(cfg)); + + for (int i = 0; i < 3; ++i) { + BOOST_TEST_CONTEXT("i=" << i) + { + // Issue a quit request, which will cause the server to close the connection. + // This request will fail + error_code ec; + co_await conn->async_exec(quit_req, ignore, net::redirect_error(ec)); + BOOST_TEST(ec == error_code()); + + // This should trigger reconnection, which will now succeed. + // We should be able to execute requests successfully now. + // TODO: this is currently unreliable - find our why and fix + co_await conn->async_exec(regular_req, ignore, net::redirect_error(ec)); + // BOOST_TEST(ec == error_code()); + } } conn->cancel(); - BOOST_CHECK_EQUAL(i, 5); - co_return; } // Test whether the client works after a reconnect. BOOST_AUTO_TEST_CASE(test_reconnect) { - net::io_context ioc; - net::co_spawn(ioc, test_reconnect_impl(), net::detached); - ioc.run(); + run_coroutine_test(test_reconnect_impl(), 5 * test_timeout); } auto async_test_reconnect_timeout() -> net::awaitable @@ -104,10 +117,11 @@ auto async_test_reconnect_timeout() -> net::awaitable BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) { - net::io_context ioc; - net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached); - ioc.run(); + run_coroutine_test(async_test_reconnect_timeout()); } + +} // namespace + #else -BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } +BOOST_AUTO_TEST_CASE(dummy) { } #endif diff --git a/test/test_conversions.cpp b/test/test_conversions.cpp index d92e431c..ac684506 100644 --- a/test/test_conversions.cpp +++ b/test/test_conversions.cpp @@ -4,11 +4,12 @@ * accompanying file LICENSE.txt) */ -#include "boost/redis/ignore.hpp" -#include "boost/system/detail/error_code.hpp" -#define BOOST_TEST_MODULE conversions #include +#include + +#include +#define BOOST_TEST_MODULE conversions #include #include "common.hpp" @@ -20,6 +21,8 @@ using boost::redis::request; using boost::redis::response; using boost::system::error_code; +namespace { + BOOST_AUTO_TEST_CASE(ints) { // Setup @@ -47,13 +50,17 @@ BOOST_AUTO_TEST_CASE(ints) unsigned long long> resp; - conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { - BOOST_TEST(!ec); + bool finished = false; + + conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) { + finished = true; + BOOST_TEST(ec == error_code()); conn->cancel(); }); // Run the operations - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); // Check BOOST_TEST(std::get<1>(resp).value() == 42); @@ -84,13 +91,16 @@ BOOST_AUTO_TEST_CASE(bools) response resp; - conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { - BOOST_TEST(!ec); + bool finished = false; + + conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) { + finished = true; + BOOST_TEST(ec == error_code()); conn->cancel(); }); // Run the operations - ioc.run(); + ioc.run_for(test_timeout); // Check BOOST_TEST(std::get<2>(resp).value() == true); @@ -111,14 +121,20 @@ BOOST_AUTO_TEST_CASE(floating_points) response resp; - conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { - BOOST_TEST(!ec); + bool finished = false; + + conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) { + finished = true; + BOOST_TEST(ec == error_code()); conn->cancel(); }); // Run the operations - ioc.run(); + ioc.run_for(test_timeout); + BOOST_TEST(finished); // Check BOOST_TEST(std::get<1>(resp).value() == 4.12); } + +} // namespace \ No newline at end of file diff --git a/test/test_issue_181.cpp b/test/test_issue_181.cpp index 1f3e7491..453d837b 100644 --- a/test/test_issue_181.cpp +++ b/test/test_issue_181.cpp @@ -7,18 +7,16 @@ #include #include -#include -#include -#define BOOST_TEST_MODULE conn_quit +#include +#include + +#define BOOST_TEST_MODULE issue_181 #include #include "common.hpp" #include #include -#include -#include -#include namespace net = boost::asio; using boost::redis::request; @@ -32,6 +30,8 @@ using boost::redis::connection; using boost::system::error_code; using namespace std::chrono_literals; +namespace { + BOOST_AUTO_TEST_CASE(issue_181) { using basic_connection = boost::redis::basic_connection; @@ -43,8 +43,12 @@ BOOST_AUTO_TEST_CASE(issue_181) net::steady_timer timer{ioc}; timer.expires_after(std::chrono::seconds{1}); - auto run_cont = [&](auto ec) { + bool run_finished = false; + + auto run_cont = [&](error_code ec) { std::cout << "async_run1: " << ec.message() << std::endl; + BOOST_TEST(ec == net::error::operation_aborted); + run_finished = true; }; auto cfg = make_test_config(); @@ -54,8 +58,9 @@ BOOST_AUTO_TEST_CASE(issue_181) BOOST_TEST(!conn.run_is_canceled()); // Uses a timer to wait some time until run has been called. - auto timer_cont = [&](auto ec) { + auto timer_cont = [&](error_code ec) { std::cout << "timer_cont: " << ec.message() << std::endl; + BOOST_TEST(ec == error_code()); BOOST_TEST(!conn.run_is_canceled()); conn.cancel(operation::run); BOOST_TEST(conn.run_is_canceled()); @@ -63,5 +68,9 @@ BOOST_AUTO_TEST_CASE(issue_181) timer.async_wait(timer_cont); - ioc.run(); + ioc.run_for(test_timeout); + + BOOST_TEST(run_finished); } + +} // namespace \ No newline at end of file diff --git a/test/test_issue_50.cpp b/test/test_issue_50.cpp index 33de47e6..98a4c76b 100644 --- a/test/test_issue_50.cpp +++ b/test/test_issue_50.cpp @@ -12,22 +12,21 @@ #include #include #include -#include -#include +#include #include -#include -#define BOOST_TEST_MODULE conn - quit +#include + +#include +#define BOOST_TEST_MODULE issue50 #include #include "common.hpp" #include -#include #if defined(BOOST_ASIO_HAS_CO_AWAIT) namespace net = boost::asio; -using steady_timer = net::use_awaitable_t<>::as_default_on_t; using boost::redis::request; using boost::redis::response; using boost::redis::ignore; @@ -36,10 +35,10 @@ using boost::redis::config; using boost::redis::operation; using boost::redis::connection; using boost::system::error_code; -using boost::asio::use_awaitable; -using boost::asio::redirect_error; using namespace std::chrono_literals; +namespace { + // Push consumer auto receiver(std::shared_ptr conn) -> net::awaitable { @@ -50,7 +49,7 @@ auto receiver(std::shared_ptr conn) -> net::awaitable for (;;) { std::cout << "aaaa" << std::endl; error_code ec; - co_await conn->async_receive(redirect_error(use_awaitable, ec)); + co_await conn->async_receive(net::redirect_error(ec)); if (ec) { std::cout << "Error in async_receive" << std::endl; break; @@ -67,14 +66,14 @@ auto periodic_task(std::shared_ptr conn) -> net::awaitable for (int i = 0; i < 10; ++i) { std::cout << "In the loop: " << i << std::endl; timer.expires_after(std::chrono::milliseconds(50)); - co_await timer.async_wait(net::use_awaitable); + co_await timer.async_wait(); // Key is not set so it will cause an error since we are passing // an adapter that does not accept null, this will cause an error // that result in the connection being closed. request req; req.push("GET", "mykey"); - auto [ec, u] = co_await conn->async_exec(req, ignore, net::as_tuple(net::use_awaitable)); + auto [ec, u] = co_await conn->async_exec(req, ignore, net::as_tuple); if (ec) { std::cout << "(1)Error: " << ec << std::endl; } else { @@ -88,26 +87,41 @@ auto periodic_task(std::shared_ptr conn) -> net::awaitable conn->cancel(operation::reconnection); } -auto co_main(config) -> net::awaitable -{ - auto ex = co_await net::this_coro::executor; - auto conn = std::make_shared(ex); - - net::co_spawn(ex, receiver(conn), net::detached); - net::co_spawn(ex, periodic_task(conn), net::detached); - auto cfg = make_test_config(); - conn->async_run(cfg, {}, net::consign(net::detached, conn)); -} - BOOST_AUTO_TEST_CASE(issue_50) { - net::io_context ioc; - net::co_spawn(ioc, co_main({}), net::detached); - ioc.run(); + bool receiver_finished = false, periodic_finished = false, run_finished = false; + + net::io_context ctx; + auto conn = std::make_shared(ctx.get_executor()); + + // Launch the receiver + net::co_spawn(ctx, receiver(conn), [&](std::exception_ptr exc) { + if (exc) + std::rethrow_exception(exc); + receiver_finished = true; + }); + + // Launch the period task + net::co_spawn(ctx, periodic_task(conn), [&](std::exception_ptr exc) { + if (exc) + std::rethrow_exception(exc); + periodic_finished = true; + }); + + // Launch run + conn->async_run(make_test_config(), {}, [&](error_code) { + run_finished = true; + }); + + ctx.run_for(2 * test_timeout); + + BOOST_TEST(receiver_finished); + BOOST_TEST(periodic_finished); + BOOST_TEST(run_finished); } -#else // defined(BOOST_ASIO_HAS_CO_AWAIT) - -BOOST_AUTO_TEST_CASE(issue_50) { } +} // namespace +#else +BOOST_AUTO_TEST_CASE(dummy) { } #endif // defined(BOOST_ASIO_HAS_CO_AWAIT) diff --git a/test/test_run.cpp b/test/test_run.cpp index d7bf8263..7540c46e 100644 --- a/test/test_run.cpp +++ b/test/test_run.cpp @@ -5,13 +5,13 @@ */ #include + +#include #define BOOST_TEST_MODULE run #include #include "common.hpp" -#include - namespace net = boost::asio; namespace redis = boost::redis; @@ -22,6 +22,8 @@ using redis::operation; using boost::system::error_code; using namespace std::chrono_literals; +namespace { + bool is_host_not_found(error_code ec) { if (ec == net::error::netdb_errors::host_not_found) @@ -34,6 +36,7 @@ bool is_host_not_found(error_code ec) BOOST_AUTO_TEST_CASE(resolve_bad_host) { net::io_context ioc; + connection conn{ioc}; auto cfg = make_test_config(); cfg.addr.host = "Atibaia"; @@ -43,17 +46,20 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - auto conn = std::make_shared(ioc); - conn->async_run(cfg, {}, [](auto ec) { - BOOST_TEST(is_host_not_found(ec)); + bool run_finished = true; + conn.async_run(cfg, {}, [&run_finished](error_code ec) { + run_finished = true; + BOOST_TEST(is_host_not_found(ec), "is_host_not_found(ec) is false, with ec = " << ec); }); - ioc.run(); + ioc.run_for(4 * test_timeout); + BOOST_TEST(run_finished); } BOOST_AUTO_TEST_CASE(resolve_with_timeout) { net::io_context ioc; + connection conn{ioc}; auto cfg = make_test_config(); cfg.addr.host = "occase.de"; @@ -63,14 +69,20 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - auto conn = std::make_shared(ioc); - run(conn, cfg); - ioc.run(); + bool run_finished = true; + conn.async_run(cfg, {}, [&run_finished](error_code ec) { + run_finished = true; + BOOST_TEST(ec != error_code()); + }); + + ioc.run_for(4 * test_timeout); + BOOST_TEST(run_finished); } BOOST_AUTO_TEST_CASE(connect_bad_port) { net::io_context ioc; + connection conn{ioc}; auto cfg = make_test_config(); cfg.addr.host = "127.0.0.1"; @@ -80,9 +92,14 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) cfg.health_check_interval = 10h; cfg.reconnect_wait_interval = 0s; - auto conn = std::make_shared(ioc); - run(conn, cfg, net::error::connection_refused); - ioc.run(); + bool run_finished = true; + conn.async_run(cfg, {}, [&run_finished](error_code ec) { + run_finished = true; + BOOST_TEST(ec != error_code()); + }); + + ioc.run_for(4 * test_timeout); + BOOST_TEST(run_finished); } // Hard to test. @@ -101,3 +118,5 @@ BOOST_AUTO_TEST_CASE(connect_bad_port) // run(conn, cfg, boost::redis::error::connect_timeout); // ioc.run(); //} + +} // namespace