Skip to content

Commit

Permalink
tests: Check particle serialization
Browse files Browse the repository at this point in the history
Provide a mechanism to walk through a serialization tree recursively
to check for a specific trait. Check that all fixed-length members
of the Particle struct are statically serializable. The std::vector
objects should be the only non-statically serializable members.
  • Loading branch information
jngrad committed Jan 3, 2022
1 parent 0bcd0a9 commit 81cb56a
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 0 deletions.
2 changes: 2 additions & 0 deletions src/core/unit_tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ unit_test(NAME p3m_test SRC p3m_test.cpp DEPENDS EspressoUtils)
unit_test(NAME link_cell_test SRC link_cell_test.cpp DEPENDS EspressoUtils)
unit_test(NAME Particle_test SRC Particle_test.cpp DEPENDS EspressoUtils
Boost::serialization)
unit_test(NAME Particle_serialization_test SRC Particle_serialization_test.cpp
DEPENDS EspressoUtils Boost::serialization)
unit_test(NAME field_coupling_couplings SRC field_coupling_couplings_test.cpp
DEPENDS EspressoUtils)
unit_test(NAME field_coupling_fields SRC field_coupling_fields_test.cpp DEPENDS
Expand Down
248 changes: 248 additions & 0 deletions src/core/unit_tests/Particle_serialization_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
/*
* Copyright (C) 2021 The ESPResSo project
*
* This file is part of ESPResSo.
*
* ESPResSo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ESPResSo 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/

#define BOOST_TEST_MODULE Particle serialization test
#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include "Particle.hpp"
#include "config.hpp"

#include <utils/demangle.hpp>
#include <utils/serialization/memcpy_archive.hpp>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/hana.hpp>
#include <boost/mpl/list.hpp>

#include <cstddef>
#include <regex>
#include <type_traits>
#include <utility>
#include <vector>

namespace traits {

namespace detail {

/**
* @brief Helper class to detect the presence of a member in a type.
* If a member exists in both @p T and @p Ref, then it's impossible
* to access the member in the derived class due to the ambiguity.
* If the member only exists in @p Ref, then it can be accessed in
* the derived class. This can be leveraged by SFINAE, even if the
* member is private. To handle all C++ types, use @c std::is_class
* in the trait specialization. Only works in a constexpr context!
* @tparam T Class to check
* @tparam Ref Reference class that contains the member
*/
template <typename T, typename Ref>
struct DetectMember : public T, public Ref {};

struct SerializableClass {
template <class Archive> void serialize(Archive &, long int);
};

auto hasnt_serialize_method = boost::hana::is_valid(
[](auto &&x) -> decltype((void)x.serialize(std::declval<int &>(),
std::declval<long int>())) {});

} // namespace detail

/**
* Does the type contain a <tt>serialize(Archive &, long int)</tt> method.
*/
template <class T, typename Enable = void>
struct has_serialize_method : std::integral_constant<bool, false> {};

template <class T>
struct has_serialize_method<T,
typename std::enable_if_t<std::is_class<T>::value>>
: std::integral_constant<
bool, !static_cast<bool>(detail::hasnt_serialize_method(
detail::DetectMember<T, detail::SerializableClass>{}))> {};

} // namespace traits

/**
* Recursively walk through the serialization tree of an object and keep
* track of which serialized types are not matching a specific trait.
* @tparam Trait Trait to check for
*/
template <template <class> class Trait> class TraitChecker {
public:
typedef boost::mpl::bool_<true> is_saving;
typedef boost::mpl::bool_<false> is_loading;
typedef std::vector<std::string> buffer_type;

TraitChecker(buffer_type &buffer) : m_buffer(buffer) {}

template <class T> auto &operator<<(T &t) {
if (not Trait<T>::value)
m_buffer.emplace_back(Utils::demangle<T>());
else
recurse(t);
return *this;
}

template <class T> auto &operator&(T &t) { return *this << t; }

/**
* Special method that skips the first level in the serialization tree.
* Allow checking for a trait in members of a type T, even if T itself
* doesn't have this trait.
*/
template <class T> auto &operator|(T &t) {
recurse(t);
return *this;
}

void save_binary(void *address, std::size_t count){};

private:
template <typename T, std::enable_if_t<std::is_class<T>::value, int> = 0>
void recurse(T &t) {
m_accessor.serialize(*this, t, 0);
}

// standard types are not classes
template <typename T, std::enable_if_t<!std::is_class<T>::value, int> = 0>
void recurse(T &t) {}

// const objects cannot be processed by boost::serialization
template <typename T> void recurse(T const &t) {}

// std::vector objects cannot be processed by boost::serialization
template <typename T> void recurse(std::vector<T> &t) {}

buffer_type &m_buffer;
boost::serialization::access m_accessor;
};

namespace Testing {

struct NotSerializable {};

class BitwiseSerializable {
int const a = 1;
long double b = 2.l;
int const c = 3;
long double d = 4.l;

friend boost::serialization::access;
template <class Archive> void serialize(Archive &ar, long int) {
ar &a &b;
ar << c << d;
}
};

class NotBitwiseSerializable {
friend boost::serialization::access;
template <class Archive> void serialize(Archive &ar, long int) {}
};

class MixedSerializable {
BitwiseSerializable a;
NotBitwiseSerializable b;

friend boost::serialization::access;
template <class Archive> void serialize(Archive &ar, long int) {
ar << a << b;
}
};

} // namespace Testing

BOOST_IS_BITWISE_SERIALIZABLE(Testing::BitwiseSerializable)

void constexpr assert_has_serialize_method() {
using namespace Testing;
static_assert(!traits::has_serialize_method<int>::value, "");
static_assert(!traits::has_serialize_method<NotSerializable>::value, "");
static_assert(traits::has_serialize_method<BitwiseSerializable>::value, "");
static_assert(traits::has_serialize_method<MixedSerializable>::value, "");
}

BOOST_AUTO_TEST_CASE(has_serialize_method_test) {
assert_has_serialize_method();
}

BOOST_AUTO_TEST_CASE(TraitChecker_test) {
using boost::serialization::is_bitwise_serializable;
using Checker = TraitChecker<is_bitwise_serializable>;
Checker::buffer_type buffer;
Checker oa{buffer};
Testing::BitwiseSerializable serializable;
oa &serializable;
BOOST_REQUIRE_EQUAL(buffer.size(), 0);
Testing::MixedSerializable mixed;
oa | mixed;
BOOST_REQUIRE_EQUAL(buffer.size(), 1);
BOOST_REQUIRE_EQUAL(buffer[0], "Testing::NotBitwiseSerializable");
}

namespace trait_wrappers {
struct is_trivially_copyable {
template <typename T> using apply = std::is_trivially_copyable<T>;
};
struct is_statically_serializable {
template <typename T> using apply = Utils::is_statically_serializable<T>;
};
} // namespace trait_wrappers

BOOST_AUTO_TEST_CASE_TEMPLATE(
particle_member_traits_test, TraitWrapper,
BOOST_IDENTITY_TYPE(
(boost::mpl::list<trait_wrappers::is_trivially_copyable,
trait_wrappers::is_statically_serializable>))) {

using Checker = TraitChecker<TraitWrapper::template apply>;

// struct Particle is not statically serializable
{
static_assert(!TraitWrapper::template apply<Particle>::value, "");
typename Checker::buffer_type buffer_ref = {"Particle"};
typename Checker::buffer_type buffer;
Checker oa{buffer};
Particle p;
oa &p;
BOOST_TEST(buffer == buffer_ref, boost::test_tools::per_element());
}

// most members of Particle are statically serializable
{
typename Checker::buffer_type buffer_ref = {
"BondList",
#ifdef EXCLUSIONS
"std::vector<int, std::allocator<int> >",
#endif
};
typename Checker::buffer_type buffer;
Checker oa{buffer};
Particle p;
oa | p;
std::transform(buffer.begin(), buffer.end(), buffer.begin(),
[](std::string const &symbol) {
return std::regex_replace(symbol, std::regex("std::__1::"),
"std::");
});
BOOST_TEST(buffer == buffer_ref, boost::test_tools::per_element());
}
}

0 comments on commit 81cb56a

Please sign in to comment.