/* * Copyright 2016-present ScyllaDB */ /* * SPDX-License-Identifier: LicenseRef-ScyllaDB-Source-Available-1.0 */ #define BOOST_TEST_MODULE core #include "utils/assert.hh" #include #include #include #include #include #include #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 : fmt::formatter { 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 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, ""); } fmt::print(os, ", second: {}}}", v.second); return os; } struct wrapped_vector { std::vector 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 first; wrapped_vector second; }; struct empty_struct { }; struct empty_final_struct { }; class fragment_generator { std::vector data; public: using fragment_type = bytes_view; using iterator = std::vector::iterator; using const_iterator = std::vector::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 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> 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 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()); 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()); BOOST_REQUIRE_EQUAL(sc.foo, sc_view.foo()); BOOST_REQUIRE_EQUAL(sc.bar, sc_view.bar()); } BOOST_AUTO_TEST_CASE(test_vector) { std::vector vec1 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 }, { 9, 10 }, }; std::vector 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 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()); 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()); auto first_range = voc_view.first(); auto first_view = std::vector(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(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 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 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()); BOOST_REQUIRE_EQUAL(wv_view.id(), 17); struct expect_compound : boost::static_visitor { 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 operator()(ser::writable_vector_view& wvv) const { auto range = wvv.vector(); return std::vector(range.begin(), range.end()); } std::vector operator()(simple_compound&) const { throw std::runtime_error("got simple_compound, expected writable_vector"); } std::vector operator()(ser::writable_final_simple_compound_view&) const { throw std::runtime_error("got writable_final_simple_compound, expected writable_vector"); } std::vector 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 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(bv1.data()), bv1.size()); auto deser_one = ser::deserialize(in1, std::type_identity()); 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(bv2.data()), bv2.size()); auto deser_two = ser::deserialize(in2, std::type_identity()); 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()); 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()); 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()); bytes_ostream buf2; ser::serialize(buf2, empty_final_struct()); auto in2 = ser::as_input_stream(buf2.linearize()); ser::deserialize(in2, std::type_identity()); } 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()); 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()); 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{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()); 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()); BOOST_REQUIRE(obj == deser_obj); }