Files
scylladb/test/boost/idl_test.cc
Avi Kivity f3eade2f62 treewide: relicense to ScyllaDB-Source-Available-1.0
Drop the AGPL license in favor of a source-available license.
See the blog post [1] for details.

[1] https://www.scylladb.com/2024/12/18/why-were-moving-to-a-source-available-license/
2024-12-18 17:45:13 +02:00

487 lines
16 KiB
C++

/*
* Copyright 2016-present ScyllaDB
*/
/*
* SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0
*/
#define BOOST_TEST_MODULE core
#include "utils/assert.hh"
#include <boost/test/unit_test.hpp>
#include <seastar/util/variant_utils.hh>
#include <fmt/ranges.h>
#include <vector>
#include <optional>
#include <fmt/ranges.h>
#include "test/lib/test_utils.hh"
#include "bytes.hh"
#include "bytes_ostream.hh"
struct simple_compound {
// TODO: change this to test for #905
uint32_t foo;
uint32_t bar;
bool operator==(const simple_compound& other) const {
return foo == other.foo && bar == other.bar;
}
};
class non_final_composite_test_object {
simple_compound _x;
public:
static thread_local int construction_count;
non_final_composite_test_object(simple_compound x) : _x(x) {
++construction_count;
}
simple_compound x() const { return _x; }
};
class final_composite_test_object {
simple_compound _x;
public:
static thread_local int construction_count;
final_composite_test_object(simple_compound x) : _x(x) {
++construction_count;
}
simple_compound x() const { return _x; }
};
thread_local int non_final_composite_test_object::construction_count = 0;
thread_local int final_composite_test_object::construction_count = 0;
template <> struct fmt::formatter<simple_compound> : fmt::formatter<string_view> {
auto format(const simple_compound& sc, fmt::format_context& ctx) const {
return fmt::format_to(ctx.out(), " {{ foo: {}, bar: {} }}", sc.foo, sc.bar);
}
};
std::ostream& operator<<(std::ostream& os, const simple_compound& sc)
{
fmt::print(os, "{}", sc);
return os;
}
struct compound_with_optional {
std::optional<simple_compound> first;
simple_compound second;
bool operator==(const compound_with_optional& other) const {
return first == other.first && second == other.second;
}
};
std::ostream& operator<<(std::ostream& os, const compound_with_optional& v)
{
os << " { first: ";
if (v.first) {
fmt::print(os, "{}", *v.first);
} else {
fmt::print(os, "<disengaged>");
}
fmt::print(os, ", second: {}}}", v.second);
return os;
}
struct wrapped_vector {
std::vector<simple_compound> vector;
bool operator==(const wrapped_vector& v) const { // = default;
return vector == v.vector;
}
};
std::ostream& operator<<(std::ostream& os, const wrapped_vector& v)
{
fmt::print(os, "{}", v.vector);
return os;
}
struct vectors_of_compounds {
std::vector<simple_compound> first;
wrapped_vector second;
};
struct empty_struct { };
struct empty_final_struct { };
class fragment_generator {
std::vector<bytes> data;
public:
using fragment_type = bytes_view;
using iterator = std::vector<bytes>::iterator;
using const_iterator = std::vector<bytes>::const_iterator;
fragment_generator(size_t fragment_count, size_t fragment_size) : data(fragment_count, bytes(fragment_size, 'x')) {
}
iterator begin() {
return data.begin();
}
iterator end() {
return data.end();
}
const_iterator begin() const {
return data.begin();
}
const_iterator end() const {
return data.end();
}
size_t size_bytes() const {
return data.empty() ? 0 : data.size() * data.front().size();
}
bool empty() const {
return data.empty();
}
bytes to_bytes() const {
return data.empty() ? bytes() : bytes(data.size() * data.front().size(), 'x');
}
};
template <typename T>
struct const_template_arg_wrapper {
T x;
const_template_arg_wrapper(const T& t)
: x(t)
{}
bool operator == (const const_template_arg_wrapper& rhs) const {
return x == rhs.x;
}
};
struct const_template_arg_test_object {
std::vector<const_template_arg_wrapper<const simple_compound>> first;
bool operator == (const const_template_arg_test_object& rhs) const {
return first == rhs.first;
}
};
#include "idl/idl_test.dist.hh"
#include "idl/idl_test.dist.impl.hh"
BOOST_AUTO_TEST_CASE(test_simple_compound)
{
simple_compound sc = { 0xdeadbeef, 0xbadc0ffe };
bytes_ostream buf1;
ser::serialize(buf1, sc);
BOOST_REQUIRE_EQUAL(buf1.size(), 12);
bytes_ostream buf2;
ser::writer_of_writable_simple_compound<bytes_ostream> wowsc(buf2);
std::move(wowsc).write_foo(sc.foo).write_bar(sc.bar).end_writable_simple_compound();
BOOST_REQUIRE_EQUAL(buf1.linearize(), buf2.linearize());
auto bv1 = buf1.linearize();
auto in1 = ser::as_input_stream(bv1);
auto deser_sc = ser::deserialize(in1, std::type_identity<simple_compound>());
BOOST_REQUIRE_EQUAL(sc, deser_sc);
auto bv2 = buf2.linearize();
auto in2 = ser::as_input_stream(bv2);
auto sc_view = ser::deserialize(in2, std::type_identity<ser::writable_simple_compound_view>());
BOOST_REQUIRE_EQUAL(sc.foo, sc_view.foo());
BOOST_REQUIRE_EQUAL(sc.bar, sc_view.bar());
}
BOOST_AUTO_TEST_CASE(test_vector)
{
std::vector<simple_compound> vec1 = {
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
{ 9, 10 },
};
std::vector<simple_compound> vec2 = {
{ 11, 12 },
{ 13, 14 },
{ 15, 16 },
{ 17, 18 },
{ 19, 20 },
};
vectors_of_compounds voc = { vec1, wrapped_vector { vec2 } };
bytes_ostream buf1;
ser::serialize(buf1, voc);
BOOST_REQUIRE_EQUAL(buf1.size(), 136);
bytes_ostream buf2;
ser::writer_of_writable_vectors_of_compounds<bytes_ostream> wowvoc(buf2);
auto first_writer = std::move(wowvoc).start_first();
for (auto& c : vec1) {
first_writer.add().write_foo(c.foo).write_bar(c.bar).end_writable_simple_compound();
}
auto second_writer = std::move(first_writer).end_first().start_second().start_vector();
for (auto& c : vec2) {
second_writer.add_vector(c);
}
std::move(second_writer).end_vector().end_second().end_writable_vectors_of_compounds();
BOOST_REQUIRE_EQUAL(buf1.linearize(), buf2.linearize());
auto bv1 = buf1.linearize();
auto in1 = ser::as_input_stream(bv1);
auto deser_voc = ser::deserialize(in1, std::type_identity<vectors_of_compounds>());
BOOST_REQUIRE_EQUAL(voc.first, deser_voc.first);
BOOST_REQUIRE_EQUAL(voc.second, deser_voc.second);
auto bv2 = buf2.linearize();
auto in2 = ser::as_input_stream(bv2);
auto voc_view = ser::deserialize(in2, std::type_identity<ser::writable_vectors_of_compounds_view>());
auto first_range = voc_view.first();
auto first_view = std::vector<ser::writable_simple_compound_view>(first_range.begin(), first_range.end());
BOOST_REQUIRE_EQUAL(vec1.size(), first_view.size());
for (size_t i = 0; i < first_view.size(); i++) {
auto fv = first_view[i];
SCYLLA_ASSERT(vec1[i].foo == fv.foo());
BOOST_REQUIRE_EQUAL(vec1[i].foo, first_view[i].foo());
BOOST_REQUIRE_EQUAL(vec1[i].bar, first_view[i].bar());
}
auto second_range = voc_view.second().vector();
auto second_view = std::vector<simple_compound>(second_range.begin(), second_range.end());
BOOST_REQUIRE_EQUAL(vec2.size(), second_view.size());
for (size_t i = 0; i < second_view.size(); i++) {
BOOST_REQUIRE_EQUAL(vec2[i], second_view[i]);
}
}
BOOST_AUTO_TEST_CASE(test_variant)
{
std::vector<simple_compound> vec = {
{ 1, 2 },
{ 3, 4 },
{ 5, 6 },
{ 7, 8 },
{ 9, 10 },
};
simple_compound sc = { 0xdeadbeef, 0xbadc0ffe };
simple_compound sc2 = { 0x12344321, 0x56788765 };
bytes_ostream buf;
ser::writer_of_writable_variants<bytes_ostream> wowv(buf);
auto second_writer = std::move(wowv).write_id(17).write_first_simple_compound(sc).start_second_writable_vector().start_vector();
for (auto&& v : vec) {
second_writer.add_vector(v);
}
auto third_writer = std::move(second_writer).end_vector().end_writable_vector().start_third_writable_final_simple_compound();
std::move(third_writer).write_foo(sc2.foo).write_bar(sc2.bar).end_writable_final_simple_compound().end_writable_variants();
BOOST_REQUIRE_EQUAL(buf.size(), 120);
auto bv = buf.linearize();
auto in = ser::as_input_stream(bv);
auto wv_view = ser::deserialize(in, std::type_identity<ser::writable_variants_view>());
BOOST_REQUIRE_EQUAL(wv_view.id(), 17);
struct expect_compound : boost::static_visitor<simple_compound> {
simple_compound operator()(ser::writable_vector_view&) const {
throw std::runtime_error("got writable_vector, expected simple_compound");
}
simple_compound operator()(simple_compound& sc) const {
return sc;
}
simple_compound operator()(ser::writable_final_simple_compound_view&) const {
throw std::runtime_error("got writable_final_simple_compound, expected simple_compound");
}
simple_compound operator()(ser::unknown_variant_type&) const {
throw std::runtime_error("unknown type, expected simple_compound");
}
};
auto v1 = wv_view.first();
auto&& compound = boost::apply_visitor(expect_compound(), v1);
BOOST_REQUIRE_EQUAL(compound, sc);
struct expect_vector : boost::static_visitor<std::vector<simple_compound>> {
std::vector<simple_compound> operator()(ser::writable_vector_view& wvv) const {
auto range = wvv.vector();
return std::vector<simple_compound>(range.begin(), range.end());
}
std::vector<simple_compound> operator()(simple_compound&) const {
throw std::runtime_error("got simple_compound, expected writable_vector");
}
std::vector<simple_compound> operator()(ser::writable_final_simple_compound_view&) const {
throw std::runtime_error("got writable_final_simple_compound, expected writable_vector");
}
std::vector<simple_compound> operator()(ser::unknown_variant_type&) const {
throw std::runtime_error("unknown type, expected writable_vector");
}
};
auto v2 = wv_view.second();
auto&& vector = boost::apply_visitor(expect_vector(), v2);
BOOST_REQUIRE_EQUAL(vector, vec);
struct expect_writable_compound : boost::static_visitor<simple_compound> {
simple_compound operator()(ser::writable_vector_view&) const {
throw std::runtime_error("got writable_vector, expected writable_final_simple_compound");
}
simple_compound operator()(simple_compound&) const {
throw std::runtime_error("got simple_compound, expected writable_final_simple_compound");
}
simple_compound operator()(ser::writable_final_simple_compound_view& scv) const {
return simple_compound { scv.foo(), scv.bar() };
}
simple_compound operator()(ser::unknown_variant_type&) const {
throw std::runtime_error("unknown type, expected writable_final_simple_compound");
}
};
auto v3 = wv_view.third();
auto&& compound2 = boost::apply_visitor(expect_writable_compound(), v3);
BOOST_REQUIRE_EQUAL(compound2, sc2);
}
BOOST_AUTO_TEST_CASE(test_compound_with_optional)
{
simple_compound foo = { 0xdeadbeef, 0xbadc0ffe };
simple_compound bar = { 0x12345678, 0x87654321 };
compound_with_optional one = { foo, bar };
bytes_ostream buf1;
ser::serialize(buf1, one);
BOOST_REQUIRE_EQUAL(buf1.size(), 29);
auto bv1 = buf1.linearize();
seastar::simple_input_stream in1(reinterpret_cast<const char*>(bv1.data()), bv1.size());
auto deser_one = ser::deserialize(in1, std::type_identity<compound_with_optional>());
BOOST_REQUIRE_EQUAL(one, deser_one);
compound_with_optional two = { {}, foo };
bytes_ostream buf2;
ser::serialize(buf2, two);
BOOST_REQUIRE_EQUAL(buf2.size(), 17);
auto bv2 = buf2.linearize();
seastar::simple_input_stream in2(reinterpret_cast<const char*>(bv2.data()), bv2.size());
auto deser_two = ser::deserialize(in2, std::type_identity<compound_with_optional>());
BOOST_REQUIRE_EQUAL(two, deser_two);
}
BOOST_AUTO_TEST_CASE(test_skip_does_not_deserialize)
{
{
non_final_composite_test_object x({1, 2});
bytes_ostream buf;
ser::serialize(buf, x);
auto in = ser::as_input_stream(buf.linearize());
auto prev = non_final_composite_test_object::construction_count;
ser::skip(in, std::type_identity<non_final_composite_test_object>());
BOOST_REQUIRE(prev == non_final_composite_test_object::construction_count);
}
{
final_composite_test_object x({1, 2});
bytes_ostream buf;
ser::serialize(buf, x);
auto in = ser::as_input_stream(buf.linearize());
auto prev = final_composite_test_object::construction_count;
ser::skip(in, std::type_identity<final_composite_test_object>());
BOOST_REQUIRE(prev == final_composite_test_object::construction_count);
}
}
BOOST_AUTO_TEST_CASE(test_empty_struct)
{
bytes_ostream buf1;
ser::serialize(buf1, empty_struct());
auto in1 = ser::as_input_stream(buf1.linearize());
ser::deserialize(in1, std::type_identity<empty_struct>());
bytes_ostream buf2;
ser::serialize(buf2, empty_final_struct());
auto in2 = ser::as_input_stream(buf2.linearize());
ser::deserialize(in2, std::type_identity<empty_final_struct>());
}
BOOST_AUTO_TEST_CASE(test_just_a_variant)
{
bytes_ostream buf;
ser::writer_of_just_a_variant(buf)
.start_variant_writable_simple_compound()
.write_foo(0x1234abcd)
.write_bar(0x1111ffff)
.end_writable_simple_compound()
.end_just_a_variant();
auto in = ser::as_input_stream(buf);
auto view = ser::deserialize(in, std::type_identity<ser::just_a_variant_view>());
bool fired = false;
seastar::visit(view.variant(), [&] (ser::writable_simple_compound_view v) {
fired = true;
BOOST_CHECK_EQUAL(v.foo(), 0x1234abcd);
BOOST_CHECK_EQUAL(v.bar(), 0x1111ffff);
},
[&] (simple_compound) { BOOST_FAIL("should not reach"); },
[&] (ser::unknown_variant_type) { BOOST_FAIL("should not reach"); }
);
BOOST_CHECK(fired);
buf = bytes_ostream();
ser::writer_of_just_a_variant(buf)
.write_variant_simple_compound(simple_compound { 0xaaaabbbb, 0xccccdddd })
.end_just_a_variant();
in = ser::as_input_stream(buf);
view = ser::deserialize(in, std::type_identity<ser::just_a_variant_view>());
fired = false;
seastar::visit(view.variant(), [&] (simple_compound v) {
fired = true;
BOOST_CHECK_EQUAL(v.foo, 0xaaaabbbb);
BOOST_CHECK_EQUAL(v.bar, 0xccccdddd);
},
[&] (ser::writable_simple_compound_view) { BOOST_FAIL("should not reach"); },
[&] (ser::unknown_variant_type) { BOOST_FAIL("should not reach"); }
);
BOOST_CHECK(fired);
}
BOOST_AUTO_TEST_CASE(test_fragmented_write)
{
for (auto [fragment_count, fragment_size] : {std::pair<size_t, size_t>{9, 1025}, {6, 8999}, {2, 29521}, {1, 60001}, {0, 0}}) {
bytes_ostream buf;
ser::serialize_fragmented(buf, fragment_generator(fragment_count, fragment_size));
auto in = ser::as_input_stream(buf);
bytes deserialized = ser::deserialize(in, std::type_identity<bytes>());
BOOST_CHECK_EQUAL(deserialized, fragment_generator(fragment_count, fragment_size).to_bytes());
}
}
BOOST_AUTO_TEST_CASE(test_const_template_arg)
{
const_template_arg_test_object obj {
.first = {
simple_compound{ 0xdeadbeef, 0xbadc0ffe },
simple_compound{ 0xbaaaaaad, 0xdeadc0de }
}
};
bytes_ostream buf;
ser::serialize(buf, obj);
BOOST_REQUIRE_EQUAL(buf.size(), 40);
auto in = ser::as_input_stream(buf);
auto deser_obj = ser::deserialize(in, std::type_identity<const_template_arg_test_object>());
BOOST_REQUIRE(obj == deser_obj);
}