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

@@ -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(

View File

@@ -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
;

View File

@@ -5,6 +5,7 @@
#include <cstdlib>
#include <iostream>
#include <stdexcept>
namespace net = boost::asio;
@@ -55,22 +56,17 @@ boost::redis::config make_test_config()
}
#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::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

View File

@@ -8,14 +8,22 @@
#include <boost/asio/use_awaitable.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#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
inline auto redir(boost::system::error_code& 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
boost::redis::config make_test_config();

View File

@@ -7,7 +7,7 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/response.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE check_health
#include <boost/test/included/unit_test.hpp>
@@ -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

View File

@@ -5,11 +5,14 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/deferred.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/io_context.hpp>
#include <cstddef>
#include <exception>
#define BOOST_TEST_MODULE echo_stress
#include <boost/test/included/unit_test.hpp>
@@ -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<connection> conn, int expected) -> net::awaitable<void>
} // namespace boost::redis
namespace {
auto push_consumer(connection& conn, int expected) -> net::awaitable<void>
{
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<connection> conn, std::shared_ptr<request> pubs, int n)
-> net::awaitable<void>
auto echo_session(connection& conn, const request& pubs, int n) -> net::awaitable<void>
{
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;
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;
// 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);
if (exc)
std::rethrow_exception(exc);
}
BOOST_AUTO_TEST_CASE(echo_stress)
{
net::io_context ioc;
auto conn = std::make_shared<connection>(ioc);
net::co_spawn(ioc, async_echo_stress(conn), net::detached);
ioc.run();
// Setup
net::io_context ctx;
connection conn{ctx};
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
BOOST_AUTO_TEST_CASE(dummy) { BOOST_TEST(true); }
BOOST_AUTO_TEST_CASE(dummy) { }
#endif

View File

@@ -8,8 +8,8 @@
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#include <string>
#define BOOST_TEST_MODULE conn_exec
#include <boost/test/included/unit_test.hpp>
@@ -18,8 +18,8 @@
#include <iostream>
// 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<connection>(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<connection>(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<request>();
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<connection>(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

View File

@@ -7,19 +7,19 @@
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_exec_cancel
#include <boost/asio/detached.hpp>
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <iostream>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp>
// 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<void>
{
auto ex = co_await net::this_coro::executor;
@@ -48,7 +50,7 @@ auto implicit_cancel_of_req_written() -> net::awaitable<void>
// 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<void>
// 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

View File

@@ -6,9 +6,8 @@
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_exec_cancel
#include <boost/asio/detached.hpp>
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
@@ -16,7 +15,6 @@
#include <iostream>
#ifdef BOOST_ASIO_HAS_CO_AWAIT
#include <boost/asio/experimental/awaitable_operators.hpp>
// 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<void>
{
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.
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

View File

@@ -5,31 +5,31 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/system/errc.hpp>
#define BOOST_TEST_MODULE conn_exec_error
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <cstddef>
#include <iostream>
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<connection>(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<connection>(ioc);
response<std::string, std::string> 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<connection>(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<connection>(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<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;
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

View File

@@ -6,8 +6,9 @@
#include <boost/redis/connection.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/system/errc.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_exec_retry
#include <boost/test/included/unit_test.hpp>
@@ -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<connection>(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();
});
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();
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<connection>(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

View File

@@ -7,24 +7,23 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/experimental/channel_error.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 "common.hpp"
#include <cstddef>
#include <iostream>
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<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;
};
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<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) {
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<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 { };
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<void> push_consumer3(std::shared_ptr<connection> conn)
void launch_push_consumer(std::shared_ptr<connection> 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<connection>(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

View File

@@ -6,7 +6,9 @@
#include <boost/redis/connection.hpp>
#include <boost/system/errc.hpp>
#include <boost/system/error_code.hpp>
#include <cstddef>
#define BOOST_TEST_MODULE conn_quit
#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.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);
}

View File

@@ -4,9 +4,11 @@
* accompanying file LICENSE.txt)
*/
#include <boost/redis/config.hpp>
#include <boost/redis/connection.hpp>
#include <boost/asio/detached.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE conn_reconnect
#include <boost/test/included/unit_test.hpp>
@@ -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<void> 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<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 (; 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;
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<void>
@@ -104,10 +117,11 @@ auto async_test_reconnect_timeout() -> net::awaitable<void>
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

View File

@@ -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 <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 "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<ignore_t, ignore_t, bool, bool> 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<ignore_t, double> 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

View File

@@ -7,18 +7,16 @@
#include <boost/redis/connection.hpp>
#include <boost/redis/logger.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#define BOOST_TEST_MODULE conn_quit
#include <boost/asio/error.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE issue_181
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <chrono>
#include <iostream>
#include <memory>
#include <optional>
#include <string>
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<net::any_io_executor>;
@@ -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

View File

@@ -12,22 +12,21 @@
#include <boost/asio/as_tuple.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/consign.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/redirect_error.hpp>
#include <boost/asio/use_awaitable.hpp>
#define BOOST_TEST_MODULE conn - quit
#include <boost/system/error_code.hpp>
#include <exception>
#define BOOST_TEST_MODULE issue50
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <iostream>
#include <tuple>
#if defined(BOOST_ASIO_HAS_CO_AWAIT)
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::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<connection> conn) -> net::awaitable<void>
{
@@ -50,7 +49,7 @@ auto receiver(std::shared_ptr<connection> conn) -> net::awaitable<void>
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<connection> conn) -> net::awaitable<void>
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<connection> conn) -> net::awaitable<void>
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)
{
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<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)
BOOST_AUTO_TEST_CASE(issue_50) { }
} // namespace
#else
BOOST_AUTO_TEST_CASE(dummy) { }
#endif // defined(BOOST_ASIO_HAS_CO_AWAIT)

View File

@@ -5,13 +5,13 @@
*/
#include <boost/redis/connection.hpp>
#include <boost/system/error_code.hpp>
#define BOOST_TEST_MODULE run
#include <boost/test/included/unit_test.hpp>
#include "common.hpp"
#include <iostream>
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<connection>(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<connection>(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<connection>(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