2
0
mirror of https://github.com/boostorg/redis.git synced 2026-01-19 04:42:09 +00:00

Increases the robustness of integration tests (#259)

Updates multiplexer to make requests complete with error_code() rather than error_code(0)
Integration tests now use io_context::run_for to run with a timeout and avoid deadlocks
Tests now use concrete lambdas where generic ones are not required
Tests now use BOOST_TEST with operator== to print values on error
Tests now use anonymous namespaces to detect dead code
Adds run_coroutine_test and removed start from common.hpp
Updates test_reconnect to perform relevant checks
Refactors how test_issue_50 launches its coroutines
Groups tests in CMake as unit or integration
Updates Jamfile tests to contain all unit tests and only unit tests
This commit is contained in:
Anarthal (Rubén Pérez)
2025-06-06 12:48:40 +02:00
committed by GitHub
parent 0c8c6fcc09
commit 2fc54bc73b
19 changed files with 653 additions and 418 deletions

View File

@@ -16,7 +16,7 @@ multiplexer::elem::elem(request const& req, pipeline_adapter_type adapter)
, adapter_{} , adapter_{}
, remaining_responses_{req.get_expected_responses()} , remaining_responses_{req.get_expected_responses()}
, status_{status::waiting} , status_{status::waiting}
, ec_{{}} , ec_{}
, read_size_{0} , read_size_{0}
{ {
adapter_ = [this, adapter](resp3::node_view const& nd, system::error_code& ec) { adapter_ = [this, adapter](resp3::node_view const& nd, system::error_code& ec) {

View File

@@ -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_compile_features(boost_redis_tests_common PRIVATE cxx_std_17)
target_link_libraries(boost_redis_tests_common PRIVATE boost_redis_project_options) 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}") set(EXE_NAME "boost_redis_${TEST_NAME}")
add_executable(${EXE_NAME} ${TEST_NAME}.cpp) add_executable(${EXE_NAME} ${TEST_NAME}.cpp)
target_link_libraries(${EXE_NAME} PRIVATE target_link_libraries(${EXE_NAME} PRIVATE
@@ -29,31 +29,32 @@ macro(make_test TEST_NAME STANDARD)
boost_redis_project_options boost_redis_project_options
Boost::unit_test_framework Boost::unit_test_framework
) )
target_compile_features(${EXE_NAME} PRIVATE cxx_std_${STANDARD})
add_test(${EXE_NAME} ${EXE_NAME}) add_test(${EXE_NAME} ${EXE_NAME})
endmacro() endmacro()
make_test(test_conn_quit 17) # Unit tests
# TODO: Configure a Redis server with TLS in the CI and reenable this test. make_test(test_low_level)
#make_test(test_conn_tls 17) make_test(test_request)
make_test(test_low_level 17) make_test(test_low_level_sync_sans_io)
make_test(test_conn_exec_retry 17) make_test(test_any_adapter)
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)
make_test(test_conn_exec 20) # Tests that require a real Redis server
make_test(test_conn_push 20) make_test(test_conn_quit)
make_test(test_conn_reconnect 20) # TODO: Configure a Redis server with TLS in the CI and reenable this test.
make_test(test_conn_exec_cancel 20) #make_test(test_conn_tls)
make_test(test_conn_exec_cancel2 20) make_test(test_conn_exec_retry)
make_test(test_conn_echo_stress 20) make_test(test_conn_exec_error)
make_test(test_any_adapter 17) make_test(test_run)
make_test(test_conversions 17) make_test(test_conn_check_health)
make_test(test_issue_50 20) make_test(test_conn_exec)
make_test(test_issue_181 17) 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 # Coverage
set( set(

View File

@@ -48,10 +48,9 @@ lib redis_test_common
# B2 runs tests in parallel, and some tests rely on having exclusive # 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. # access to a Redis server, so we only run the ones that don't require a DB server.
local tests = local tests =
test_low_level_sync_sans_io
test_low_level test_low_level
test_request test_request
test_run test_low_level_sync_sans_io
test_any_adapter test_any_adapter
; ;

View File

@@ -5,6 +5,7 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <stdexcept>
namespace net = boost::asio; namespace net = boost::asio;
@@ -55,22 +56,17 @@ boost::redis::config make_test_config()
} }
#ifdef BOOST_ASIO_HAS_CO_AWAIT #ifdef BOOST_ASIO_HAS_CO_AWAIT
auto start(net::awaitable<void> op) -> int void run_coroutine_test(net::awaitable<void> op, std::chrono::steady_clock::duration timeout)
{ {
try { net::io_context ioc;
net::io_context ioc; bool finished = false;
net::co_spawn(ioc, std::move(op), [](std::exception_ptr p) { net::co_spawn(ioc, std::move(op), [&finished](std::exception_ptr p) {
if (p) if (p)
std::rethrow_exception(p); std::rethrow_exception(p);
}); finished = true;
ioc.run(); });
ioc.run_for(timeout);
return 0; if (!finished)
throw std::runtime_error("Coroutine test did not finish");
} catch (std::exception const& e) {
std::cerr << "start> " << e.what() << std::endl;
}
return 1;
} }
#endif // BOOST_ASIO_HAS_CO_AWAIT #endif // BOOST_ASIO_HAS_CO_AWAIT

View File

@@ -8,14 +8,22 @@
#include <boost/asio/use_awaitable.hpp> #include <boost/asio/use_awaitable.hpp>
#include <boost/system/error_code.hpp> #include <boost/system/error_code.hpp>
#include <chrono>
#include <memory> #include <memory>
// 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 #ifdef BOOST_ASIO_HAS_CO_AWAIT
inline auto redir(boost::system::error_code& ec) inline auto redir(boost::system::error_code& ec)
{ {
return boost::asio::redirect_error(boost::asio::use_awaitable, ec); return boost::asio::redirect_error(boost::asio::use_awaitable, ec);
} }
auto start(boost::asio::awaitable<void> op) -> int; void run_coroutine_test(
boost::asio::awaitable<void>,
std::chrono::steady_clock::duration timeout = test_timeout);
#endif // BOOST_ASIO_HAS_CO_AWAIT #endif // BOOST_ASIO_HAS_CO_AWAIT
boost::redis::config make_test_config(); boost::redis::config make_test_config();

View File

@@ -7,7 +7,7 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/response.hpp> #include <boost/redis/response.hpp>
#include <boost/system/errc.hpp> #include <cstddef>
#define BOOST_TEST_MODULE check_health #define BOOST_TEST_MODULE check_health
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -25,9 +25,12 @@ using boost::redis::ignore;
using boost::redis::operation; using boost::redis::operation;
using boost::redis::generic_response; using boost::redis::generic_response;
using boost::redis::consume_one; using boost::redis::consume_one;
using namespace std::chrono_literals;
// TODO: Test cancel(health_check) // TODO: Test cancel(health_check)
namespace {
struct push_callback { struct push_callback {
connection* conn1; connection* conn1;
connection* conn2; connection* conn2;
@@ -82,10 +85,13 @@ BOOST_AUTO_TEST_CASE(check_health)
auto cfg1 = make_test_config(); auto cfg1 = make_test_config();
cfg1.health_check_id = "conn1"; cfg1.health_check_id = "conn1";
cfg1.reconnect_wait_interval = std::chrono::seconds::zero(); 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; 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(); auto cfg2 = make_test_config();
cfg2.health_check_id = "conn2"; cfg2.health_check_id = "conn2";
error_code res2; conn2.async_run(cfg2, {}, [&](error_code ec) {
conn2.async_run(cfg2, {}, [&](auto ec) { run2_finished = true;
std::cout << "async_run 2 completed: " << ec.message() << std::endl; std::cout << "async_run 2 completed: " << ec.message() << std::endl;
res2 = ec; BOOST_TEST(ec != error_code());
}); });
request req2; request req2;
@@ -107,21 +113,25 @@ BOOST_AUTO_TEST_CASE(check_health)
generic_response resp2; generic_response resp2;
conn2.set_receive_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; std::cout << "async_exec: " << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
}); });
//-------------------------------- //--------------------------------
push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes. push_callback{&conn1, &conn2, &resp2, &req1}(); // Starts reading pushes.
ioc.run(); ioc.run_for(2 * test_timeout);
BOOST_TEST(!!res1); BOOST_TEST(run1_finished);
BOOST_TEST(!!res2); BOOST_TEST(run2_finished);
BOOST_TEST(exec_finished);
// Waits before exiting otherwise it might cause subsequent tests // Waits before exiting otherwise it might cause subsequent tests
// to fail. // to fail.
std::this_thread::sleep_for(std::chrono::seconds{10}); std::this_thread::sleep_for(std::chrono::seconds{10});
} }
} // namespace

View File

@@ -5,11 +5,14 @@
*/ */
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/co_spawn.hpp> #include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp> #include <boost/asio/error.hpp>
#include <boost/asio/detached.hpp> #include <boost/asio/io_context.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#include <exception>
#define BOOST_TEST_MODULE echo_stress #define BOOST_TEST_MODULE echo_stress
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -30,6 +33,9 @@ using boost::redis::logger;
using boost::redis::connection; using boost::redis::connection;
using boost::redis::usage; using boost::redis::usage;
using boost::redis::error; using boost::redis::error;
using namespace std::chrono_literals;
namespace boost::redis {
std::ostream& operator<<(std::ostream& os, usage const& u) std::ostream& operator<<(std::ostream& os, usage const& u)
{ {
@@ -43,90 +49,106 @@ std::ostream& operator<<(std::ostream& os, usage const& u)
return os; return os;
} }
auto push_consumer(std::shared_ptr<connection> conn, int expected) -> net::awaitable<void> } // namespace boost::redis
namespace {
auto push_consumer(connection& conn, int expected) -> net::awaitable<void>
{ {
int c = 0; int c = 0;
for (error_code ec;;) { for (error_code ec;;) {
conn->receive(ec); conn.receive(ec);
if (ec == error::sync_receive_push_failed) { if (ec == error::sync_receive_push_failed) {
ec = {}; ec = {};
co_await conn->async_receive(redirect_error(net::use_awaitable, ec)); co_await conn.async_receive(net::redirect_error(ec));
} else if (!ec) { } else if (!ec) {
//std::cout << "Skipping suspension." << std::endl; //std::cout << "Skipping suspension." << std::endl;
} }
if (ec) { if (ec) {
BOOST_TEST(false); BOOST_TEST(false, "push_consumer error: " << ec.message());
std::cout << "push_consumer error: " << ec.message() << std::endl;
co_return; co_return;
} }
if (++c == expected) if (++c == expected)
break; break;
} }
conn->cancel(); conn.cancel();
} }
auto echo_session(std::shared_ptr<connection> conn, std::shared_ptr<request> pubs, int n) auto echo_session(connection& conn, const request& pubs, int n) -> net::awaitable<void>
-> net::awaitable<void>
{ {
for (auto i = 0; i < n; ++i) for (auto i = 0; i < n; ++i)
co_await conn->async_exec(*pubs, ignore, net::deferred); co_await conn.async_exec(pubs, ignore);
} }
auto async_echo_stress(std::shared_ptr<connection> conn) -> net::awaitable<void> void rethrow_on_error(std::exception_ptr exc)
{ {
auto ex = co_await net::this_coro::executor; if (exc)
auto cfg = make_test_config(); std::rethrow_exception(exc);
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;
// The number of pings that will be sent by each session.
int const msgs = 200;
// The number of publishes that will be sent by each session with
// each message.
int const n_pubs = 25;
// This is the total number of pushes we will receive.
int total_pushes = sessions * msgs * n_pubs + 1;
auto pubs = std::make_shared<request>();
pubs->push("PING");
for (int i = 0; i < n_pubs; ++i)
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);
for (int i = 0; i < sessions; ++i)
net::co_spawn(ex, echo_session(conn, pubs, msgs), net::detached);
} }
BOOST_AUTO_TEST_CASE(echo_stress) BOOST_AUTO_TEST_CASE(echo_stress)
{ {
net::io_context ioc; // Setup
auto conn = std::make_shared<connection>(ioc); net::io_context ctx;
net::co_spawn(ioc, async_echo_stress(conn), net::detached); connection conn{ctx};
ioc.run(); auto cfg = make_test_config();
cfg.health_check_interval = std::chrono::seconds::zero();
std::cout << "-------------------\n" << conn->get_usage() << std::endl; // Number of coroutines that will send pings sharing the same
// connection to redis.
constexpr int sessions = 150;
// The number of pings that will be sent by each session.
constexpr int msgs = 200;
// The number of publishes that will be sent by each session with
// each message.
constexpr int n_pubs = 25;
// This is the total number of pushes we will receive.
constexpr int total_pushes = sessions * msgs * n_pubs + 1;
request pubs;
pubs.push("PING");
for (int i = 0; i < n_pubs; ++i)
pubs.push("PUBLISH", "channel", "payload");
// 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;
});
// 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());
// 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);
});
// 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 #else
BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } BOOST_AUTO_TEST_CASE(dummy) { }
#endif #endif

View File

@@ -8,8 +8,8 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp> #include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#include <string> #include <string>
#define BOOST_TEST_MODULE conn_exec #define BOOST_TEST_MODULE conn_exec
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -18,8 +18,8 @@
#include <iostream> #include <iostream>
// TODO: Test whether HELLO won't be inserted passt commands that have // TODO: Test whether HELLO won't be inserted past commands that have
// been already writen. // been already written.
// TODO: Test async_exec with empty request e.g. hgetall with an empty // TODO: Test async_exec with empty request e.g. hgetall with an empty
// container. // container.
@@ -31,6 +31,10 @@ using boost::redis::ignore;
using boost::redis::operation; using boost::redis::operation;
using boost::redis::request; using boost::redis::request;
using boost::redis::response; 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 // Sends three requests where one of them has a hello with a priority
// set, which means it should be executed first. // set, which means it should be executed first.
@@ -57,19 +61,19 @@ BOOST_AUTO_TEST_CASE(hello_priority)
bool seen2 = false; bool seen2 = false;
bool seen3 = 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. // Second callback to the called.
std::cout << "req1" << std::endl; std::cout << "req1" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
BOOST_TEST(!seen2); BOOST_TEST(!seen2);
BOOST_TEST(seen3); BOOST_TEST(seen3);
seen1 = true; 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. // Last callback to the called.
std::cout << "req2" << std::endl; std::cout << "req2" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
BOOST_TEST(seen1); BOOST_TEST(seen1);
BOOST_TEST(seen3); BOOST_TEST(seen3);
seen2 = true; seen2 = true;
@@ -77,17 +81,20 @@ BOOST_AUTO_TEST_CASE(hello_priority)
conn->cancel(operation::reconnection); 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. // Callback that will be called first.
std::cout << "req3" << std::endl; std::cout << "req3" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
BOOST_TEST(!seen1); BOOST_TEST(!seen1);
BOOST_TEST(!seen2); BOOST_TEST(!seen2);
seen3 = true; seen3 = true;
}); });
run(conn); 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. // 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; net::io_context ioc;
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
bool finished = false;
conn->async_exec(req, resp, [conn](auto ec, auto) { conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) {
BOOST_CHECK_EQUAL(ec, boost::redis::error::not_a_number); BOOST_TEST(ec == boost::redis::error::not_a_number);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
finished = true;
}); });
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
} }
BOOST_AUTO_TEST_CASE(cancel_request_if_not_connected) 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; net::io_context ioc;
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
conn->async_exec(req, ignore, [conn](auto ec, auto) { bool finished = false;
BOOST_CHECK_EQUAL(ec, boost::redis::error::not_connected); conn->async_exec(req, ignore, [conn, &finished](error_code ec, std::size_t) {
BOOST_TEST(ec, boost::redis::error::not_connected);
conn->cancel(); conn->cancel();
finished = true;
}); });
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
} }
BOOST_AUTO_TEST_CASE(correct_database) BOOST_AUTO_TEST_CASE(correct_database)
@@ -141,19 +154,25 @@ BOOST_AUTO_TEST_CASE(correct_database)
generic_response resp; generic_response resp;
conn->async_exec(req, resp, [&](auto ec, auto n) { bool exec_finished = false, run_finished = false;
BOOST_TEST(!ec);
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; std::clog << "async_exec has completed: " << n << std::endl;
conn->cancel(); 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; 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& value = resp.value().front().value;
auto const pos = value.find("db="); auto const pos = value.find("db=");
auto const index_str = value.substr(pos + 3, 1); 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); cfg.health_check_interval = std::chrono::seconds(0);
conn->async_run(cfg, {}, net::detached); conn->async_run(cfg, {}, net::detached);
int counter = 0; constexpr int repeat = 8000;
int const repeat = 8000; int remaining = repeat;
for (int i = 0; i < repeat; ++i) { for (int i = 0; i < repeat; ++i) {
auto req = std::make_shared<request>(); auto req = std::make_shared<request>();
req->push("PING", payload); req->push("PING", payload);
conn->async_exec(*req, ignore, [req, &counter, conn](auto ec, auto) { conn->async_exec(*req, ignore, [req, &remaining, conn](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
if (++counter == repeat) if (--remaining == 0)
conn->cancel(); 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) BOOST_AUTO_TEST_CASE(exec_any_adapter)
@@ -208,13 +227,19 @@ BOOST_AUTO_TEST_CASE(exec_any_adapter)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
conn->async_exec(req, boost::redis::any_adapter(res), [&](auto ec, auto) { bool finished = false;
BOOST_TEST(!ec);
conn->async_exec(req, boost::redis::any_adapter(res), [&](error_code ec, std::size_t) {
BOOST_TEST(ec == error_code());
conn->cancel(); conn->cancel();
finished = true;
}); });
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST_REQUIRE(finished);
BOOST_TEST(std::get<0>(res).value() == "PONG"); BOOST_TEST(std::get<0>(res).value() == "PONG");
} }
} // namespace

View File

@@ -7,19 +7,19 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp> #include <boost/system/errc.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_exec_cancel #define BOOST_TEST_MODULE conn_exec_cancel
#include <boost/asio/detached.hpp> #include <boost/asio/detached.hpp>
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <iostream>
#ifdef BOOST_ASIO_HAS_CO_AWAIT #ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/awaitable_operators.hpp>
// NOTE1: I have observed that if hello and // 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. // right away, not waiting for blpop.
namespace net = boost::asio; namespace net = boost::asio;
@@ -36,6 +36,8 @@ using boost::redis::logger;
using boost::redis::connection; using boost::redis::connection;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
auto implicit_cancel_of_req_written() -> net::awaitable<void> auto implicit_cancel_of_req_written() -> net::awaitable<void>
{ {
auto ex = co_await net::this_coro::executor; auto ex = co_await net::this_coro::executor;
@@ -48,7 +50,7 @@ auto implicit_cancel_of_req_written() -> net::awaitable<void>
// See NOTE1. // See NOTE1.
request req0; request req0;
req0.push("PING"); 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 // Will be cancelled after it has been written but before the
// response arrives. // response arrives.
@@ -66,15 +68,13 @@ auto implicit_cancel_of_req_written() -> net::awaitable<void>
// I have observed this produces terminal cancellation so it can't // I have observed this produces terminal cancellation so it can't
// be ignored, an error is expected. // be ignored, an error is expected.
BOOST_CHECK_EQUAL(ec1, net::error::operation_aborted); BOOST_TEST(ec1 == net::error::operation_aborted);
BOOST_TEST(!ec2); BOOST_TEST(ec2 == error_code());
} }
BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written) BOOST_AUTO_TEST_CASE(test_ignore_implicit_cancel_of_req_written)
{ {
net::io_context ioc; run_coroutine_test(implicit_cancel_of_req_written());
net::co_spawn(ioc, implicit_cancel_of_req_written(), net::detached);
ioc.run();
} }
BOOST_AUTO_TEST_CASE(test_cancel_of_req_written_on_run_canceled) 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.get_config().cancel_if_unresponded = true;
req1.push("BLPOP", "any", 0); 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); BOOST_CHECK_EQUAL(ec, net::error::operation_aborted);
finished = true;
}; };
auto c0 = [&](auto ec, auto) { auto c0 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c1); 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}; net::steady_timer st{ioc};
st.expires_after(std::chrono::seconds{1}); st.expires_after(std::chrono::seconds{1});
st.async_wait([&](auto ec) { st.async_wait([&](error_code ec) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->cancel(operation::run); conn->cancel(operation::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
}); });
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
} }
} // namespace
#else #else
BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } BOOST_AUTO_TEST_CASE(dummy) { }
#endif #endif

View File

@@ -6,9 +6,8 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp> #include <cstddef>
#define BOOST_TEST_MODULE conn_exec_cancel #define BOOST_TEST_MODULE conn_exec_cancel
#include <boost/asio/detached.hpp>
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
@@ -16,7 +15,6 @@
#include <iostream> #include <iostream>
#ifdef BOOST_ASIO_HAS_CO_AWAIT #ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp>
// NOTE1: Sends hello separately. I have observed that if hello and // NOTE1: Sends hello separately. I have observed that if hello and
// blpop are sent toguether, Redis will send the response of hello // blpop are sent toguether, Redis will send the response of hello
@@ -25,7 +23,6 @@
namespace net = boost::asio; namespace net = boost::asio;
using error_code = boost::system::error_code; using error_code = boost::system::error_code;
using namespace net::experimental::awaitable_operators;
using boost::redis::operation; using boost::redis::operation;
using boost::redis::request; using boost::redis::request;
using boost::redis::response; using boost::redis::response;
@@ -37,6 +34,8 @@ using boost::redis::logger;
using boost::redis::connection; using boost::redis::connection;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void> auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
{ {
auto ex = co_await net::this_coro::executor; auto ex = co_await net::this_coro::executor;
@@ -52,46 +51,45 @@ auto async_ignore_explicit_cancel_of_req_written() -> net::awaitable<void>
// See NOTE1. // See NOTE1.
request req0; request req0;
req0.push("PING", "async_ignore_explicit_cancel_of_req_written"); 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; request req1;
req1.push("BLPOP", "any", 3); req1.push("BLPOP", "any", 3);
bool seen = false; bool seen = false;
conn->async_exec(req1, gresp, [&](auto ec, auto) mutable { conn->async_exec(req1, gresp, [&](error_code ec, std::size_t) {
// No error should occur since the cancelation should be // No error should occur since the cancellation should be ignored
// ignored.
std::cout << "async_exec (1): " << ec.message() << std::endl; std::cout << "async_exec (1): " << ec.message() << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
seen = true; seen = true;
}); });
// Will complete while BLPOP is pending. // Will complete while BLPOP is pending.
boost::system::error_code ec1; error_code ec;
co_await st.async_wait(net::redirect_error(net::use_awaitable, ec1)); co_await st.async_wait(net::redirect_error(ec));
conn->cancel(operation::exec); conn->cancel(operation::exec);
BOOST_TEST(!ec1); BOOST_TEST(ec == error_code());
request req3; request req2;
req3.push("PING"); req2.push("PING");
// Test whether the connection remains usable after a call to // Test whether the connection remains usable after a call to
// cancel(exec). // 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(); conn->cancel();
BOOST_TEST(!ec1); BOOST_TEST(ec == error_code());
BOOST_TEST(seen); BOOST_TEST(seen);
} }
BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written) BOOST_AUTO_TEST_CASE(test_ignore_explicit_cancel_of_req_written)
{ {
net::io_context ioc; run_coroutine_test(async_ignore_explicit_cancel_of_req_written());
net::co_spawn(ioc, async_ignore_explicit_cancel_of_req_written(), net::detached);
ioc.run();
} }
} // namespace
#else #else
BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } BOOST_AUTO_TEST_CASE(dummy) { }
#endif #endif

View File

@@ -5,31 +5,31 @@
*/ */
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn_exec_error #define BOOST_TEST_MODULE conn_exec_error
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <cstddef>
#include <iostream> #include <iostream>
namespace net = boost::asio; namespace net = boost::asio;
namespace redis = boost::redis; namespace redis = boost::redis;
namespace resp3 = redis::resp3; namespace resp3 = redis::resp3;
using error_code = boost::system::error_code; using error_code = boost::system::error_code;
using connection = boost::redis::connection; using boost::redis::connection;
using boost::redis::request; using boost::redis::request;
using boost::redis::response; using boost::redis::response;
using boost::redis::generic_response; using boost::redis::generic_response;
using boost::redis::ignore; using boost::redis::ignore;
using boost::redis::ignore_t; using boost::redis::ignore_t;
using boost::redis::error; using boost::redis::error;
using boost::redis::logger;
using boost::redis::operation; using boost::redis::operation;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
BOOST_AUTO_TEST_CASE(no_ignore_error) BOOST_AUTO_TEST_CASE(no_ignore_error)
{ {
request req; request req;
@@ -41,15 +41,20 @@ BOOST_AUTO_TEST_CASE(no_ignore_error)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
conn->async_exec(req, ignore, [&](auto ec, auto) { bool exec_finished = false;
BOOST_CHECK_EQUAL(ec, error::resp3_simple_error);
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::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
}); });
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(exec_finished);
} }
BOOST_AUTO_TEST_CASE(has_diagnostic) BOOST_AUTO_TEST_CASE(has_diagnostic)
@@ -68,19 +73,22 @@ BOOST_AUTO_TEST_CASE(has_diagnostic)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
response<std::string, std::string> resp; response<std::string, std::string> resp;
conn->async_exec(req, resp, [&](auto ec, auto) { bool exec_finished = false;
BOOST_TEST(!ec); conn->async_exec(req, resp, [&](error_code ec, std::size_t) {
exec_finished = true;
BOOST_TEST(ec == error_code());
// HELLO // HELLO
BOOST_TEST(std::get<0>(resp).has_error()); 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; auto const diag = std::get<0>(resp).error().diagnostic;
BOOST_TEST(!std::empty(diag)); BOOST_TEST(!std::empty(diag));
std::cout << "has_diagnostic: " << diag << std::endl; std::cout << "has_diagnostic: " << diag << std::endl;
// PING // PING
BOOST_TEST(std::get<1>(resp).has_value()); 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::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
@@ -88,7 +96,9 @@ BOOST_AUTO_TEST_CASE(has_diagnostic)
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(exec_finished);
} }
BOOST_AUTO_TEST_CASE(resp3_error_in_cmd_pipeline) 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; net::io_context ioc;
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
auto c2 = [&](auto ec, auto) { bool c2_called = false, c1_called = false;
BOOST_TEST(!ec);
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_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::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
}; };
auto c1 = [&](auto ec, auto) { auto c1 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); c1_called = true;
BOOST_TEST(ec == error_code());
BOOST_TEST(std::get<2>(resp1).has_error()); 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; auto const diag = std::get<2>(resp1).error().diagnostic;
BOOST_TEST(!std::empty(diag)); BOOST_TEST(!std::empty(diag));
std::cout << "resp3_error_in_cmd_pipeline: " << diag << std::endl; std::cout << "resp3_error_in_cmd_pipeline: " << diag << std::endl;
BOOST_TEST(std::get<3>(resp1).has_value()); 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); 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); conn->async_exec(req1, resp1, c1);
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(c1_called);
BOOST_TEST(c2_called);
} }
BOOST_AUTO_TEST_CASE(error_in_transaction) BOOST_AUTO_TEST_CASE(error_in_transaction)
@@ -163,8 +180,11 @@ BOOST_AUTO_TEST_CASE(error_in_transaction)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
conn->async_exec(req, resp, [&](auto ec, auto) { bool finished = false;
BOOST_TEST(!ec);
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<0>(resp).has_value());
BOOST_TEST(std::get<1>(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. // Test errors in the pipeline commands.
BOOST_TEST(std::get<0>(std::get<5>(resp).value()).has_value()); 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. // The ping in the transaction that should be an error.
BOOST_TEST(std::get<1>(std::get<5>(resp).value()).has_error()); BOOST_TEST(std::get<1>(std::get<5>(resp).value()).has_error());
BOOST_CHECK_EQUAL( BOOST_TEST(
std::get<1>(std::get<5>(resp).value()).error().data_type, std::get<1>(std::get<5>(resp).value()).error().data_type == resp3::type::simple_error);
resp3::type::simple_error);
auto const diag = std::get<1>(std::get<5>(resp).value()).error().diagnostic; auto const diag = std::get<1>(std::get<5>(resp).value()).error().diagnostic;
BOOST_TEST(!std::empty(diag)); BOOST_TEST(!std::empty(diag));
// The ping thereafter in the transaction should not be an error. // The ping thereafter in the transaction should not be an error.
BOOST_TEST(std::get<2>(std::get<5>(resp).value()).has_value()); 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. // The command right after the pipeline should be successful.
BOOST_TEST(std::get<6>(resp).has_value()); 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::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
@@ -199,7 +218,9 @@ BOOST_AUTO_TEST_CASE(error_in_transaction)
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
} }
// This test is important because a SUBSCRIBE command has no response // 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 // even more complex. For example, without a ping, we might get the
// sequence HELLO + SUBSCRIBE + PING where the hello and ping are // sequence HELLO + SUBSCRIBE + PING where the hello and ping are
// automatically sent by the implementation. In this case, if the // automatically sent by the implementation. In this case, if the
// subscribe synthax is wrong, redis will send a response, which does // subscribe syntax is wrong, redis will send a response, which does
// not exist on success. That response will be interprested as the // not exist on success. That response will be interpreted as the
// response to the PING command that comes thereafter and won't be // response to the PING command that comes thereafter and won't be
// forwarded to the receive_op, resulting in a difficult to handle // forwarded to the receive_op, resulting in a difficult to handle
// error. // error.
@@ -223,19 +244,23 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
req1.push("PING"); req1.push("PING");
request req2; request req2;
req2.push("SUBSCRIBE"); // Wrong command synthax. req2.push("SUBSCRIBE"); // Wrong command syntax.
net::io_context ioc; net::io_context ioc;
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(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; 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; std::cout << "async_exec: hello" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
@@ -244,7 +269,8 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
generic_response gresp; generic_response gresp;
conn->set_receive_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; std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(!ec);
BOOST_TEST(gresp.has_error()); BOOST_TEST(gresp.has_error());
@@ -259,5 +285,11 @@ BOOST_AUTO_TEST_CASE(subscriber_wrong_syntax)
run(conn); run(conn);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(c1_called);
BOOST_TEST(c2_called);
BOOST_TEST(c3_called);
} }
} // namespace

View File

@@ -6,8 +6,9 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/asio/awaitable.hpp> #include <boost/system/error_code.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_exec_retry #define BOOST_TEST_MODULE conn_exec_retry
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -27,6 +28,8 @@ using boost::redis::logger;
using boost::redis::config; using boost::redis::config;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
BOOST_AUTO_TEST_CASE(request_retry_false) BOOST_AUTO_TEST_CASE(request_retry_false)
{ {
request req0; request req0;
@@ -46,31 +49,40 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
net::steady_timer st{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.expires_after(std::chrono::seconds{1});
st.async_wait([&](auto) { st.async_wait([&](error_code ec) {
// Cancels the request before receiving the response. This // Cancels the request before receiving the response. This
// should cause the third request to complete with error // should cause the third request to complete with error
// although it has cancel_on_connection_lost = false. The reason // although it has cancel_on_connection_lost = false. The reason
// being it has already been written so // being it has already been written so
// cancel_on_connection_lost does not apply. // cancel_on_connection_lost does not apply.
timer_finished = true;
BOOST_TEST(ec == error_code());
conn->cancel(operation::run); conn->cancel(operation::run);
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
std::cout << "async_wait" << std::endl; 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; 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; 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; std::cout << "c0" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c1); conn->async_exec(req1, ignore, c1);
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
@@ -78,15 +90,19 @@ BOOST_AUTO_TEST_CASE(request_retry_false)
conn->async_exec(req0, ignore, c0); conn->async_exec(req0, ignore, c0);
auto cfg = make_test_config(); auto cfg = make_test_config();
conn->async_run( conn->async_run(cfg, {boost::redis::logger::level::debug}, [&](error_code ec) {
cfg, run_finished = true;
{boost::redis::logger::level::debug}, std::cout << "async_run: " << ec.message() << std::endl;
[&](boost::system::error_code const& ec) { conn->cancel();
std::cout << "async_run: " << ec.message() << std::endl; });
conn->cancel();
});
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(run_finished);
} }
BOOST_AUTO_TEST_CASE(request_retry_true) BOOST_AUTO_TEST_CASE(request_retry_true)
@@ -113,32 +129,42 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
net::steady_timer st{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.expires_after(std::chrono::seconds{1});
st.async_wait([&](auto) { st.async_wait([&](error_code ec) {
// Cancels the request before receiving the response. This // Cancels the request before receiving the response. This
// should cause the third request to not complete with error // should cause the third request to not complete with error
// since it has cancel_if_unresponded = true and cancellation // since it has cancel_if_unresponded = true and cancellation
// comes after it was written. // comes after it was written.
timer_finished = true;
BOOST_TEST(ec == error_code());
conn->cancel(operation::run); 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; std::cout << "c3: " << ec.message() << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->cancel(); conn->cancel();
}; };
auto c2 = [&](auto ec, auto) { auto c2 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); c2_called = true;
BOOST_TEST(ec == error_code());
conn->async_exec(req3, ignore, c3); conn->async_exec(req3, ignore, c3);
}; };
auto c1 = [](auto ec, auto) { auto c1 = [&](error_code ec, std::size_t) {
BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled); c1_called = true;
BOOST_TEST(ec == net::error::operation_aborted);
}; };
auto c0 = [&](auto ec, auto) { auto c0 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); c0_called = true;
BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c1); conn->async_exec(req1, ignore, c1);
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
@@ -147,10 +173,20 @@ BOOST_AUTO_TEST_CASE(request_retry_true)
auto cfg = make_test_config(); auto cfg = make_test_config();
cfg.health_check_interval = 5s; 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; 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

View File

@@ -7,24 +7,23 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp> #include <boost/redis/logger.hpp>
#include <boost/asio/as_tuple.hpp> #include <boost/asio/experimental/channel_error.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp> #include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn - push
#define BOOST_TEST_MODULE conn_push
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <cstddef>
#include <iostream> #include <iostream>
namespace net = boost::asio; namespace net = boost::asio;
namespace redis = boost::redis; namespace redis = boost::redis;
using boost::redis::operation; using boost::redis::operation;
using connection = boost::redis::connection; using boost::redis::connection;
using error_code = boost::system::error_code; using boost::system::error_code;
using net::as_tuple;
using boost::redis::request; using boost::redis::request;
using boost::redis::response; using boost::redis::response;
using boost::redis::ignore; using boost::redis::ignore;
@@ -33,6 +32,8 @@ using boost::system::error_code;
using boost::redis::logger; using boost::redis::logger;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
BOOST_AUTO_TEST_CASE(receives_push_waiting_resps) BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
{ {
request req1; request req1;
@@ -50,17 +51,22 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(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; std::cout << "c3: " << ec.message() << std::endl;
}; };
auto c2 = [&, conn](auto ec, auto...) { auto c2 = [&, conn](error_code ec, std::size_t) {
BOOST_TEST(!ec); c2_called = true;
BOOST_TEST(ec == error_code());
conn->async_exec(req3, ignore, c3); conn->async_exec(req3, ignore, c3);
}; };
auto c1 = [&, conn](auto ec, auto...) { auto c1 = [&, conn](error_code ec, std::size_t) {
BOOST_TEST(!ec); c1_called = true;
BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
@@ -68,17 +74,19 @@ BOOST_AUTO_TEST_CASE(receives_push_waiting_resps)
run(conn, make_test_config(), {}); run(conn, make_test_config(), {});
bool push_received = false; conn->async_receive([&, conn](error_code ec, std::size_t) {
conn->async_receive([&, conn](auto ec, auto) {
std::cout << "async_receive" << std::endl; std::cout << "async_receive" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
push_received = true; push_received = true;
conn->cancel(); conn->cancel();
}); });
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(push_received); BOOST_TEST(push_received);
BOOST_TEST(c1_called);
BOOST_TEST(c2_called);
BOOST_TEST(c3_called);
} }
BOOST_AUTO_TEST_CASE(push_received1) BOOST_AUTO_TEST_CASE(push_received1)
@@ -94,17 +102,19 @@ BOOST_AUTO_TEST_CASE(push_received1)
req.push("SUBSCRIBE", "channel1"); req.push("SUBSCRIBE", "channel1");
req.push("SUBSCRIBE", "channel2"); 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; std::cout << "async_exec" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
}); });
bool push_async_received = false; conn->async_receive([&, conn](error_code ec, std::size_t) {
conn->async_receive([&, conn](auto ec, auto) { push_received = true;
std::cout << "(1) async_receive" << std::endl; std::cout << "(1) async_receive" << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
push_async_received = true;
// Receives the second push synchronously. // Receives the second push synchronously.
error_code ec2; error_code ec2;
@@ -124,9 +134,10 @@ BOOST_AUTO_TEST_CASE(push_received1)
}); });
run(conn); 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) BOOST_AUTO_TEST_CASE(push_filtered_out)
@@ -141,39 +152,30 @@ BOOST_AUTO_TEST_CASE(push_filtered_out)
req.push("QUIT"); req.push("QUIT");
response<ignore_t, std::string, std::string> resp; response<ignore_t, std::string, std::string> 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) { conn->async_receive([&, conn](error_code ec, std::size_t) {
BOOST_TEST(!ec); push_received = true;
BOOST_TEST(ec == error_code());
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
}); });
run(conn); 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<1>(resp).value(), "PONG");
BOOST_CHECK_EQUAL(std::get<2>(resp).value(), "OK"); BOOST_CHECK_EQUAL(std::get<2>(resp).value(), "OK");
} }
#ifdef BOOST_ASIO_HAS_CO_AWAIT
net::awaitable<void> push_consumer1(std::shared_ptr<connection> 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 { }; struct response_error_tag { };
response_error_tag error_tag_obj; 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->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); BOOST_CHECK_EQUAL(ec, boost::asio::experimental::error::channel_cancelled);
conn->cancel(operation::reconnection); 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); BOOST_CHECK_EQUAL(ec, boost::system::errc::errc_t::operation_canceled);
exec_finished = true;
}); });
auto cfg = make_test_config(); 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); 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 // TODO: Reset the ioc reconnect and send a quit to ensure
// reconnection is possible after an error. // reconnection is possible after an error.
} }
net::awaitable<void> push_consumer3(std::shared_ptr<connection> conn) void launch_push_consumer(std::shared_ptr<connection> conn)
{ {
for (;;) { conn->async_receive([conn](error_code ec, std::size_t) {
co_await conn->async_receive(net::use_awaitable); if (ec) {
} BOOST_TEST(ec == net::experimental::error::channel_cancelled);
return;
}
launch_push_consumer(conn);
});
} }
BOOST_AUTO_TEST_CASE(many_subscribers) BOOST_AUTO_TEST_CASE(many_subscribers)
@@ -256,60 +270,65 @@ BOOST_AUTO_TEST_CASE(many_subscribers)
net::io_context ioc; net::io_context ioc;
auto conn = std::make_shared<connection>(ioc); auto conn = std::make_shared<connection>(ioc);
auto c11 = [&](auto ec, auto...) { bool finished = false;
std::cout << "quit sent: " << ec.message() << std::endl;
auto c11 = [&](error_code ec, std::size_t) {
BOOST_TEST(ec == error_code());
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
finished = true;
}; };
auto c10 = [&](auto ec, auto...) { auto c10 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req3, ignore, c11); conn->async_exec(req3, ignore, c11);
}; };
auto c9 = [&](auto ec, auto...) { auto c9 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c10); conn->async_exec(req2, ignore, c10);
}; };
auto c8 = [&](auto ec, auto...) { auto c8 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c9); conn->async_exec(req1, ignore, c9);
}; };
auto c7 = [&](auto ec, auto...) { auto c7 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c8); conn->async_exec(req2, ignore, c8);
}; };
auto c6 = [&](auto ec, auto...) { auto c6 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c7); conn->async_exec(req2, ignore, c7);
}; };
auto c5 = [&](auto ec, auto...) { auto c5 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c6); conn->async_exec(req1, ignore, c6);
}; };
auto c4 = [&](auto ec, auto...) { auto c4 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c5); conn->async_exec(req2, ignore, c5);
}; };
auto c3 = [&](auto ec, auto...) { auto c3 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c4); conn->async_exec(req1, ignore, c4);
}; };
auto c2 = [&](auto ec, auto...) { auto c2 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c3); conn->async_exec(req2, ignore, c3);
}; };
auto c1 = [&](auto ec, auto...) { auto c1 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
auto c0 = [&](auto ec, auto...) { auto c0 = [&](error_code ec, std::size_t) {
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req1, ignore, c1); conn->async_exec(req1, ignore, c1);
}; };
conn->async_exec(req0, ignore, c0); conn->async_exec(req0, ignore, c0);
launch_push_consumer(conn);
run(conn, make_test_config(), {}); run(conn, make_test_config(), {});
net::co_spawn(ioc.get_executor(), push_consumer3(conn), net::detached); ioc.run_for(test_timeout);
ioc.run(); BOOST_TEST(finished);
} }
#endif
} // namespace

View File

@@ -6,7 +6,9 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp> #include <boost/system/error_code.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_quit #define BOOST_TEST_MODULE conn_quit
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -43,20 +45,25 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
req3.get_config().cancel_if_not_connected = true; req3.get_config().cancel_if_not_connected = true;
req3.push("PING"); 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; 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; std::clog << "c2: " << ec.message() << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req3, ignore, c3); 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; std::cout << "c1: " << ec.message() << std::endl;
BOOST_TEST(!ec); BOOST_TEST(ec == error_code());
conn->async_exec(req2, ignore, c2); conn->async_exec(req2, ignore, c2);
}; };
@@ -69,5 +76,9 @@ BOOST_AUTO_TEST_CASE(test_async_run_exits)
cfg.reconnect_wait_interval = 0s; cfg.reconnect_wait_interval = 0s;
run(conn, cfg); run(conn, cfg);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(c1_called);
BOOST_TEST(c2_called);
BOOST_TEST(c3_called);
} }

View File

@@ -4,9 +4,11 @@
* accompanying file LICENSE.txt) * accompanying file LICENSE.txt)
*/ */
#include <boost/redis/config.hpp>
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp> #include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE conn_reconnect #define BOOST_TEST_MODULE conn_reconnect
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
@@ -26,41 +28,52 @@ using boost::redis::logger;
using boost::redis::operation; using boost::redis::operation;
using boost::redis::connection; using boost::redis::connection;
using namespace std::chrono_literals; using namespace std::chrono_literals;
using namespace boost::asio::experimental::awaitable_operators; using namespace boost::asio::experimental::awaitable_operators;
namespace {
net::awaitable<void> test_reconnect_impl() net::awaitable<void> test_reconnect_impl()
{ {
auto ex = co_await net::this_coro::executor; auto ex = co_await net::this_coro::executor;
request req; request quit_req;
req.push("QUIT"); 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<connection>(ex); auto conn = std::make_shared<connection>(ex);
run(conn); auto cfg = make_test_config();
cfg.reconnect_wait_interval = 100ms; // make the test run faster
run(conn, std::move(cfg));
int i = 0; for (int i = 0; i < 3; ++i) {
for (; i < 5; ++i) { BOOST_TEST_CONTEXT("i=" << i)
error_code ec1, ec2; {
auto cfg = make_test_config(); // Issue a quit request, which will cause the server to close the connection.
logger l; // This request will fail
co_await conn->async_exec(req, ignore, net::redirect_error(net::use_awaitable, ec1)); error_code ec;
//BOOST_TEST(!ec); co_await conn->async_exec(quit_req, ignore, net::redirect_error(ec));
std::cout << "test_reconnect: " << i << " " << ec2.message() << " " << ec1.message() BOOST_TEST(ec == error_code());
<< std::endl;
// 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(); conn->cancel();
BOOST_CHECK_EQUAL(i, 5);
co_return;
} }
// Test whether the client works after a reconnect. // Test whether the client works after a reconnect.
BOOST_AUTO_TEST_CASE(test_reconnect) BOOST_AUTO_TEST_CASE(test_reconnect)
{ {
net::io_context ioc; run_coroutine_test(test_reconnect_impl(), 5 * test_timeout);
net::co_spawn(ioc, test_reconnect_impl(), net::detached);
ioc.run();
} }
auto async_test_reconnect_timeout() -> net::awaitable<void> auto async_test_reconnect_timeout() -> net::awaitable<void>
@@ -104,10 +117,11 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
BOOST_AUTO_TEST_CASE(test_reconnect_and_idle) BOOST_AUTO_TEST_CASE(test_reconnect_and_idle)
{ {
net::io_context ioc; run_coroutine_test(async_test_reconnect_timeout());
net::co_spawn(ioc, async_test_reconnect_timeout(), net::detached);
ioc.run();
} }
} // namespace
#else #else
BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); } BOOST_AUTO_TEST_CASE(dummy) { }
#endif #endif

View File

@@ -4,11 +4,12 @@
* accompanying file LICENSE.txt) * accompanying file LICENSE.txt)
*/ */
#include "boost/redis/ignore.hpp"
#include "boost/system/detail/error_code.hpp"
#define BOOST_TEST_MODULE conversions
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/ignore.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE conversions
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
@@ -20,6 +21,8 @@ using boost::redis::request;
using boost::redis::response; using boost::redis::response;
using boost::system::error_code; using boost::system::error_code;
namespace {
BOOST_AUTO_TEST_CASE(ints) BOOST_AUTO_TEST_CASE(ints)
{ {
// Setup // Setup
@@ -47,13 +50,17 @@ BOOST_AUTO_TEST_CASE(ints)
unsigned long long> unsigned long long>
resp; resp;
conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { bool finished = false;
BOOST_TEST(!ec);
conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) {
finished = true;
BOOST_TEST(ec == error_code());
conn->cancel(); conn->cancel();
}); });
// Run the operations // Run the operations
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
// Check // Check
BOOST_TEST(std::get<1>(resp).value() == 42); BOOST_TEST(std::get<1>(resp).value() == 42);
@@ -84,13 +91,16 @@ BOOST_AUTO_TEST_CASE(bools)
response<ignore_t, ignore_t, bool, bool> resp; response<ignore_t, ignore_t, bool, bool> resp;
conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { bool finished = false;
BOOST_TEST(!ec);
conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) {
finished = true;
BOOST_TEST(ec == error_code());
conn->cancel(); conn->cancel();
}); });
// Run the operations // Run the operations
ioc.run(); ioc.run_for(test_timeout);
// Check // Check
BOOST_TEST(std::get<2>(resp).value() == true); BOOST_TEST(std::get<2>(resp).value() == true);
@@ -111,14 +121,20 @@ BOOST_AUTO_TEST_CASE(floating_points)
response<ignore_t, double> resp; response<ignore_t, double> resp;
conn->async_exec(req, resp, [conn](error_code ec, std::size_t) { bool finished = false;
BOOST_TEST(!ec);
conn->async_exec(req, resp, [conn, &finished](error_code ec, std::size_t) {
finished = true;
BOOST_TEST(ec == error_code());
conn->cancel(); conn->cancel();
}); });
// Run the operations // Run the operations
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(finished);
// Check // Check
BOOST_TEST(std::get<1>(resp).value() == 4.12); BOOST_TEST(std::get<1>(resp).value() == 4.12);
} }
} // namespace

View File

@@ -7,18 +7,16 @@
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp> #include <boost/redis/logger.hpp>
#include <boost/asio/awaitable.hpp> #include <boost/asio/error.hpp>
#include <boost/asio/use_awaitable.hpp> #include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE conn_quit
#define BOOST_TEST_MODULE issue_181
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <chrono> #include <chrono>
#include <iostream> #include <iostream>
#include <memory>
#include <optional>
#include <string>
namespace net = boost::asio; namespace net = boost::asio;
using boost::redis::request; using boost::redis::request;
@@ -32,6 +30,8 @@ using boost::redis::connection;
using boost::system::error_code; using boost::system::error_code;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
BOOST_AUTO_TEST_CASE(issue_181) BOOST_AUTO_TEST_CASE(issue_181)
{ {
using basic_connection = boost::redis::basic_connection<net::any_io_executor>; using basic_connection = boost::redis::basic_connection<net::any_io_executor>;
@@ -43,8 +43,12 @@ BOOST_AUTO_TEST_CASE(issue_181)
net::steady_timer timer{ioc}; net::steady_timer timer{ioc};
timer.expires_after(std::chrono::seconds{1}); 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; std::cout << "async_run1: " << ec.message() << std::endl;
BOOST_TEST(ec == net::error::operation_aborted);
run_finished = true;
}; };
auto cfg = make_test_config(); auto cfg = make_test_config();
@@ -54,8 +58,9 @@ BOOST_AUTO_TEST_CASE(issue_181)
BOOST_TEST(!conn.run_is_canceled()); BOOST_TEST(!conn.run_is_canceled());
// Uses a timer to wait some time until run has been called. // 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; std::cout << "timer_cont: " << ec.message() << std::endl;
BOOST_TEST(ec == error_code());
BOOST_TEST(!conn.run_is_canceled()); BOOST_TEST(!conn.run_is_canceled());
conn.cancel(operation::run); conn.cancel(operation::run);
BOOST_TEST(conn.run_is_canceled()); BOOST_TEST(conn.run_is_canceled());
@@ -63,5 +68,9 @@ BOOST_AUTO_TEST_CASE(issue_181)
timer.async_wait(timer_cont); timer.async_wait(timer_cont);
ioc.run(); ioc.run_for(test_timeout);
BOOST_TEST(run_finished);
} }
} // namespace

View File

@@ -12,22 +12,21 @@
#include <boost/asio/as_tuple.hpp> #include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp> #include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp> #include <boost/asio/co_spawn.hpp>
#include <boost/asio/consign.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/redirect_error.hpp> #include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp> #include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE conn - quit
#include <exception>
#define BOOST_TEST_MODULE issue50
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <iostream> #include <iostream>
#include <tuple>
#if defined(BOOST_ASIO_HAS_CO_AWAIT) #if defined(BOOST_ASIO_HAS_CO_AWAIT)
namespace net = boost::asio; namespace net = boost::asio;
using steady_timer = net::use_awaitable_t<>::as_default_on_t<net::steady_timer>;
using boost::redis::request; using boost::redis::request;
using boost::redis::response; using boost::redis::response;
using boost::redis::ignore; using boost::redis::ignore;
@@ -36,10 +35,10 @@ using boost::redis::config;
using boost::redis::operation; using boost::redis::operation;
using boost::redis::connection; using boost::redis::connection;
using boost::system::error_code; using boost::system::error_code;
using boost::asio::use_awaitable;
using boost::asio::redirect_error;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
// Push consumer // Push consumer
auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void> auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
{ {
@@ -50,7 +49,7 @@ auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
for (;;) { for (;;) {
std::cout << "aaaa" << std::endl; std::cout << "aaaa" << std::endl;
error_code ec; error_code ec;
co_await conn->async_receive(redirect_error(use_awaitable, ec)); co_await conn->async_receive(net::redirect_error(ec));
if (ec) { if (ec) {
std::cout << "Error in async_receive" << std::endl; std::cout << "Error in async_receive" << std::endl;
break; break;
@@ -67,14 +66,14 @@ auto periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
for (int i = 0; i < 10; ++i) { for (int i = 0; i < 10; ++i) {
std::cout << "In the loop: " << i << std::endl; std::cout << "In the loop: " << i << std::endl;
timer.expires_after(std::chrono::milliseconds(50)); 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 // 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 // an adapter that does not accept null, this will cause an error
// that result in the connection being closed. // that result in the connection being closed.
request req; request req;
req.push("GET", "mykey"); 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) { if (ec) {
std::cout << "(1)Error: " << ec << std::endl; std::cout << "(1)Error: " << ec << std::endl;
} else { } else {
@@ -88,26 +87,41 @@ auto periodic_task(std::shared_ptr<connection> conn) -> net::awaitable<void>
conn->cancel(operation::reconnection); conn->cancel(operation::reconnection);
} }
auto co_main(config) -> net::awaitable<void>
{
auto ex = co_await net::this_coro::executor;
auto conn = std::make_shared<connection>(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) BOOST_AUTO_TEST_CASE(issue_50)
{ {
net::io_context ioc; bool receiver_finished = false, periodic_finished = false, run_finished = false;
net::co_spawn(ioc, co_main({}), net::detached);
ioc.run(); net::io_context ctx;
auto conn = std::make_shared<connection>(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) } // namespace
BOOST_AUTO_TEST_CASE(issue_50) { }
#else
BOOST_AUTO_TEST_CASE(dummy) { }
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT) #endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,13 +5,13 @@
*/ */
#include <boost/redis/connection.hpp> #include <boost/redis/connection.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE run #define BOOST_TEST_MODULE run
#include <boost/test/included/unit_test.hpp> #include <boost/test/included/unit_test.hpp>
#include "common.hpp" #include "common.hpp"
#include <iostream>
namespace net = boost::asio; namespace net = boost::asio;
namespace redis = boost::redis; namespace redis = boost::redis;
@@ -22,6 +22,8 @@ using redis::operation;
using boost::system::error_code; using boost::system::error_code;
using namespace std::chrono_literals; using namespace std::chrono_literals;
namespace {
bool is_host_not_found(error_code ec) bool is_host_not_found(error_code ec)
{ {
if (ec == net::error::netdb_errors::host_not_found) 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) BOOST_AUTO_TEST_CASE(resolve_bad_host)
{ {
net::io_context ioc; net::io_context ioc;
connection conn{ioc};
auto cfg = make_test_config(); auto cfg = make_test_config();
cfg.addr.host = "Atibaia"; cfg.addr.host = "Atibaia";
@@ -43,17 +46,20 @@ BOOST_AUTO_TEST_CASE(resolve_bad_host)
cfg.health_check_interval = 10h; cfg.health_check_interval = 10h;
cfg.reconnect_wait_interval = 0s; cfg.reconnect_wait_interval = 0s;
auto conn = std::make_shared<connection>(ioc); bool run_finished = true;
conn->async_run(cfg, {}, [](auto ec) { conn.async_run(cfg, {}, [&run_finished](error_code ec) {
BOOST_TEST(is_host_not_found(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) BOOST_AUTO_TEST_CASE(resolve_with_timeout)
{ {
net::io_context ioc; net::io_context ioc;
connection conn{ioc};
auto cfg = make_test_config(); auto cfg = make_test_config();
cfg.addr.host = "occase.de"; cfg.addr.host = "occase.de";
@@ -63,14 +69,20 @@ BOOST_AUTO_TEST_CASE(resolve_with_timeout)
cfg.health_check_interval = 10h; cfg.health_check_interval = 10h;
cfg.reconnect_wait_interval = 0s; cfg.reconnect_wait_interval = 0s;
auto conn = std::make_shared<connection>(ioc); bool run_finished = true;
run(conn, cfg); conn.async_run(cfg, {}, [&run_finished](error_code ec) {
ioc.run(); 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) BOOST_AUTO_TEST_CASE(connect_bad_port)
{ {
net::io_context ioc; net::io_context ioc;
connection conn{ioc};
auto cfg = make_test_config(); auto cfg = make_test_config();
cfg.addr.host = "127.0.0.1"; cfg.addr.host = "127.0.0.1";
@@ -80,9 +92,14 @@ BOOST_AUTO_TEST_CASE(connect_bad_port)
cfg.health_check_interval = 10h; cfg.health_check_interval = 10h;
cfg.reconnect_wait_interval = 0s; cfg.reconnect_wait_interval = 0s;
auto conn = std::make_shared<connection>(ioc); bool run_finished = true;
run(conn, cfg, net::error::connection_refused); conn.async_run(cfg, {}, [&run_finished](error_code ec) {
ioc.run(); run_finished = true;
BOOST_TEST(ec != error_code());
});
ioc.run_for(4 * test_timeout);
BOOST_TEST(run_finished);
} }
// Hard to test. // Hard to test.
@@ -101,3 +118,5 @@ BOOST_AUTO_TEST_CASE(connect_bad_port)
// run(conn, cfg, boost::redis::error::connect_timeout); // run(conn, cfg, boost::redis::error::connect_timeout);
// ioc.run(); // ioc.run();
//} //}
} // namespace