mirror of
https://github.com/boostorg/redis.git
synced 2026-01-19 04:42:09 +00:00
Makes flat_tree aware of incremental parsing to avoid race conditions with pushes (#378)
Adds the concept of a "temporary working area" to flat_tree. Nodes in this area belong to a partially parsed message, and are hidden from the user. Now flat_tree can be used as the receive response without explicitly handling partial messages. Changes flat_tree::get_view return type from const vector& to span. Adds flat_tree::capacity. Splits generic_flat_response tests to a separate file and adds extra cases. close #369
This commit is contained in:
committed by
GitHub
parent
7750a6b126
commit
3b07119e54
@@ -53,6 +53,7 @@ make_test(test_multiplexer)
|
||||
make_test(test_parse_sentinel_response)
|
||||
make_test(test_update_sentinel_list)
|
||||
make_test(test_flat_tree)
|
||||
make_test(test_generic_flat_response)
|
||||
make_test(test_read_buffer)
|
||||
|
||||
# Tests that require a real Redis server
|
||||
|
||||
@@ -70,6 +70,7 @@ local tests =
|
||||
test_parse_sentinel_response
|
||||
test_update_sentinel_list
|
||||
test_flat_tree
|
||||
test_generic_flat_response
|
||||
test_read_buffer
|
||||
;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <boost/redis/adapter/any_adapter.hpp>
|
||||
#include <boost/redis/connection.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/asio/detached.hpp>
|
||||
|
||||
@@ -180,4 +181,31 @@ BOOST_AUTO_TEST_CASE(exec_any_adapter)
|
||||
BOOST_TEST(std::get<0>(res).value() == "PONG");
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(exec_generic_flat_response)
|
||||
{
|
||||
// Executing with a generic_flat_response works
|
||||
request req;
|
||||
req.push("PING", "PONG");
|
||||
boost::redis::generic_flat_response resp;
|
||||
|
||||
net::io_context ioc;
|
||||
|
||||
auto conn = std::make_shared<connection>(ioc);
|
||||
|
||||
bool finished = false;
|
||||
|
||||
conn->async_exec(req, resp, [&](error_code ec, std::size_t) {
|
||||
BOOST_TEST(ec == error_code());
|
||||
conn->cancel();
|
||||
finished = true;
|
||||
});
|
||||
|
||||
run(conn);
|
||||
ioc.run_for(test_timeout);
|
||||
BOOST_TEST_REQUIRE(finished);
|
||||
|
||||
BOOST_TEST(resp.has_value());
|
||||
BOOST_TEST(resp->get_view().front().value == "PONG");
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/resp3/flat_tree.hpp>
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/resp3/parser.hpp>
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
|
||||
#include <boost/assert/source_location.hpp>
|
||||
@@ -32,6 +33,7 @@ using boost::redis::resp3::type;
|
||||
using boost::redis::resp3::detail::deserialize;
|
||||
using boost::redis::resp3::node;
|
||||
using boost::redis::resp3::node_view;
|
||||
using boost::redis::resp3::parser;
|
||||
using boost::redis::resp3::to_string;
|
||||
using boost::redis::response;
|
||||
using boost::system::error_code;
|
||||
@@ -49,6 +51,20 @@ void add_nodes(
|
||||
std::cerr << "Called from " << loc << std::endl;
|
||||
}
|
||||
|
||||
bool parse_checked(
|
||||
flat_tree& to,
|
||||
parser& p,
|
||||
std::string_view data,
|
||||
boost::source_location loc = BOOST_CURRENT_LOCATION)
|
||||
{
|
||||
error_code ec;
|
||||
auto adapter = adapt2(to);
|
||||
bool done = boost::redis::resp3::parse(p, data, adapter, ec);
|
||||
if (!BOOST_TEST_EQ(ec, error_code{}))
|
||||
std::cerr << "Called from " << loc << std::endl;
|
||||
return done;
|
||||
}
|
||||
|
||||
void check_nodes(
|
||||
const flat_tree& tree,
|
||||
boost::span<const node_view> expected,
|
||||
@@ -202,6 +218,116 @@ void test_add_nodes_big_node()
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Flat trees have a temporary area (tmp) where nodes are stored while
|
||||
// messages are being parsed. Nodes in the tmp area are not part of the representation
|
||||
// until they are committed when the message has been fully parsed
|
||||
void test_add_nodes_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add part of a message, but not all of it.
|
||||
// These nodes are stored but are not part of the user-facing representation
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.data_size(), 0u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 512u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// Finish the message. Nodes will now show up
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 512u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// We can repeat this cycle again
|
||||
p.reset();
|
||||
BOOST_TEST_NOT(parse_checked(t, p, ">2\r\n+good\r\n"));
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 512u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
BOOST_TEST(parse_checked(t, p, ">2\r\n+good\r\n+bye\r\n"));
|
||||
expected_nodes.push_back({type::push, 2u, 0u, ""});
|
||||
expected_nodes.push_back({type::simple_string, 1u, 1u, "good"});
|
||||
expected_nodes.push_back({type::simple_string, 1u, 1u, "bye"});
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 17u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 512u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// If there was an unfinished message when another message is started,
|
||||
// the former is discarded
|
||||
void test_add_nodes_existing_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add part of a message
|
||||
BOOST_TEST_NOT(parse_checked(t, p, ">3\r\n+some message\r\n"));
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.data_size(), 0u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// This message is abandoned, and another one is started
|
||||
p.reset();
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "%66\r\n+abandoned\r\n"));
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.data_size(), 0u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// This happens again, but this time a complete message is added
|
||||
add_nodes(t, "*2\r\n+hello\r\n+world\r\n");
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// The same works even if there is existing committed data
|
||||
void test_add_nodes_existing_data_and_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add a full message
|
||||
add_nodes(t, "*2\r\n+hello\r\n+world\r\n");
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// Add part of a message
|
||||
p.reset();
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "%66\r\n+abandoned\r\n"));
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// This message is abandoned, and replaced by a full one
|
||||
add_nodes(t, "+complete message\r\n");
|
||||
expected_nodes.push_back({type::simple_string, 1u, 0u, "complete message"});
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 26u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// --- Reserving space ---
|
||||
// The usual case, calling it before using it
|
||||
void test_reserve()
|
||||
@@ -210,7 +336,7 @@ void test_reserve()
|
||||
|
||||
t.reserve(1024u, 5u);
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_view().capacity(), 5u);
|
||||
BOOST_TEST_GE(t.capacity(), 5u);
|
||||
BOOST_TEST_EQ(t.data_size(), 0u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 1024);
|
||||
BOOST_TEST_EQ(t.get_reallocs(), 1u);
|
||||
@@ -285,6 +411,32 @@ void test_reserve_with_data()
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Reserve also handles the tmp area
|
||||
void test_reserve_with_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add a partial message, and then reserve
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
t.reserve(1000u, 10u);
|
||||
|
||||
// Finish the current message so nodes in the tmp area show up
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
|
||||
// Check
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 10u);
|
||||
BOOST_TEST_EQ(t.data_capacity(), 1024u);
|
||||
BOOST_TEST_EQ(t.get_reallocs(), 2u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// --- Clear ---
|
||||
void test_clear()
|
||||
{
|
||||
@@ -349,6 +501,93 @@ void test_clear_reuse()
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Clear doesn't remove the tmp area
|
||||
void test_clear_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add a full message and part of another
|
||||
add_nodes(t, ">2\r\n+orange\r\n+apple\r\n");
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::push, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "orange"},
|
||||
{type::simple_string, 1u, 1u, "apple" },
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// Clearing removes the user-facing representation
|
||||
t.clear();
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// The nodes in the tmp area are still alive. Adding the remaining yields the full message
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
expected_nodes = {
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Clearing having only tmp area is safe
|
||||
void test_clear_only_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add part of a message
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// Clearing here does nothing
|
||||
t.clear();
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// The nodes in the tmp area are still alive. Adding the remaining yields the full message
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
std::vector<node_view> expected_nodes = {
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Clearing having tmp nodes but no data is also safe
|
||||
void test_clear_only_tmp_nodes()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
|
||||
// Add part of a message
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n"));
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// Clearing here does nothing
|
||||
t.clear();
|
||||
check_nodes(t, {});
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
|
||||
// The nodes in the tmp area are still alive. Adding the remaining yields the full message
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
std::vector<node_view> expected_nodes = {
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello"},
|
||||
{type::simple_string, 1u, 1u, "world"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// --- Default ctor ---
|
||||
void test_default_constructor()
|
||||
{
|
||||
@@ -437,6 +676,37 @@ void test_copy_ctor_adjust_capacity()
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Copying an object also copies its tmp area
|
||||
void test_copy_ctor_tmp()
|
||||
{
|
||||
// Setup
|
||||
flat_tree t;
|
||||
parser p;
|
||||
add_nodes(t, "+message\r\n");
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
};
|
||||
|
||||
// Copy. The copy has the tmp nodes but they're hidden in its tmp area
|
||||
flat_tree t2{t};
|
||||
check_nodes(t2, expected_nodes);
|
||||
BOOST_TEST_EQ(t2.data_size(), 7u);
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 1u);
|
||||
|
||||
// Finishing the message in the copy works
|
||||
BOOST_TEST(parse_checked(t2, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
expected_nodes = {
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello" },
|
||||
{type::simple_string, 1u, 1u, "world" },
|
||||
};
|
||||
check_nodes(t2, expected_nodes);
|
||||
BOOST_TEST_EQ(t2.data_size(), 17u);
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// --- Move ctor ---
|
||||
void test_move_ctor()
|
||||
{
|
||||
@@ -486,6 +756,37 @@ void test_move_ctor_with_capacity()
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 0u);
|
||||
}
|
||||
|
||||
// Moving an object also moves its tmp area
|
||||
void test_move_ctor_tmp()
|
||||
{
|
||||
// Setup
|
||||
flat_tree t;
|
||||
parser p;
|
||||
add_nodes(t, "+message\r\n");
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+hello\r\n"));
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
};
|
||||
|
||||
// Move. The new object has the same tmp area
|
||||
flat_tree t2{std::move(t)};
|
||||
check_nodes(t2, expected_nodes);
|
||||
BOOST_TEST_EQ(t2.data_size(), 7u);
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 1u);
|
||||
|
||||
// Finishing the message in the copy works
|
||||
BOOST_TEST(parse_checked(t2, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
expected_nodes = {
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello" },
|
||||
{type::simple_string, 1u, 1u, "world" },
|
||||
};
|
||||
check_nodes(t2, expected_nodes);
|
||||
BOOST_TEST_EQ(t2.data_size(), 17u);
|
||||
BOOST_TEST_EQ(t2.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// --- Copy assignment ---
|
||||
void test_copy_assign()
|
||||
{
|
||||
@@ -645,6 +946,40 @@ void test_copy_assign_self()
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
}
|
||||
|
||||
// Copy assignment also assigns the tmp area
|
||||
void test_copy_assign_tmp()
|
||||
{
|
||||
parser p;
|
||||
|
||||
flat_tree t;
|
||||
add_nodes(t, "+some_data\r\n");
|
||||
|
||||
flat_tree t2;
|
||||
add_nodes(t2, "+message\r\n");
|
||||
BOOST_TEST_NOT(parse_checked(t2, p, "*2\r\n+hello\r\n"));
|
||||
|
||||
// Assigning also copies where the tmp area starts
|
||||
t = t2;
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 7u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// The tmp area was also copied
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
expected_nodes = {
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello" },
|
||||
{type::simple_string, 1u, 1u, "world" },
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 17u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// --- Move assignment ---
|
||||
void test_move_assign()
|
||||
{
|
||||
@@ -721,6 +1056,40 @@ void test_move_assign_both_empty()
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 0u);
|
||||
}
|
||||
|
||||
// Move assignment also propagates the tmp area
|
||||
void test_move_assign_tmp()
|
||||
{
|
||||
parser p;
|
||||
|
||||
flat_tree t;
|
||||
add_nodes(t, "+some_data\r\n");
|
||||
|
||||
flat_tree t2;
|
||||
add_nodes(t2, "+message\r\n");
|
||||
BOOST_TEST_NOT(parse_checked(t2, p, "*2\r\n+hello\r\n"));
|
||||
|
||||
// When moving, the tmp area is moved, too
|
||||
t = std::move(t2);
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 7u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 1u);
|
||||
|
||||
// Finish the message
|
||||
BOOST_TEST(parse_checked(t, p, "*2\r\n+hello\r\n+world\r\n"));
|
||||
expected_nodes = {
|
||||
{type::simple_string, 1u, 0u, "message"},
|
||||
{type::array, 2u, 0u, "" },
|
||||
{type::simple_string, 1u, 1u, "hello" },
|
||||
{type::simple_string, 1u, 1u, "world" },
|
||||
};
|
||||
check_nodes(t, expected_nodes);
|
||||
BOOST_TEST_EQ(t.data_size(), 17u);
|
||||
BOOST_TEST_EQ(t.get_total_msgs(), 2u);
|
||||
}
|
||||
|
||||
// --- Comparison ---
|
||||
void test_comparison_different()
|
||||
{
|
||||
@@ -826,6 +1195,67 @@ void test_comparison_self()
|
||||
BOOST_TEST_NOT(tempty != tempty);
|
||||
}
|
||||
|
||||
// The tmp area is not taken into account when comparing
|
||||
void test_comparison_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
add_nodes(t, "+hello\r\n");
|
||||
|
||||
flat_tree t2;
|
||||
add_nodes(t2, "+hello\r\n");
|
||||
parser p;
|
||||
BOOST_TEST_NOT(parse_checked(t2, p, "*2\r\n+more data\r\n"));
|
||||
|
||||
BOOST_TEST(t == t2);
|
||||
BOOST_TEST_NOT(t != t2);
|
||||
}
|
||||
|
||||
void test_comparison_tmp_different()
|
||||
{
|
||||
flat_tree t;
|
||||
add_nodes(t, "+hello\r\n");
|
||||
|
||||
flat_tree t2;
|
||||
add_nodes(t2, "+world\r\n");
|
||||
parser p;
|
||||
BOOST_TEST_NOT(parse_checked(t2, p, "*2\r\n+more data\r\n"));
|
||||
|
||||
BOOST_TEST_NOT(t == t2);
|
||||
BOOST_TEST(t != t2);
|
||||
}
|
||||
|
||||
// Comparing object with only tmp area doesn't cause trouble
|
||||
void test_comparison_only_tmp()
|
||||
{
|
||||
flat_tree t;
|
||||
parser p;
|
||||
BOOST_TEST_NOT(parse_checked(t, p, "*2\r\n+more data\r\n"));
|
||||
|
||||
flat_tree t2;
|
||||
parser p2;
|
||||
BOOST_TEST_NOT(parse_checked(t2, p2, "*2\r\n+random\r\n"));
|
||||
|
||||
BOOST_TEST(t == t2);
|
||||
BOOST_TEST_NOT(t != t2);
|
||||
}
|
||||
|
||||
// --- Capacity ---
|
||||
// Delegates to the underlying vector function
|
||||
void test_capacity()
|
||||
{
|
||||
flat_tree t;
|
||||
BOOST_TEST_EQ(t.capacity(), 0u);
|
||||
|
||||
// Inserting a node increases capacity.
|
||||
// It is not specified how capacity grows, though.
|
||||
add_nodes(t, "+hello\r\n");
|
||||
BOOST_TEST_GE(t.capacity(), 1u);
|
||||
|
||||
// Reserve also affects capacity
|
||||
t.reserve(1000u, 8u);
|
||||
BOOST_TEST_GE(t.capacity(), 8u);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main()
|
||||
@@ -834,15 +1264,22 @@ int main()
|
||||
test_add_nodes_copies();
|
||||
test_add_nodes_capacity_limit();
|
||||
test_add_nodes_big_node();
|
||||
test_add_nodes_tmp();
|
||||
test_add_nodes_existing_tmp();
|
||||
test_add_nodes_existing_data_and_tmp();
|
||||
|
||||
test_reserve();
|
||||
test_reserve_not_power_of_2();
|
||||
test_reserve_below_current_capacity();
|
||||
test_reserve_with_data();
|
||||
test_reserve_with_tmp();
|
||||
|
||||
test_clear();
|
||||
test_clear_empty();
|
||||
test_clear_reuse();
|
||||
test_clear_tmp();
|
||||
test_clear_only_tmp();
|
||||
test_clear_only_tmp_nodes();
|
||||
|
||||
test_default_constructor();
|
||||
|
||||
@@ -850,15 +1287,12 @@ int main()
|
||||
test_copy_ctor_empty();
|
||||
test_copy_ctor_empty_with_capacity();
|
||||
test_copy_ctor_adjust_capacity();
|
||||
test_copy_ctor_tmp();
|
||||
|
||||
test_move_ctor();
|
||||
test_move_ctor_empty();
|
||||
test_move_ctor_with_capacity();
|
||||
|
||||
test_move_assign();
|
||||
test_move_assign_target_empty();
|
||||
test_move_assign_source_empty();
|
||||
test_move_assign_both_empty();
|
||||
test_move_ctor_tmp();
|
||||
|
||||
test_copy_assign();
|
||||
test_copy_assign_target_empty();
|
||||
@@ -868,6 +1302,13 @@ int main()
|
||||
test_copy_assign_source_with_extra_capacity();
|
||||
test_copy_assign_both_empty();
|
||||
test_copy_assign_self();
|
||||
test_copy_assign_tmp();
|
||||
|
||||
test_move_assign();
|
||||
test_move_assign_target_empty();
|
||||
test_move_assign_source_empty();
|
||||
test_move_assign_both_empty();
|
||||
test_move_assign_tmp();
|
||||
|
||||
test_comparison_different();
|
||||
test_comparison_different_node_types();
|
||||
@@ -876,6 +1317,11 @@ int main()
|
||||
test_comparison_equal_capacity();
|
||||
test_comparison_empty();
|
||||
test_comparison_self();
|
||||
test_comparison_tmp();
|
||||
test_comparison_tmp_different();
|
||||
test_comparison_only_tmp();
|
||||
|
||||
test_capacity();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
|
||||
119
test/test_generic_flat_response.cpp
Normal file
119
test/test_generic_flat_response.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/* Copyright (c) 2018-2026 Marcelo Zimbres Silva (mzimbres@gmail.com),
|
||||
* Ruben Perez Hidalgo (rubenperez038@gmail.com)
|
||||
*
|
||||
* Distributed under the Boost Software License, Version 1.0. (See
|
||||
* accompanying file LICENSE.txt)
|
||||
*/
|
||||
|
||||
#include <boost/redis/adapter/adapt.hpp>
|
||||
#include <boost/redis/resp3/node.hpp>
|
||||
#include <boost/redis/resp3/serialization.hpp>
|
||||
#include <boost/redis/resp3/type.hpp>
|
||||
#include <boost/redis/response.hpp>
|
||||
|
||||
#include <boost/core/lightweight_test.hpp>
|
||||
#include <boost/system/error_code.hpp>
|
||||
|
||||
#include "print_node.hpp"
|
||||
|
||||
using namespace boost::redis;
|
||||
using boost::system::error_code;
|
||||
using boost::redis::resp3::detail::deserialize;
|
||||
using resp3::node_view;
|
||||
using resp3::type;
|
||||
using adapter::adapt2;
|
||||
|
||||
// Regular nodes are just stored
|
||||
void test_success()
|
||||
{
|
||||
generic_flat_response resp;
|
||||
|
||||
error_code ec;
|
||||
deserialize("+hello\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
BOOST_TEST(resp.has_value());
|
||||
std::vector<node_view> expected_nodes{
|
||||
{type::simple_string, 1u, 0u, "hello"},
|
||||
};
|
||||
BOOST_TEST_ALL_EQ(
|
||||
resp->get_view().begin(),
|
||||
resp->get_view().end(),
|
||||
expected_nodes.begin(),
|
||||
expected_nodes.end());
|
||||
}
|
||||
|
||||
// If an error of any kind appears, we set the overall result to error
|
||||
void test_simple_error()
|
||||
{
|
||||
generic_flat_response resp;
|
||||
|
||||
error_code ec;
|
||||
deserialize("-Error\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
BOOST_TEST(resp.has_error());
|
||||
auto const error = resp.error();
|
||||
|
||||
BOOST_TEST_EQ(error.data_type, resp3::type::simple_error);
|
||||
BOOST_TEST_EQ(error.diagnostic, "Error");
|
||||
}
|
||||
|
||||
void test_blob_error()
|
||||
{
|
||||
generic_flat_response resp;
|
||||
|
||||
error_code ec;
|
||||
deserialize("!5\r\nError\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
BOOST_TEST(resp.has_error());
|
||||
auto const error = resp.error();
|
||||
|
||||
BOOST_TEST_EQ(error.data_type, resp3::type::blob_error);
|
||||
BOOST_TEST_EQ(error.diagnostic, "Error");
|
||||
}
|
||||
|
||||
// Mixing success and error nodes is safe. Only the last error is stored
|
||||
void test_mix_success_error()
|
||||
{
|
||||
generic_flat_response resp;
|
||||
error_code ec;
|
||||
|
||||
// Success message
|
||||
deserialize("+message\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
// An error
|
||||
deserialize("-Error\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
// Another success message
|
||||
deserialize("+other data\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
// Another error
|
||||
deserialize("-Different err\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
// Final success message
|
||||
deserialize("*1\r\n+last message\r\n", adapt2(resp), ec);
|
||||
BOOST_TEST_EQ(ec, error_code{});
|
||||
|
||||
// Check
|
||||
BOOST_TEST(resp.has_error());
|
||||
auto const error = resp.error();
|
||||
|
||||
BOOST_TEST_EQ(error.data_type, resp3::type::simple_error);
|
||||
BOOST_TEST_EQ(error.diagnostic, "Different err");
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
test_success();
|
||||
test_simple_error();
|
||||
test_blob_error();
|
||||
test_mix_success_error();
|
||||
|
||||
return boost::report_errors();
|
||||
}
|
||||
@@ -23,7 +23,6 @@ using boost::redis::request;
|
||||
using boost::redis::adapter::adapt2;
|
||||
using boost::redis::adapter::result;
|
||||
using boost::redis::resp3::tree;
|
||||
using boost::redis::resp3::flat_tree;
|
||||
using boost::redis::generic_flat_response;
|
||||
using boost::redis::ignore_t;
|
||||
using boost::redis::resp3::detail::deserialize;
|
||||
@@ -253,39 +252,3 @@ BOOST_AUTO_TEST_CASE(check_counter_adapter)
|
||||
BOOST_CHECK_EQUAL(node, 7);
|
||||
BOOST_CHECK_EQUAL(done, 1);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(generic_flat_response_simple_error)
|
||||
{
|
||||
generic_flat_response resp;
|
||||
|
||||
char const* wire = "-Error\r\n";
|
||||
|
||||
error_code ec;
|
||||
deserialize(wire, adapt2(resp), ec);
|
||||
BOOST_CHECK_EQUAL(ec, error_code{});
|
||||
|
||||
BOOST_TEST(!resp.has_value());
|
||||
BOOST_TEST(resp.has_error());
|
||||
auto const error = resp.error();
|
||||
|
||||
BOOST_CHECK_EQUAL(error.data_type, boost::redis::resp3::type::simple_error);
|
||||
BOOST_CHECK_EQUAL(error.diagnostic, std::string{"Error"});
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(generic_flat_response_blob_error)
|
||||
{
|
||||
generic_flat_response resp;
|
||||
|
||||
char const* wire = "!5\r\nError\r\n";
|
||||
|
||||
error_code ec;
|
||||
deserialize(wire, adapt2(resp), ec);
|
||||
BOOST_CHECK_EQUAL(ec, error_code{});
|
||||
|
||||
BOOST_TEST(!resp.has_value());
|
||||
BOOST_TEST(resp.has_error());
|
||||
auto const error = resp.error();
|
||||
|
||||
BOOST_CHECK_EQUAL(error.data_type, boost::redis::resp3::type::blob_error);
|
||||
BOOST_CHECK_EQUAL(error.diagnostic, std::string{"Error"});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user