/* * Copyright (C) 2018 ScyllaDB */ /* * This file is part of Scylla. * * Scylla is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Scylla is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ #define BOOST_TEST_MODULE imr #include #include #include #include #include #include #include #include "imr/fundamental.hh" #include "imr/compound.hh" #include "imr/methods.hh" #include "imr/utils.hh" #include "failure_injecting_allocation_strategy.hh" #include "utils/logalloc.hh" #include "random-utils.hh" static constexpr auto random_test_iteration_count = 20; class A; class B; class C; class D; BOOST_AUTO_TEST_SUITE(fundamental); template struct generate_flags_type; template struct generate_flags_type, std::index_sequence> { using type = imr::flags..., B, std::integral_constant..., C>; }; BOOST_AUTO_TEST_CASE(test_flags) { using flags_type = generate_flags_type, std::make_index_sequence<8>>::type; static constexpr size_t expected_size = 3; BOOST_CHECK_EQUAL(flags_type::size_when_serialized(), expected_size); BOOST_CHECK_EQUAL(flags_type::size_when_serialized(imr::set_flag(), imr::set_flag(), imr::set_flag()), expected_size); uint8_t buffer[expected_size]; std::fill_n(buffer, expected_size, 0xbe); BOOST_CHECK_EQUAL(flags_type::serialize(buffer, imr::set_flag()), expected_size); auto mview = flags_type::make_view(buffer); BOOST_CHECK(!mview.get()); BOOST_CHECK(mview.get()); BOOST_CHECK(!mview.get()); mview.set(); mview.set(false); BOOST_CHECK(mview.get()); BOOST_CHECK(!mview.get()); BOOST_CHECK(!mview.get()); flags_type::view view = mview; mview.set(); BOOST_CHECK(view.get()); BOOST_CHECK(!view.get()); BOOST_CHECK(view.get()); BOOST_CHECK_EQUAL(flags_type::serialized_object_size(buffer), expected_size); int some_context; BOOST_CHECK_EQUAL(flags_type::serialized_object_size(buffer, some_context), expected_size); std::fill_n(buffer, expected_size, 0xff); BOOST_CHECK_EQUAL(flags_type::serialize(buffer), expected_size); BOOST_CHECK(!mview.get()); BOOST_CHECK(!mview.get()); BOOST_CHECK(!mview.get()); } struct test_pod_type { int32_t x; uint64_t y; friend bool operator==(const test_pod_type& a, const test_pod_type& b) { return a.x == b.x && a.y == b.y; } friend std::ostream& operator<<(std::ostream& os, const test_pod_type& obj) { return os << "test_pod_type { x: " << obj.x << ", y: " << obj.y << " }"; } }; BOOST_AUTO_TEST_CASE(test_pod) { auto generate_object = [] { std::uniform_int_distribution dist_x; std::uniform_int_distribution dist_y; return test_pod_type { dist_x(tests::random::gen), dist_y(tests::random::gen) }; }; using pod_type = imr::pod; uint8_t buffer[pod_type::size]; for (auto i = 0; i < random_test_iteration_count; i++) { auto obj = generate_object(); BOOST_CHECK_EQUAL(pod_type::size_when_serialized(obj), pod_type::size); BOOST_CHECK_EQUAL(pod_type::serialize(buffer, obj), pod_type::size); BOOST_CHECK_EQUAL(pod_type::serialized_object_size(buffer), pod_type::size); int some_context; BOOST_CHECK_EQUAL(pod_type::serialized_object_size(buffer, some_context), pod_type::size); auto mview = pod_type::make_view(buffer); pod_type::view view = mview; BOOST_CHECK_EQUAL(mview.load(), obj); BOOST_CHECK_EQUAL(view.load(), obj); auto obj2 = generate_object(); mview.store(obj2); BOOST_CHECK_EQUAL(mview.load(), obj2); BOOST_CHECK_EQUAL(view.load(), obj2); } } class test_buffer_context { size_t _size; public: explicit test_buffer_context(size_t sz) : _size(sz) { } template size_t size_of() const noexcept; }; template<> size_t test_buffer_context::size_of() const noexcept { return _size; } BOOST_AUTO_TEST_CASE(test_buffer) { using buffer_type = imr::buffer; auto test = [] (auto serialize) { auto data = tests::random::get_bytes(); auto size = data.size(); auto buffer = std::make_unique(size); serialize(buffer.get(), size, data); const auto ctx = test_buffer_context(size); BOOST_CHECK_EQUAL(buffer_type::serialized_object_size(buffer.get(), ctx), size); BOOST_CHECK(boost::range::equal(buffer_type::make_view(buffer.get(), ctx), data)); BOOST_CHECK(boost::range::equal(buffer_type::make_view(const_cast(buffer.get()), ctx), data)); BOOST_CHECK_EQUAL(buffer_type::make_view(buffer.get(), ctx).size(), size); }; for (auto i = 0; i < random_test_iteration_count; i++) { test([] (uint8_t* out, size_t size, const bytes& data) { BOOST_CHECK_EQUAL(buffer_type::size_when_serialized(data), size); BOOST_CHECK_EQUAL(buffer_type::serialize(out, data), size); }); test([] (uint8_t* out, size_t size, const bytes& data) { auto serializer = [&data] (uint8_t* out) noexcept { boost::range::copy(data, out); }; BOOST_CHECK_EQUAL(buffer_type::size_when_serialized(size, serializer), size); BOOST_CHECK_EQUAL(buffer_type::serialize(out, size, serializer), size); }); } } BOOST_AUTO_TEST_SUITE_END(); BOOST_AUTO_TEST_SUITE(compound); struct test_optional_context { template bool is_present() const noexcept; template decltype(auto) context_for(Args&&...) const noexcept { return *this; } }; template<> bool test_optional_context::is_present() const noexcept { return true; } template<> bool test_optional_context::is_present() const noexcept { return false; } BOOST_AUTO_TEST_CASE(test_optional) { using optional_type1 = imr::optional>; using optional_type2 = imr::optional>; for (auto i = 0; i < random_test_iteration_count; i++) { auto value = tests::random::get_int(); auto expected_size = imr::pod::size_when_serialized(value); auto buffer = std::make_unique(expected_size); BOOST_CHECK_EQUAL(optional_type1::size_when_serialized(value), expected_size); BOOST_CHECK_EQUAL(optional_type1::serialize(buffer.get(), value), expected_size); BOOST_CHECK_EQUAL(optional_type1::serialized_object_size(buffer.get(), test_optional_context()), expected_size); BOOST_CHECK_EQUAL(optional_type2::serialized_object_size(buffer.get(), test_optional_context()), 0); auto view = optional_type1::make_view(buffer.get()); BOOST_CHECK_EQUAL(view.get().load(), value); } } static constexpr auto data_size = 128; using variant_type = imr::variant>, imr::member>, imr::member>>; struct test_variant_context { unsigned _alternative_idx; public: template size_t size_of() const noexcept; template auto active_alternative_of() const noexcept; template decltype(auto) context_for(Args&&...) const noexcept { return *this; } }; template<> size_t test_variant_context::size_of() const noexcept { return data_size; } template<> auto test_variant_context::active_alternative_of() const noexcept { switch (_alternative_idx) { case 0: return variant_type::index_for(); case 1: return variant_type::index_for(); case 2: return variant_type::index_for(); default: BOOST_FAIL("should not reach"); abort(); } } BOOST_AUTO_TEST_CASE(test_variant) { for (auto i = 0; i < random_test_iteration_count; i++) { unsigned alternative_idx = tests::random::get_int(2); uint64_t uinteger = tests::random::get_int(); int64_t integer = tests::random::get_int(); bytes data = tests::random::get_bytes(data_size); const size_t expected_size = alternative_idx == 0 ? imr::pod::size_when_serialized(uinteger) : (alternative_idx == 1 ? data_size : sizeof(int64_t)); auto buffer = std::make_unique(expected_size); if (!alternative_idx) { BOOST_CHECK_EQUAL(variant_type::size_when_serialized(uinteger), expected_size); BOOST_CHECK_EQUAL(variant_type::serialize(buffer.get(), uinteger), expected_size); } else if (alternative_idx == 1) { BOOST_CHECK_EQUAL(variant_type::size_when_serialized(data), expected_size); BOOST_CHECK_EQUAL(variant_type::serialize(buffer.get(), data), expected_size); } else { BOOST_CHECK_EQUAL(variant_type::size_when_serialized(integer), expected_size); BOOST_CHECK_EQUAL(variant_type::serialize(buffer.get(), integer), expected_size); } auto ctx = test_variant_context { alternative_idx }; BOOST_CHECK_EQUAL(variant_type::serialized_object_size(buffer.get(), ctx), expected_size); auto view = variant_type::make_view(buffer.get(), ctx); bool visitor_was_called = false; view.visit(make_visitor( [&] (imr::pod::view val) { visitor_was_called = true; if (alternative_idx == 0) { BOOST_CHECK_EQUAL(val.load(), uinteger); } else { BOOST_FAIL("wrong variant alternative (B)"); } }, [&] (imr::buffer::view buf) { visitor_was_called = true; if (alternative_idx == 1) { BOOST_CHECK(boost::equal(data, buf)); } else { BOOST_FAIL("wrong variant alternative (C)"); } }, [&] (imr::pod::view val) { visitor_was_called = true; if (alternative_idx == 2) { BOOST_CHECK_EQUAL(val.load(), integer); } else { BOOST_FAIL("wrong variant alternative (D)"); } } ), ctx); BOOST_CHECK(visitor_was_called); } } BOOST_AUTO_TEST_CASE(test_structure_with_fixed) { using S = imr::structure>, imr::member>, imr::member>>; static constexpr auto expected_size = sizeof(uint8_t) + sizeof(uint64_t) + sizeof(uint32_t); for (auto i = 0; i < random_test_iteration_count; i++) { auto a = tests::random::get_int(); auto b = tests::random::get_int(); auto c = tests::random::get_int(); auto writer = [&] (auto&& serializer) noexcept { return serializer .serialize(a) .serialize(b) .serialize(c) .done(); }; uint8_t buffer[expected_size]; BOOST_CHECK_EQUAL(S::size_when_serialized(writer), expected_size); BOOST_CHECK_EQUAL(S::serialize(buffer, writer), expected_size); BOOST_CHECK_EQUAL(S::serialized_object_size(buffer), expected_size); auto mview = S::make_view(buffer); BOOST_CHECK_EQUAL(mview.get().load(), a); BOOST_CHECK_EQUAL(mview.get().load(), b); BOOST_CHECK_EQUAL(mview.get().load(), c); auto view = S::make_view(const_cast(buffer)); BOOST_CHECK_EQUAL(view.get().load(), a); BOOST_CHECK_EQUAL(view.get().load(), b); BOOST_CHECK_EQUAL(view.get().load(), c); a = tests::random::get_int(); b = tests::random::get_int(); c = tests::random::get_int(); mview.get().store(a); mview.get().store(b); mview.get().store(c); BOOST_CHECK_EQUAL(view.get().load(), a); BOOST_CHECK_EQUAL(view.get().load(), b); BOOST_CHECK_EQUAL(view.get().load(), c); } } class test_structure_context { bool _b_is_present; size_t _c_size_of; public: test_structure_context(bool b_is_present, size_t c_size_of) noexcept : _b_is_present(b_is_present), _c_size_of(c_size_of) { } template bool is_present() const noexcept; template size_t size_of() const noexcept; template decltype(auto) context_for(Args&&...) const noexcept { return *this; } }; template<> bool test_structure_context::is_present() const noexcept { return _b_is_present; } template<> size_t test_structure_context::size_of() const noexcept { return _c_size_of; } BOOST_AUTO_TEST_CASE(test_structure_with_context) { using S = imr::structure>, imr::optional_member>, imr::member>>; for (auto i = 0; i < random_test_iteration_count; i++) { auto b_value = tests::random::get_int(); auto c_data = tests::random::get_bytes(); const auto expected_size = 1 + imr::pod::size_when_serialized(b_value) + c_data.size(); auto writer = [&] (auto&& serializer) noexcept { return serializer .serialize(imr::set_flag()) .serialize(b_value) .serialize(c_data) .done(); }; BOOST_CHECK_EQUAL(S::size_when_serialized(writer), expected_size); auto buffer = std::make_unique(expected_size); BOOST_CHECK_EQUAL(S::serialize(buffer.get(), writer), expected_size); auto ctx = test_structure_context(true, c_data.size()); BOOST_CHECK_EQUAL(S::serialized_object_size(buffer.get(), ctx), expected_size); auto mview = S::make_view(buffer.get(), ctx); BOOST_CHECK(mview.get().get()); BOOST_CHECK(!mview.get().get()); BOOST_CHECK_EQUAL(mview.get().get().load(), b_value); BOOST_CHECK(boost::range::equal(mview.get(ctx), c_data)); auto view = S::view(mview); BOOST_CHECK(view.get().get()); BOOST_CHECK(!view.get().get()); BOOST_CHECK_EQUAL(view.get().get().load(), b_value); BOOST_CHECK(boost::range::equal(view.get(ctx), c_data)); } } BOOST_AUTO_TEST_CASE(test_structure_get_element_without_view) { using S = imr::structure>, imr::member>, imr::optional_member>>; auto uinteger = tests::random::get_int(); static constexpr auto expected_size = 1 + sizeof(uint64_t); auto writer = [&] (auto&& serializer) noexcept { return serializer .serialize(imr::set_flag()) .serialize(uinteger) .skip() .done(); }; BOOST_CHECK_EQUAL(S::size_when_serialized(writer), expected_size); uint8_t buffer[expected_size]; BOOST_CHECK_EQUAL(S::serialize(buffer, writer), expected_size); auto fview = S::get_member(buffer); BOOST_CHECK(fview.get()); BOOST_CHECK(!fview.get()); auto uview = S::get_member(buffer); BOOST_CHECK_EQUAL(uview.load(), uinteger); // FIXME test offset } BOOST_AUTO_TEST_CASE(test_nested_structure) { using S1 = imr::structure>, imr::member>, imr::member>>; using S = imr::structure>, imr::member, imr::member>>; for (auto i = 0; i < random_test_iteration_count; i++) { auto b1_value = tests::random::get_int(); auto c1_data = tests::random::get_bytes(); auto a1_value = tests::random::get_int(); const auto expected_size1 = imr::pod::size_when_serialized(b1_value) + c1_data.size() + sizeof(uint8_t); auto a_value = tests::random::get_int(); auto c_value = tests::random::get_int(); const auto expected_size = sizeof(uint16_t) + expected_size1 + sizeof(uint32_t); auto writer1 = [&] (auto&& serializer) noexcept { return serializer .serialize(b1_value) .serialize(c1_data) .serialize(a1_value) .done(); }; auto writer = [&] (auto&& serializer) noexcept { return serializer .serialize(a_value) .serialize(writer1) .serialize(c_value) .done(); }; BOOST_CHECK_EQUAL(S::size_when_serialized(writer), expected_size); auto buffer = std::make_unique(expected_size); BOOST_CHECK_EQUAL(S::serialize(buffer.get(), writer), expected_size); auto ctx = test_structure_context(true, c1_data.size()); BOOST_CHECK_EQUAL(S::serialized_object_size(buffer.get(), ctx), expected_size); auto view = S::make_view(buffer.get(), ctx); BOOST_CHECK_EQUAL(view.get().load(), a_value); BOOST_CHECK_EQUAL(view.get(ctx).get().get().load(), b1_value); BOOST_CHECK(boost::range::equal(view.get(ctx).get(ctx), c1_data)); BOOST_CHECK_EQUAL(view.get(ctx).load(), c_value); } } BOOST_AUTO_TEST_SUITE_END(); struct object_with_destructor { static size_t destruction_count; static uint64_t last_destroyed_one; static void reset() { destruction_count = 0; last_destroyed_one = 0; } uint64_t value; }; size_t object_with_destructor::destruction_count = 0; uint64_t object_with_destructor::last_destroyed_one = 0; struct object_without_destructor { uint64_t value; }; namespace imr { namespace methods { template<> struct destructor> { template static void run(uint8_t* ptr, Args&&...) noexcept { object_with_destructor::destruction_count++; auto view = imr::pod::make_view(ptr); object_with_destructor::last_destroyed_one = view.load().value; } }; } } BOOST_AUTO_TEST_SUITE(methods); BOOST_AUTO_TEST_CASE(test_simple_destructor) { object_with_destructor::reset(); using O1 = imr::pod; using O2 = imr::pod; BOOST_CHECK(!imr::methods::is_trivially_destructible::value); BOOST_CHECK(imr::methods::is_trivially_destructible::value); static constexpr auto expected_size = sizeof(object_with_destructor); uint8_t buffer[expected_size]; auto value = tests::random::get_int(); BOOST_CHECK_EQUAL(O1::serialize(buffer, object_with_destructor { value }), expected_size); imr::methods::destroy(buffer); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value); imr::methods::destroy(buffer); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value); } BOOST_AUTO_TEST_CASE(test_structure_destructor) { object_with_destructor::reset(); using S = imr::structure>, imr::member>, imr::member>>; using S1 = imr::structure>, imr::member>, imr::member>>; BOOST_CHECK(!imr::methods::is_trivially_destructible::value); BOOST_CHECK(imr::methods::is_trivially_destructible::value); static constexpr auto expected_size = sizeof(object_with_destructor) * 3; uint8_t buffer[expected_size]; auto a = tests::random::get_int(); auto b = tests::random::get_int(); auto c = tests::random::get_int(); BOOST_CHECK_EQUAL(S::serialize(buffer, [&] (auto serializer) noexcept { return serializer .serialize(object_with_destructor { a }) .serialize(object_without_destructor { b }) .serialize(object_with_destructor { c }) .done(); }), expected_size); imr::methods::destroy(buffer); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 2); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, c); imr::methods::destroy(buffer); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 2); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, c); } BOOST_AUTO_TEST_CASE(test_optional_destructor) { object_with_destructor::reset(); using O1 = imr::optional>; using O2 = imr::optional>; using O3 = imr::optional>; BOOST_CHECK(!imr::methods::is_trivially_destructible::value); BOOST_CHECK(!imr::methods::is_trivially_destructible::value); BOOST_CHECK(imr::methods::is_trivially_destructible::value); static constexpr auto expected_size = sizeof(object_with_destructor); uint8_t buffer[expected_size]; auto value = tests::random::get_int(); BOOST_CHECK_EQUAL(O1::serialize(buffer, object_with_destructor { value }), expected_size); imr::methods::destroy(buffer, compound::test_optional_context()); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 0); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, 0); imr::methods::destroy(buffer, compound::test_optional_context()); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value); imr::methods::destroy(buffer, compound::test_optional_context()); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value); } using V = imr::variant>, imr::member>>; struct test_variant_context { bool _alternative_b; public: template auto active_alternative_of() const noexcept; template decltype(auto) context_for(...) const noexcept { return *this; } }; template<> auto test_variant_context::active_alternative_of() const noexcept { if (_alternative_b) { return V::index_for(); } else { return V::index_for(); } } BOOST_AUTO_TEST_CASE(test_variant_destructor) { object_with_destructor::reset(); using V1 = imr::variant>>; BOOST_CHECK(!imr::methods::is_trivially_destructible::value); BOOST_CHECK(imr::methods::is_trivially_destructible::value); static constexpr auto expected_size = sizeof(object_with_destructor); uint8_t buffer[expected_size]; auto value = tests::random::get_int(); BOOST_CHECK_EQUAL(V::serialize(buffer, object_with_destructor { value }), expected_size); imr::methods::destroy(buffer, test_variant_context { false }); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 0); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, 0); imr::methods::destroy(buffer, test_variant_context { true }); BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1); BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value); } BOOST_AUTO_TEST_SUITE_END(); namespace object_exception_safety { using nested_structure = imr::structure< imr::member>, imr::member> >; using structure = imr::structure< imr::member>, imr::member>>, imr::member>>, imr::member> >; struct structue_context { size_t _size; structue_context(const uint8_t* ptr) : _size(imr::pod::make_view(ptr).load()) { BOOST_CHECK_EQUAL(_size, 4); } template size_t size_of() const noexcept { return _size; } template decltype(auto) context_for(Args&&...) const noexcept { return *this; } }; struct nested_structue_context { size_t _size; nested_structue_context(const uint8_t* ptr) : _size(imr::pod::make_view(ptr).load()) { BOOST_CHECK_NE(_size, 0); } template size_t size_of() const noexcept { return _size; } template decltype(auto) context_for(Args&&...) const noexcept { return *this; } }; } namespace imr::methods { template<> struct destructor>> { static void run(uint8_t* ptr, ...) { using namespace object_exception_safety; auto obj_ptr = imr::pod::make_view(ptr).load(); imr::methods::destroy(obj_ptr, nested_structue_context(obj_ptr)); current_allocator().free(obj_ptr); } }; } BOOST_AUTO_TEST_CASE(test_object_exception_safety) { using namespace object_exception_safety; using context_factory_for_structure = imr::alloc::context_factory>; using lsa_migrator_fn_for_structure = imr::alloc::lsa_migrate_fn::structure, context_factory_for_structure>; auto migrator_for_structure = lsa_migrator_fn_for_structure(context_factory_for_structure()); using context_factory_for_nested_structure = imr::alloc::context_factory; using lsa_migrator_fn_for_nested_structure = imr::alloc::lsa_migrate_fn; auto migrator_for_nested_structure = lsa_migrator_fn_for_nested_structure(context_factory_for_nested_structure()); auto writer_fn = [&] (auto serializer, auto& allocator) { return serializer .serialize(4) .serialize(allocator.template allocate( &migrator_for_nested_structure, [&] (auto nested_serializer) { return nested_serializer .serialize(128) .serialize(128, [] (auto&&...) { }) .done(); } )) .serialize(allocator.template allocate( &migrator_for_nested_structure, [&] (auto nested_serializer) { return nested_serializer .serialize(1024) .serialize(1024, [] (auto&&...) { }) .done(); } )) .serialize(bytes(4, 'a')) .done(); }; logalloc::region reg; size_t fail_offset = 0; auto allocator = failure_injecting_allocation_strategy(reg.allocator()); with_allocator(allocator, [&] { while (true) { allocator.fail_after(fail_offset++); try { imr::utils::object::make(writer_fn, &migrator_for_structure); } catch (const std::bad_alloc&) { BOOST_CHECK_EQUAL(reg.occupancy().used_space(), 0); continue; } BOOST_CHECK_EQUAL(reg.occupancy().used_space(), 0); break; } }); BOOST_CHECK_EQUAL(fail_offset, 4); }