mirror of
https://github.com/scylladb/scylladb.git
synced 2026-04-24 02:20:37 +00:00
848 lines
29 KiB
C++
848 lines
29 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#define BOOST_TEST_MODULE imr
|
|
#include <boost/test/unit_test.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <random>
|
|
|
|
#include <boost/range/irange.hpp>
|
|
#include <boost/range/algorithm/copy.hpp>
|
|
#include <boost/range/algorithm/generate.hpp>
|
|
|
|
#include <seastar/util/variant_utils.hh>
|
|
|
|
#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<typename FillAB, typename FillBC>
|
|
struct generate_flags_type;
|
|
|
|
template<size_t... IdxAB, size_t... IdxBC>
|
|
struct generate_flags_type<std::index_sequence<IdxAB...>, std::index_sequence<IdxBC...>> {
|
|
using type = imr::flags<A, std::integral_constant<size_t, IdxAB>...,
|
|
B, std::integral_constant<ssize_t, IdxBC>..., C>;
|
|
};
|
|
|
|
BOOST_AUTO_TEST_CASE(test_flags) {
|
|
using flags_type = generate_flags_type<std::make_index_sequence<7>, 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<A>(),
|
|
imr::set_flag<B>(),
|
|
imr::set_flag<C>()), expected_size);
|
|
|
|
uint8_t buffer[expected_size];
|
|
std::fill_n(buffer, expected_size, 0xbe);
|
|
BOOST_CHECK_EQUAL(flags_type::serialize(buffer, imr::set_flag<B>()), expected_size);
|
|
|
|
auto mview = flags_type::make_view(buffer);
|
|
BOOST_CHECK(!mview.get<A>());
|
|
BOOST_CHECK(mview.get<B>());
|
|
BOOST_CHECK(!mview.get<C>());
|
|
|
|
mview.set<A>();
|
|
mview.set<B>(false);
|
|
BOOST_CHECK(mview.get<A>());
|
|
BOOST_CHECK(!mview.get<B>());
|
|
BOOST_CHECK(!mview.get<C>());
|
|
|
|
flags_type::view view = mview;
|
|
mview.set<C>();
|
|
BOOST_CHECK(view.get<A>());
|
|
BOOST_CHECK(!view.get<B>());
|
|
BOOST_CHECK(view.get<C>());
|
|
|
|
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<A>());
|
|
BOOST_CHECK(!mview.get<B>());
|
|
BOOST_CHECK(!mview.get<C>());
|
|
}
|
|
|
|
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<decltype(test_pod_type::x)> dist_x;
|
|
std::uniform_int_distribution<decltype(test_pod_type::y)> dist_y;
|
|
return test_pod_type { dist_x(tests::random::gen), dist_y(tests::random::gen) };
|
|
};
|
|
using pod_type = imr::pod<test_pod_type>;
|
|
|
|
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<typename Tag>
|
|
size_t size_of() const noexcept;
|
|
};
|
|
|
|
template<>
|
|
size_t test_buffer_context::size_of<A>() const noexcept {
|
|
return _size;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_buffer) {
|
|
using buffer_type = imr::buffer<A>;
|
|
|
|
auto test = [] (auto serialize) {
|
|
auto data = tests::random::get_bytes();
|
|
auto size = data.size();
|
|
|
|
auto buffer = std::make_unique<uint8_t[]>(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<const uint8_t*>(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<typename Tag>
|
|
bool is_present() const noexcept;
|
|
|
|
template<typename Tag, typename... Args>
|
|
decltype(auto) context_for(Args&&...) const noexcept { return *this; }
|
|
};
|
|
template<>
|
|
bool test_optional_context::is_present<A>() const noexcept {
|
|
return true;
|
|
}
|
|
template<>
|
|
bool test_optional_context::is_present<B>() const noexcept {
|
|
return false;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_optional) {
|
|
using optional_type1 = imr::optional<A, imr::pod<uint32_t>>;
|
|
using optional_type2 = imr::optional<B, imr::pod<uint32_t>>;
|
|
|
|
for (auto i = 0; i < random_test_iteration_count; i++) {
|
|
auto value = tests::random::get_int<uint32_t>();
|
|
auto expected_size = imr::pod<uint32_t>::size_when_serialized(value);
|
|
|
|
auto buffer = std::make_unique<uint8_t[]>(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<A,
|
|
imr::member<B, imr::pod<uint64_t>>,
|
|
imr::member<C, imr::buffer<C>>,
|
|
imr::member<D, imr::pod<int64_t>>>;
|
|
|
|
struct test_variant_context {
|
|
unsigned _alternative_idx;
|
|
public:
|
|
template<typename Tag>
|
|
size_t size_of() const noexcept;
|
|
|
|
template<typename Tag>
|
|
auto active_alternative_of() const noexcept;
|
|
|
|
template<typename Tag, typename... Args>
|
|
decltype(auto) context_for(Args&&...) const noexcept { return *this; }
|
|
};
|
|
|
|
template<>
|
|
size_t test_variant_context::size_of<C>() const noexcept {
|
|
return data_size;
|
|
}
|
|
|
|
template<>
|
|
auto test_variant_context::active_alternative_of<A>() const noexcept {
|
|
switch (_alternative_idx) {
|
|
case 0:
|
|
return variant_type::index_for<B>();
|
|
case 1:
|
|
return variant_type::index_for<C>();
|
|
case 2:
|
|
return variant_type::index_for<D>();
|
|
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<unsigned>(2);
|
|
|
|
uint64_t uinteger = tests::random::get_int<uint64_t>();
|
|
int64_t integer = tests::random::get_int<int64_t>();
|
|
bytes data = tests::random::get_bytes(data_size);
|
|
|
|
const size_t expected_size = alternative_idx == 0
|
|
? imr::pod<uint64_t>::size_when_serialized(uinteger)
|
|
: (alternative_idx == 1 ? data_size : sizeof(int64_t));
|
|
|
|
auto buffer = std::make_unique<uint8_t[]>(expected_size);
|
|
|
|
if (!alternative_idx) {
|
|
BOOST_CHECK_EQUAL(variant_type::size_when_serialized<B>(uinteger), expected_size);
|
|
BOOST_CHECK_EQUAL(variant_type::serialize<B>(buffer.get(), uinteger), expected_size);
|
|
} else if (alternative_idx == 1) {
|
|
BOOST_CHECK_EQUAL(variant_type::size_when_serialized<C>(data), expected_size);
|
|
BOOST_CHECK_EQUAL(variant_type::serialize<C>(buffer.get(), data), expected_size);
|
|
} else {
|
|
BOOST_CHECK_EQUAL(variant_type::size_when_serialized<D>(integer), expected_size);
|
|
BOOST_CHECK_EQUAL(variant_type::serialize<D>(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<uint64_t>::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<C>::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<int64_t>::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<A, imr::pod<uint8_t>>,
|
|
imr::member<B, imr::pod<int64_t>>,
|
|
imr::member<C, imr::pod<uint32_t>>>;
|
|
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<uint8_t>();
|
|
auto b = tests::random::get_int<uint64_t>();
|
|
auto c = tests::random::get_int<uint32_t>();
|
|
|
|
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<A>().load(), a);
|
|
BOOST_CHECK_EQUAL(mview.get<B>().load(), b);
|
|
BOOST_CHECK_EQUAL(mview.get<C>().load(), c);
|
|
|
|
auto view = S::make_view(const_cast<const uint8_t*>(buffer));
|
|
BOOST_CHECK_EQUAL(view.get<A>().load(), a);
|
|
BOOST_CHECK_EQUAL(view.get<B>().load(), b);
|
|
BOOST_CHECK_EQUAL(view.get<C>().load(), c);
|
|
|
|
a = tests::random::get_int<uint8_t>();
|
|
b = tests::random::get_int<uint64_t>();
|
|
c = tests::random::get_int<uint32_t>();
|
|
mview.get<A>().store(a);
|
|
mview.get<B>().store(b);
|
|
mview.get<C>().store(c);
|
|
|
|
BOOST_CHECK_EQUAL(view.get<A>().load(), a);
|
|
BOOST_CHECK_EQUAL(view.get<B>().load(), b);
|
|
BOOST_CHECK_EQUAL(view.get<C>().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<typename Tag>
|
|
bool is_present() const noexcept;
|
|
|
|
template<typename Tag>
|
|
size_t size_of() const noexcept;
|
|
|
|
template<typename Tag, typename... Args>
|
|
decltype(auto) context_for(Args&&...) const noexcept { return *this; }
|
|
};
|
|
|
|
template<>
|
|
bool test_structure_context::is_present<B>() const noexcept {
|
|
return _b_is_present;
|
|
}
|
|
|
|
template<>
|
|
size_t test_structure_context::size_of<C>() const noexcept {
|
|
return _c_size_of;
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_structure_with_context) {
|
|
using S = imr::structure<imr::member<A, imr::flags<B, C>>,
|
|
imr::optional_member<B, imr::pod<uint16_t>>,
|
|
imr::member<C, imr::buffer<C>>>;
|
|
|
|
for (auto i = 0; i < random_test_iteration_count; i++) {
|
|
auto b_value = tests::random::get_int<uint16_t>();
|
|
auto c_data = tests::random::get_bytes();
|
|
|
|
const auto expected_size = 1 + imr::pod<uint16_t>::size_when_serialized(b_value)
|
|
+ c_data.size();
|
|
|
|
auto writer = [&] (auto&& serializer) noexcept {
|
|
return serializer
|
|
.serialize(imr::set_flag<B>())
|
|
.serialize(b_value)
|
|
.serialize(c_data)
|
|
.done();
|
|
};
|
|
|
|
BOOST_CHECK_EQUAL(S::size_when_serialized(writer), expected_size);
|
|
|
|
auto buffer = std::make_unique<uint8_t[]>(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<A>().get<B>());
|
|
BOOST_CHECK(!mview.get<A>().get<C>());
|
|
BOOST_CHECK_EQUAL(mview.get<B>().get().load(), b_value);
|
|
BOOST_CHECK(boost::range::equal(mview.get<C>(ctx), c_data));
|
|
|
|
auto view = S::view(mview);
|
|
BOOST_CHECK(view.get<A>().get<B>());
|
|
BOOST_CHECK(!view.get<A>().get<C>());
|
|
BOOST_CHECK_EQUAL(view.get<B>().get().load(), b_value);
|
|
BOOST_CHECK(boost::range::equal(view.get<C>(ctx), c_data));
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_structure_get_element_without_view) {
|
|
using S = imr::structure<imr::member<A, imr::flags<B, C>>,
|
|
imr::member<B, imr::pod<uint64_t>>,
|
|
imr::optional_member<C, imr::pod<uint16_t>>>;
|
|
|
|
auto uinteger = tests::random::get_int<uint64_t>();
|
|
|
|
static constexpr auto expected_size = 1 + sizeof(uint64_t);
|
|
|
|
auto writer = [&] (auto&& serializer) noexcept {
|
|
return serializer
|
|
.serialize(imr::set_flag<B>())
|
|
.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<A>(buffer);
|
|
BOOST_CHECK(fview.get<B>());
|
|
BOOST_CHECK(!fview.get<C>());
|
|
|
|
auto uview = S::get_member<B>(buffer);
|
|
BOOST_CHECK_EQUAL(uview.load(), uinteger);
|
|
// FIXME test offset
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_nested_structure) {
|
|
using S1 = imr::structure<imr::optional_member<B, imr::pod<uint16_t>>,
|
|
imr::member<C, imr::buffer<C>>,
|
|
imr::member<A, imr::pod<uint8_t>>>;
|
|
|
|
using S = imr::structure<imr::member<A, imr::pod<uint16_t>>,
|
|
imr::member<B, S1>,
|
|
imr::member<C, imr::pod<uint32_t>>>;
|
|
|
|
for (auto i = 0; i < random_test_iteration_count; i++) {
|
|
auto b1_value = tests::random::get_int<uint16_t>();
|
|
auto c1_data = tests::random::get_bytes();
|
|
auto a1_value = tests::random::get_int<uint8_t>();
|
|
|
|
const auto expected_size1 = imr::pod<uint16_t>::size_when_serialized(b1_value)
|
|
+ c1_data.size() + sizeof(uint8_t);
|
|
|
|
auto a_value = tests::random::get_int<uint16_t>();
|
|
auto c_value = tests::random::get_int<uint32_t>();
|
|
|
|
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<uint8_t[]>(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<A>().load(), a_value);
|
|
BOOST_CHECK_EQUAL(view.get<B>(ctx).get<B>().get().load(), b1_value);
|
|
BOOST_CHECK(boost::range::equal(view.get<B>(ctx).get<C>(ctx), c1_data));
|
|
BOOST_CHECK_EQUAL(view.get<C>(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<pod<object_with_destructor>> {
|
|
template<typename... Args>
|
|
static void run(uint8_t* ptr, Args&&...) noexcept {
|
|
object_with_destructor::destruction_count++;
|
|
|
|
auto view = imr::pod<object_with_destructor>::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<object_with_destructor>;
|
|
using O2 = imr::pod<object_without_destructor>;
|
|
|
|
BOOST_CHECK(!imr::methods::is_trivially_destructible<O1>::value);
|
|
BOOST_CHECK(imr::methods::is_trivially_destructible<O2>::value);
|
|
|
|
static constexpr auto expected_size = sizeof(object_with_destructor);
|
|
uint8_t buffer[expected_size];
|
|
|
|
auto value = tests::random::get_int<uint64_t>();
|
|
BOOST_CHECK_EQUAL(O1::serialize(buffer, object_with_destructor { value }), expected_size);
|
|
imr::methods::destroy<O1>(buffer);
|
|
BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 1);
|
|
BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, value);
|
|
|
|
imr::methods::destroy<O2>(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<A, imr::pod<object_with_destructor>>,
|
|
imr::member<B, imr::pod<object_without_destructor>>,
|
|
imr::member<C, imr::pod<object_with_destructor>>>;
|
|
|
|
using S1 = imr::structure<imr::member<A, imr::pod<object_without_destructor>>,
|
|
imr::member<B, imr::pod<object_without_destructor>>,
|
|
imr::member<C, imr::pod<object_without_destructor>>>;
|
|
|
|
BOOST_CHECK(!imr::methods::is_trivially_destructible<S>::value);
|
|
BOOST_CHECK(imr::methods::is_trivially_destructible<S1>::value);
|
|
|
|
static constexpr auto expected_size = sizeof(object_with_destructor) * 3;
|
|
uint8_t buffer[expected_size];
|
|
|
|
auto a = tests::random::get_int<uint64_t>();
|
|
auto b = tests::random::get_int<uint64_t>();
|
|
auto c = tests::random::get_int<uint64_t>();
|
|
|
|
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<S>(buffer);
|
|
BOOST_CHECK_EQUAL(object_with_destructor::destruction_count, 2);
|
|
BOOST_CHECK_EQUAL(object_with_destructor::last_destroyed_one, c);
|
|
|
|
imr::methods::destroy<S1>(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<A, imr::pod<object_with_destructor>>;
|
|
using O2 = imr::optional<B, imr::pod<object_with_destructor>>;
|
|
using O3 = imr::optional<A, imr::pod<object_without_destructor>>;
|
|
|
|
BOOST_CHECK(!imr::methods::is_trivially_destructible<O1>::value);
|
|
BOOST_CHECK(!imr::methods::is_trivially_destructible<O2>::value);
|
|
BOOST_CHECK(imr::methods::is_trivially_destructible<O3>::value);
|
|
|
|
static constexpr auto expected_size = sizeof(object_with_destructor);
|
|
uint8_t buffer[expected_size];
|
|
|
|
auto value = tests::random::get_int<uint64_t>();
|
|
|
|
BOOST_CHECK_EQUAL(O1::serialize(buffer, object_with_destructor { value }), expected_size);
|
|
|
|
imr::methods::destroy<O2>(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<O1>(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<O3>(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<A,
|
|
imr::member<B, imr::pod<object_with_destructor>>,
|
|
imr::member<C, imr::pod<object_without_destructor>>>;
|
|
|
|
struct test_variant_context {
|
|
bool _alternative_b;
|
|
public:
|
|
template<typename Tag>
|
|
auto active_alternative_of() const noexcept;
|
|
|
|
template<typename Tag>
|
|
decltype(auto) context_for(...) const noexcept { return *this; }
|
|
};
|
|
|
|
template<>
|
|
auto test_variant_context::active_alternative_of<A>() const noexcept {
|
|
if (_alternative_b) {
|
|
return V::index_for<B>();
|
|
} else {
|
|
return V::index_for<C>();
|
|
}
|
|
}
|
|
|
|
BOOST_AUTO_TEST_CASE(test_variant_destructor) {
|
|
object_with_destructor::reset();
|
|
|
|
using V1 = imr::variant<A, imr::member<B, imr::pod<object_without_destructor>>>;
|
|
|
|
BOOST_CHECK(!imr::methods::is_trivially_destructible<V>::value);
|
|
BOOST_CHECK(imr::methods::is_trivially_destructible<V1>::value);
|
|
|
|
static constexpr auto expected_size = sizeof(object_with_destructor);
|
|
uint8_t buffer[expected_size];
|
|
|
|
auto value = tests::random::get_int<uint64_t>();
|
|
|
|
BOOST_CHECK_EQUAL(V::serialize<B>(buffer, object_with_destructor { value }), expected_size);
|
|
|
|
imr::methods::destroy<V>(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<V>(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<A, imr::pod<size_t>>,
|
|
imr::member<B, imr::buffer<B>>
|
|
>;
|
|
|
|
using structure = imr::structure<
|
|
imr::member<A, imr::pod<size_t>>,
|
|
imr::member<C, imr::tagged_type<C, imr::pod<void*>>>,
|
|
imr::member<D, imr::tagged_type<C, imr::pod<void*>>>,
|
|
imr::member<B, imr::buffer<A>>
|
|
>;
|
|
|
|
struct structue_context {
|
|
size_t _size;
|
|
|
|
structue_context(const uint8_t* ptr)
|
|
: _size(imr::pod<size_t>::make_view(ptr).load())
|
|
{
|
|
BOOST_CHECK_EQUAL(_size, 4);
|
|
}
|
|
|
|
template<typename Tag>
|
|
size_t size_of() const noexcept {
|
|
return _size;
|
|
}
|
|
|
|
template<typename Tag, typename... Args>
|
|
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<size_t>::make_view(ptr).load())
|
|
{
|
|
BOOST_CHECK_NE(_size, 0);
|
|
}
|
|
|
|
template<typename Tag>
|
|
size_t size_of() const noexcept {
|
|
return _size;
|
|
}
|
|
|
|
template<typename Tag, typename... Args>
|
|
decltype(auto) context_for(Args&&...) const noexcept { return *this; }
|
|
};
|
|
|
|
}
|
|
|
|
namespace imr::methods {
|
|
|
|
template<>
|
|
struct destructor<imr::tagged_type<C, imr::pod<void*>>> {
|
|
static void run(uint8_t* ptr, ...) {
|
|
using namespace object_exception_safety;
|
|
auto obj_ptr = imr::pod<uint8_t*>::make_view(ptr).load();
|
|
imr::methods::destroy<nested_structure>(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<imr::utils::object_context<structue_context>>;
|
|
using lsa_migrator_fn_for_structure = imr::alloc::lsa_migrate_fn<imr::utils::object<structure>::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<nested_structue_context>;
|
|
using lsa_migrator_fn_for_nested_structure = imr::alloc::lsa_migrate_fn<nested_structure, context_factory_for_nested_structure>;
|
|
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<nested_structure>(
|
|
&migrator_for_nested_structure,
|
|
[&] (auto nested_serializer) {
|
|
return nested_serializer
|
|
.serialize(128)
|
|
.serialize(128, [] (auto&&...) { })
|
|
.done();
|
|
}
|
|
))
|
|
.serialize(allocator.template allocate<nested_structure>(
|
|
&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<structure>::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);
|
|
}
|
|
|