tested boost::concurrent_flat_set

This commit is contained in:
joaquintides
2023-09-10 18:35:51 +02:00
parent 49f0929466
commit 8d2a5c25ea
21 changed files with 2301 additions and 1173 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
test::seed_t initialize_seed{674140082};
@ -14,49 +16,62 @@ using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using map_value_type = typename map_type::value_type;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
namespace {
template <class G> void clear_tests(G gen, test::random_generator rg)
template <class X, class GF>
void clear_tests(X*, GF gen_factory, test::random_generator rg)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
raii::reset_counts();
map_type x(values.begin(), values.end(), values.size(), hasher(1),
X x(values.begin(), values.end(), values.size(), hasher(1),
key_equal(2), allocator_type(3));
auto const old_size = x.size();
auto const old_d = +raii::destructor;
thread_runner(values, [&x](boost::span<map_value_type> s) {
thread_runner(values, [&x](boost::span<value_type> s) {
(void)s;
x.clear();
});
BOOST_TEST(x.empty());
BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size);
BOOST_TEST_EQ(raii::destructor, old_d + value_type_cardinality * old_size);
check_raii_counts();
}
template <class G> void insert_and_clear(G gen, test::random_generator rg)
template <class X, class GF>
void insert_and_clear(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
std::thread t1, t2;
{
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
X x(0, hasher(1), key_equal(2), allocator_type(3));
std::mutex m;
std::condition_variable cv;
@ -103,7 +118,7 @@ namespace {
BOOST_TEST_GE(num_clears, 1u);
if (!x.empty()) {
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
}
@ -115,11 +130,13 @@ namespace {
// clang-format off
UNORDERED_TEST(
clear_tests,
((value_type_generator))
((test_map)(test_set))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(insert_and_clear,
((value_type_generator))
((test_map)(test_set))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -0,0 +1,137 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_UNORDERED_TEST_CFOA_COMMON_HELPERS_HPP
#define BOOST_UNORDERED_TEST_CFOA_COMMON_HELPERS_HPP
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
#include <boost/unordered/concurrent_flat_set_fwd.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <cstddef>
#include <type_traits>
#include <utility>
template <typename K>
struct value_cardinality
{
static constexpr std::size_t value=1;
};
template <typename K, typename V>
struct value_cardinality<std::pair<K, V> >
{
static constexpr std::size_t value=2;
};
template <class Container>
struct reference_container_impl;
template <class Container>
using reference_container = typename reference_container_impl<Container>::type;
template <typename K, typename V, typename H, typename P, typename A>
struct reference_container_impl<boost::concurrent_flat_map<K, V, H, P, A> >
{
using type = boost::unordered_flat_map<K, V>;
};
template <typename K, typename H, typename P, typename A>
struct reference_container_impl<boost::concurrent_flat_set<K, H, P, A> >
{
using type = boost::unordered_flat_set<K>;
};
template <class Container>
struct flat_container_impl;
template <class Container>
using flat_container = typename flat_container_impl<Container>::type;
template <typename K, typename V, typename H, typename P, typename A>
struct flat_container_impl<boost::concurrent_flat_map<K, V, H, P, A> >
{
using type = boost::unordered_flat_map<K, V, H, P, A>;
};
template <typename K, typename H, typename P, typename A>
struct flat_container_impl<boost::concurrent_flat_set<K, H, P, A> >
{
using type = boost::unordered_flat_set<K, H, P, A>;
};
template <typename Container, template <typename> class Allocator>
struct replace_allocator_impl;
template <typename Container, template <typename> class Allocator>
using replace_allocator =
typename replace_allocator_impl<Container, Allocator>::type;
template <
typename K, typename V, typename H, typename P, typename A,
template <typename> class Allocator
>
struct replace_allocator_impl<
boost::concurrent_flat_map<K, V, H, P, A>, Allocator>
{
using value_type =
typename boost::concurrent_flat_map<K, V, H, P, A>::value_type;
using type =
boost::concurrent_flat_map<K, V, H, P, Allocator<value_type> >;
};
template <
typename K, typename H, typename P, typename A,
template <typename> class Allocator
>
struct replace_allocator_impl<
boost::concurrent_flat_set<K, H, P, A>, Allocator>
{
using value_type =
typename boost::concurrent_flat_set<K, H, P, A>::value_type;
using type =
boost::concurrent_flat_set<K, H, P, Allocator<value_type> >;
};
template <typename K>
K const& get_key(K const& x) { return x; }
template <typename K,typename V>
K const& get_key(const std::pair<K, V>& x) { return x.first; }
template <typename K>
K const& get_value(K const& x) { return x; }
template <typename K,typename V>
V const& get_value(const std::pair<K, V>& x) { return x.second; }
template <typename K,typename V>
V& get_value(std::pair<K, V>& x) { return x.second; }
template <class X, class Y>
void test_matches_reference(X const& x, Y const& reference_cont)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}));
}
template <class X, class Y>
void test_fuzzy_matches_reference(
X const& x, Y const& reference_cont, test::random_generator rg)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
if (rg == test::sequential) {
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}
}));
}
#endif // BOOST_UNORDERED_TEST_CFOA_COMMON_HELPERS_HPP

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,114 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
namespace {
test::seed_t initialize_seed(335740237);
template <typename Container, typename Value>
bool member_emplace(Container& x, Value const & v)
{
return x.emplace(v.x_);
}
template <typename Container, typename Value>
bool member_emplace(Container& x, Value& v)
{
return x.emplace(v.x_);
}
template <typename Container, typename Key, typename Value>
bool member_emplace(Container& x, std::pair<Key, Value> const & v)
{
return x.emplace(v.first.x_, v.second.x_);
}
template <typename Container, typename Key, typename Value>
bool member_emplace(Container& x, std::pair<Key, Value>& v)
{
return x.emplace(v.first.x_, v.second.x_);
}
template <typename Container, typename Value, typename F>
bool member_emplace_or_visit(Container& x, Value const & v, F f)
{
return x.emplace_or_visit(v.x_, f);
}
template <typename Container, typename Value, typename F>
bool member_emplace_or_visit(Container& x, Value& v, F f)
{
return x.emplace_or_visit(v.x_, f);
}
template <typename Container, typename Key, typename Value, typename F>
bool member_emplace_or_visit(
Container& x, std::pair<Key, Value> const & v, F f)
{
return x.emplace_or_visit(v.first.x_, v.second.x_, f);
}
template <typename Container, typename Key, typename Value, typename F>
bool member_emplace_or_visit(Container& x, std::pair<Key, Value>& v, F f)
{
return x.emplace_or_visit(v.first.x_, v.second.x_, f);
}
template <typename Container, typename Value, typename F>
bool member_emplace_or_cvisit(Container& x, Value const & v, F f)
{
return x.emplace_or_cvisit(v.x_, f);
}
template <typename Container, typename Value, typename F>
bool member_emplace_or_cvisit(Container& x, Value& v, F f)
{
return x.emplace_or_cvisit(v.x_, f);
}
template <typename Container, typename Key, typename Value, typename F>
bool member_emplace_or_cvisit(
Container& x, std::pair<Key, Value> const & v, F f)
{
return x.emplace_or_cvisit(v.first.x_, v.second.x_, f);
}
template <typename Container, typename Key, typename Value, typename F>
bool member_emplace_or_cvisit(Container& x, std::pair<Key, Value>& v, F f)
{
return x.emplace_or_cvisit(v.first.x_, v.second.x_, f);
}
struct lvalue_emplacer_type
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto const& r : s) {
bool b = x.emplace(r.first.x_, r.second.x_);
bool b = member_emplace(x, r);
if (b) {
++num_inserts;
}
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
@ -40,9 +120,12 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
x.reserve(values.size());
lvalue_emplacer_type::operator()(values, x);
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::move_constructor, value_type_cardinality * x.size());
}
} norehash_lvalue_emplacer;
@ -50,12 +133,15 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b = x.emplace_or_cvisit(
r.first.x_, r.second.x_,
bool b = member_emplace_or_cvisit(
x, r,
[&num_invokes](typename X::value_type const& v) {
(void)v;
++num_invokes;
@ -70,9 +156,10 @@ namespace {
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
@ -82,13 +169,23 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b = x.emplace_or_visit(
r.first.x_, r.second.x_,
[&num_invokes](typename X::value_type& v) {
bool b = member_emplace_or_visit(
x, r,
[&num_invokes](arg_type& v) {
(void)v;
++num_invokes;
});
@ -102,20 +199,21 @@ namespace {
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_GE(raii::move_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::move_assignment, 0u);
BOOST_TEST_EQ(raii::copy_assignment, 0u);
}
} lvalue_emplace_or_visit;
template <class X, class G, class F>
void emplace(X*, G gen, F emplacer, test::random_generator rg)
template <class X, class GF, class F>
void emplace(X*, GF gen_factory, F emplacer, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
{
@ -123,13 +221,13 @@ namespace {
emplacer(values, x);
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}
}));
}
@ -145,6 +243,7 @@ namespace {
}
boost::unordered::concurrent_flat_map<raii, raii>* map;
boost::unordered::concurrent_flat_set<raii>* set;
} // namespace
@ -156,8 +255,8 @@ using test::sequential;
UNORDERED_TEST(
emplace,
((map))
((value_type_generator)(init_type_generator))
((map)(set))
((value_type_generator_factory)(init_type_generator_factory))
((lvalue_emplacer)(norehash_lvalue_emplacer)
(lvalue_emplace_or_cvisit)(lvalue_emplace_or_visit))
((default_generator)(sequential)(limited_range)))

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
test::seed_t initialize_seed{1634048962};
@ -14,16 +16,21 @@ using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using map_value_type = typename map_type::value_type;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
namespace {
UNORDERED_AUTO_TEST (simple_equality) {
UNORDERED_AUTO_TEST (simple_map_equality) {
using allocator_type = map_type::allocator_type;
{
map_type x1(
{{1, 11}, {2, 22}}, 0, hasher(1), key_equal(2), allocator_type(3));
@ -50,17 +57,42 @@ namespace {
}
}
template <class G> void insert_and_compare(G gen, test::random_generator rg)
UNORDERED_AUTO_TEST (simple_set_equality) {
using allocator_type = set_type::allocator_type;
{
set_type x1(
{1, 2}, 0, hasher(1), key_equal(2), allocator_type(3));
set_type x2(
{1, 2}, 0, hasher(2), key_equal(2), allocator_type(3));
set_type x3({1}, 0, hasher(2), key_equal(2), allocator_type(3));
BOOST_TEST_EQ(x1.size(), x2.size());
BOOST_TEST(x1 == x2);
BOOST_TEST(!(x1 != x2));
BOOST_TEST(x1.size() != x3.size());
BOOST_TEST(!(x1 == x3));
BOOST_TEST(x1 != x3);
}
}
template <class X, class GF>
void insert_and_compare(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
boost::unordered_flat_map<raii, raii> reference_map(
vals1.begin(), vals1.end());
auto reference_cont = reference_container<X>(vals1.begin(), vals1.end());
{
raii::reset_counts();
map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
map_type x2(vals1.begin(), vals1.end(), vals1.size(), hasher(2),
X x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
X x2(vals1.begin(), vals1.end(), vals1.size(), hasher(2),
key_equal(2), allocator_type(3));
std::thread t1, t2;
@ -126,7 +158,7 @@ namespace {
BOOST_TEST(x1 == x2);
BOOST_TEST(!(x1 != x2));
test_matches_reference(x1, reference_map);
test_matches_reference(x1, reference_cont);
}
check_raii_counts();
}
@ -135,7 +167,8 @@ namespace {
// clang-format off
UNORDERED_TEST(
insert_and_compare,
((value_type_generator))
((test_map)(test_set))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
@ -15,6 +17,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
@ -26,11 +31,11 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
raii::move_constructor,
raii::destructor + 2 * x.size());
raii::destructor + value_type_cardinality * x.size());
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
for (auto const& k : values) {
auto count = x.erase(k.first);
for (auto const& v : values) {
auto count = x.erase(get_key(v));
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
@ -41,7 +46,7 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * old_size);
BOOST_TEST_EQ(raii::destructor, old_d + value_type_cardinality * old_size);
BOOST_TEST_EQ(x.size(), 0u);
BOOST_TEST(x.empty());
@ -53,6 +58,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
@ -64,7 +72,7 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor +
raii::move_constructor,
raii::destructor + 2 * x.size());
raii::destructor + value_type_cardinality * x.size());
thread_runner(values, [&num_erased, &x](boost::span<T> s) {
for (auto const& k : s) {
@ -92,6 +100,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_erased{0};
@ -105,8 +122,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -114,15 +131,15 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
auto count = x.erase_if(k.first,
[threshold](value_type& v) { return v.second.x_ > threshold; });
for (auto const& v : s) {
auto count = x.erase_if(get_key(v),
[threshold](arg_type& w) { return get_value(w).x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
@ -136,7 +153,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} lvalue_eraser_if;
@ -145,6 +163,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_erased{0};
@ -158,8 +185,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -167,15 +194,15 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
auto count = x.erase_if(k.first.x_,
[threshold](value_type& v) { return v.second.x_ > threshold; });
for (auto const& v : s) {
auto count = x.erase_if(get_key(v).x_,
[threshold](arg_type& w) { return get_value(w).x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
@ -189,7 +216,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} transp_lvalue_eraser_if;
@ -198,6 +226,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_erased{0};
@ -211,8 +248,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -220,7 +257,7 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
@ -229,7 +266,7 @@ namespace {
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 128; ++i) {
auto count = x.erase_if(
[threshold](value_type& v) { return v.second.x_ > threshold; });
[threshold](arg_type& v) { return get_value(v).x_ > threshold; });
num_erased += count;
}
});
@ -241,7 +278,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} erase_if;
@ -250,6 +288,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_erased{0};
@ -263,8 +310,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -272,7 +319,7 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
@ -281,7 +328,8 @@ namespace {
values, [&num_erased, &x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 128; ++i) {
auto count = boost::unordered::erase_if(x,
[threshold](value_type& v) { return v.second.x_ > threshold; });
[threshold](arg_type& v) {
return get_value(v).x_ > threshold; });
num_erased += count;
}
});
@ -293,7 +341,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} free_fn_erase_if;
@ -303,6 +352,15 @@ namespace {
{
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_invokes{0};
@ -316,8 +374,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -325,7 +383,7 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
@ -333,9 +391,9 @@ namespace {
thread_runner(values, [&num_invokes, &x, threshold](boost::span<T> s) {
(void)s;
x.erase_if(
std::execution::par, [&num_invokes, threshold](value_type& v) {
std::execution::par, [&num_invokes, threshold](arg_type& v) {
++num_invokes;
return v.second.x_ > threshold;
return get_value(v).x_ > threshold;
});
});
@ -346,7 +404,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * expected_erasures);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * expected_erasures);
#else
(void)values;
(void)x;
@ -354,12 +413,12 @@ namespace {
}
} erase_if_exec_policy;
template <class X, class G, class F>
void erase(X*, G gen, F eraser, test::random_generator rg)
template <class X, class GF, class F>
void erase(X*, GF gen_factory, F eraser, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
{
@ -367,20 +426,23 @@ namespace {
x.insert(values.begin(), values.end());
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
eraser(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
check_raii_counts();
}
boost::unordered::concurrent_flat_map<raii, raii>* map;
boost::unordered::concurrent_flat_set<raii>* set;
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
transp_key_equal>* transparent_map;
boost::unordered::concurrent_flat_map<raii, transp_hash,
transp_key_equal>* transparent_set;
} // namespace
@ -391,15 +453,15 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
erase,
((map))
((value_type_generator)(init_type_generator))
((map)(set))
((value_type_generator_factory)(init_type_generator_factory))
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if)(erase_if_exec_policy))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
erase,
((transparent_map))
((value_type_generator)(init_type_generator))
((value_type_generator_factory)(init_type_generator_factory))
((transp_lvalue_eraser)(transp_lvalue_eraser_if)(erase_if_exec_policy))
((default_generator)(sequential)(limited_range)))

View File

@ -1,24 +1,87 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
#include <boost/unordered/concurrent_flat_set.hpp>
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
std::initializer_list<map_type::value_type> map_init_list{
{raii{0}, raii{0}},
{raii{1}, raii{1}},
{raii{2}, raii{2}},
{raii{3}, raii{3}},
{raii{4}, raii{4}},
{raii{5}, raii{5}},
{raii{6}, raii{6}},
{raii{6}, raii{6}},
{raii{7}, raii{7}},
{raii{8}, raii{8}},
{raii{9}, raii{9}},
{raii{10}, raii{10}},
{raii{9}, raii{9}},
{raii{8}, raii{8}},
{raii{7}, raii{7}},
{raii{6}, raii{6}},
{raii{5}, raii{5}},
{raii{4}, raii{4}},
{raii{3}, raii{3}},
{raii{2}, raii{2}},
{raii{1}, raii{1}},
{raii{0}, raii{0}},
};
std::initializer_list<set_type::value_type> set_init_list{
raii{0},
raii{1},
raii{2},
raii{3},
raii{4},
raii{5},
raii{6},
raii{6},
raii{7},
raii{8},
raii{9},
raii{10},
raii{9},
raii{8},
raii{7},
raii{6},
raii{5},
raii{4},
raii{3},
raii{2},
raii{1},
raii{0},
};
auto test_map_and_init_list=std::make_pair(test_map,map_init_list);
auto test_set_and_init_list=std::make_pair(test_set,set_init_list);
namespace {
test::seed_t initialize_seed(1794114520);
template <class G> void copy_assign(G gen, test::random_generator rg)
template <class X, class GF>
void copy_assign(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
@ -31,12 +94,12 @@ namespace {
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
auto end = values.end();
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
auto reference_cont = reference_container<X>(begin, mid);
map_type x(
X x(
begin, mid, values.size(), hasher(1), key_equal(2), allocator_type(3));
map_type y(
X y(
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
BOOST_TEST(!y.empty());
@ -53,13 +116,17 @@ namespace {
disable_exceptions();
BOOST_TEST_GT(num_throws, 0u);
test_fuzzy_matches_reference(y, reference_map, rg);
test_fuzzy_matches_reference(y, reference_cont, rg);
}
check_raii_counts();
}
template <class G> void move_assign(G gen, test::random_generator rg)
template <class X, class GF>
void move_assign(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
@ -72,7 +139,7 @@ namespace {
values.begin() + static_cast<std::ptrdiff_t>(values.size() / 2);
auto end = values.end();
auto reference_map = boost::unordered_flat_map<raii, raii>(begin, mid);
auto reference_cont = reference_container<X>(begin, mid);
BOOST_TEST(
!boost::allocator_is_always_equal<allocator_type>::type::value);
@ -83,10 +150,10 @@ namespace {
for (std::size_t i = 0; i < 2 * alloc_throw_threshold; ++i) {
disable_exceptions();
map_type x(begin, mid, values.size(), hasher(1), key_equal(2),
X x(begin, mid, values.size(), hasher(1), key_equal(2),
allocator_type(3));
map_type y(
X y(
mid, end, values.size(), hasher(2), key_equal(1), allocator_type(4));
enable_exceptions();
@ -96,7 +163,7 @@ namespace {
++num_throws;
}
disable_exceptions();
test_fuzzy_matches_reference(y, reference_map, rg);
test_fuzzy_matches_reference(y, reference_cont, rg);
}
BOOST_TEST_GT(num_throws, 0u);
@ -104,43 +171,22 @@ namespace {
check_raii_counts();
}
UNORDERED_AUTO_TEST (intializer_list_assign) {
using value_type = typename map_type::value_type;
template <class X, class IL>
void intializer_list_assign(std::pair<X*, IL> p)
{
using allocator_type = typename X::allocator_type;
std::initializer_list<value_type> values{
value_type{raii{0}, raii{0}},
value_type{raii{1}, raii{1}},
value_type{raii{2}, raii{2}},
value_type{raii{3}, raii{3}},
value_type{raii{4}, raii{4}},
value_type{raii{5}, raii{5}},
value_type{raii{6}, raii{6}},
value_type{raii{6}, raii{6}},
value_type{raii{7}, raii{7}},
value_type{raii{8}, raii{8}},
value_type{raii{9}, raii{9}},
value_type{raii{10}, raii{10}},
value_type{raii{9}, raii{9}},
value_type{raii{8}, raii{8}},
value_type{raii{7}, raii{7}},
value_type{raii{6}, raii{6}},
value_type{raii{5}, raii{5}},
value_type{raii{4}, raii{4}},
value_type{raii{3}, raii{3}},
value_type{raii{2}, raii{2}},
value_type{raii{1}, raii{1}},
value_type{raii{0}, raii{0}},
};
auto init_list = p.second;
{
raii::reset_counts();
unsigned num_throws = 0;
for (std::size_t i = 0; i < throw_threshold; ++i) {
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
X x(0, hasher(1), key_equal(2), allocator_type(3));
enable_exceptions();
try {
x = values;
x = init_list;
} catch (...) {
++num_throws;
}
@ -160,13 +206,19 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
copy_assign,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
move_assign,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)))
UNORDERED_TEST(
intializer_list_assign,
((test_map_and_init_list)(test_set_and_init_list)))
// clang-format on
RUN_TESTS()

View File

@ -1,23 +1,84 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
#include <boost/unordered/concurrent_flat_set.hpp>
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
std::initializer_list<map_type::value_type> map_init_list{
{raii{0}, raii{0}},
{raii{1}, raii{1}},
{raii{2}, raii{2}},
{raii{3}, raii{3}},
{raii{4}, raii{4}},
{raii{5}, raii{5}},
{raii{6}, raii{6}},
{raii{6}, raii{6}},
{raii{7}, raii{7}},
{raii{8}, raii{8}},
{raii{9}, raii{9}},
{raii{10}, raii{10}},
{raii{9}, raii{9}},
{raii{8}, raii{8}},
{raii{7}, raii{7}},
{raii{6}, raii{6}},
{raii{5}, raii{5}},
{raii{4}, raii{4}},
{raii{3}, raii{3}},
{raii{2}, raii{2}},
{raii{1}, raii{1}},
{raii{0}, raii{0}},
};
std::initializer_list<set_type::value_type> set_init_list{
raii{0},
raii{1},
raii{2},
raii{3},
raii{4},
raii{5},
raii{6},
raii{6},
raii{7},
raii{8},
raii{9},
raii{10},
raii{9},
raii{8},
raii{7},
raii{6},
raii{5},
raii{4},
raii{3},
raii{2},
raii{1},
raii{0},
};
auto test_map_and_init_list=std::make_pair(test_map,map_init_list);
auto test_set_and_init_list=std::make_pair(test_set,set_init_list);
namespace {
test::seed_t initialize_seed(795610904);
UNORDERED_AUTO_TEST (bucket_constructor) {
template <class X>
void bucket_constructor(X*)
{
raii::reset_counts();
bool was_thrown = false;
@ -25,7 +86,7 @@ namespace {
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold; ++i) {
try {
map_type m(128);
X m(128);
} catch (...) {
was_thrown = true;
}
@ -35,8 +96,12 @@ namespace {
BOOST_TEST(was_thrown);
}
template <class G> void iterator_range(G gen, test::random_generator rg)
template <class X, class GF>
void iterator_range(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
@ -46,7 +111,7 @@ namespace {
enable_exceptions();
try {
map_type x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
X x(values.begin(), values.end(), 0, hasher(1), key_equal(2),
allocator_type(3));
} catch (...) {
was_thrown = true;
@ -64,7 +129,7 @@ namespace {
enable_exceptions();
try {
map_type x(values.begin(), values.end(), allocator_type(3));
X x(values.begin(), values.end(), allocator_type(3));
} catch (...) {
was_thrown = true;
}
@ -81,7 +146,7 @@ namespace {
enable_exceptions();
try {
map_type x(
X x(
values.begin(), values.end(), values.size(), allocator_type(3));
} catch (...) {
was_thrown = true;
@ -99,7 +164,7 @@ namespace {
enable_exceptions();
try {
map_type x(values.begin(), values.end(), values.size(), hasher(1),
X x(values.begin(), values.end(), values.size(), hasher(1),
allocator_type(3));
} catch (...) {
was_thrown = true;
@ -111,8 +176,12 @@ namespace {
}
}
template <class G> void copy_constructor(G gen, test::random_generator rg)
template <class X, class GF>
void copy_constructor(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
@ -121,10 +190,10 @@ namespace {
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
X x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(x);
X y(x);
} catch (...) {
was_thrown = true;
}
@ -140,10 +209,10 @@ namespace {
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
X x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(x, allocator_type(4));
X y(x, allocator_type(4));
} catch (...) {
was_thrown = true;
}
@ -154,20 +223,24 @@ namespace {
}
}
template <class G> void move_constructor(G gen, test::random_generator rg)
template <class X, class GF>
void move_constructor(X*, GF gen_factory, test::random_generator rg)
{
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
raii::reset_counts();
bool was_thrown = false;
try {
map_type x(values.begin(), values.end(), 0);
X x(values.begin(), values.end(), 0);
enable_exceptions();
map_type y(std::move(x), allocator_type(4));
X y(std::move(x), allocator_type(4));
} catch (...) {
was_thrown = true;
}
@ -178,33 +251,13 @@ namespace {
}
}
UNORDERED_AUTO_TEST (initializer_list_bucket_count) {
using value_type = typename map_type::value_type;
template <class X, class IL>
void initializer_list_bucket_count(std::pair<X*, IL> p)
{
using value_type = typename X::value_type;
using allocator_type = typename X::allocator_type;
std::initializer_list<value_type> values{
value_type{raii{0}, raii{0}},
value_type{raii{1}, raii{1}},
value_type{raii{2}, raii{2}},
value_type{raii{3}, raii{3}},
value_type{raii{4}, raii{4}},
value_type{raii{5}, raii{5}},
value_type{raii{6}, raii{6}},
value_type{raii{6}, raii{6}},
value_type{raii{7}, raii{7}},
value_type{raii{8}, raii{8}},
value_type{raii{9}, raii{9}},
value_type{raii{10}, raii{10}},
value_type{raii{9}, raii{9}},
value_type{raii{8}, raii{8}},
value_type{raii{7}, raii{7}},
value_type{raii{6}, raii{6}},
value_type{raii{5}, raii{5}},
value_type{raii{4}, raii{4}},
value_type{raii{3}, raii{3}},
value_type{raii{2}, raii{2}},
value_type{raii{1}, raii{1}},
value_type{raii{0}, raii{0}},
};
auto init_list = p.second;
{
raii::reset_counts();
@ -213,7 +266,7 @@ namespace {
enable_exceptions();
for (std::size_t i = 0; i < throw_threshold; ++i) {
try {
map_type x(values, 0, hasher(1), key_equal(2), allocator_type(3));
X x(init_list, 0, hasher(1), key_equal(2), allocator_type(3));
} catch (...) {
++num_throws;
}
@ -231,7 +284,7 @@ namespace {
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
try {
map_type x(values, allocator_type(3));
X x(init_list, allocator_type(3));
} catch (...) {
++num_throws;
}
@ -249,7 +302,7 @@ namespace {
enable_exceptions();
for (std::size_t i = 0; i < alloc_throw_threshold * 2; ++i) {
try {
map_type x(values, values.size() * 2, allocator_type(3));
X x(init_list, init_list.size() * 2, allocator_type(3));
} catch (...) {
++num_throws;
}
@ -267,7 +320,7 @@ namespace {
enable_exceptions();
for (std::size_t i = 0; i < throw_threshold; ++i) {
try {
map_type x(values, values.size() * 2, hasher(1), allocator_type(3));
X x(init_list, init_list.size() * 2, hasher(1), allocator_type(3));
} catch (...) {
++num_throws;
}
@ -285,20 +338,31 @@ using test::limited_range;
using test::sequential;
// clang-format off
UNORDERED_TEST(
bucket_constructor,
((test_map)(test_set)))
UNORDERED_TEST(
iterator_range,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
copy_constructor,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)))
UNORDERED_TEST(
move_constructor,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)))
UNORDERED_TEST(
initializer_list_bucket_count,
((test_map_and_init_list)(test_set_and_init_list)))
// clang-format on
RUN_TESTS()

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
@ -15,6 +17,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_erased{0};
auto const old_size = x.size();
@ -27,9 +32,9 @@ namespace {
enable_exceptions();
thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
for (auto const& k : values) {
for (auto const& v : values) {
try {
auto count = x.erase(k.first);
auto count = x.erase(get_key(v));
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
@ -46,7 +51,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} lvalue_eraser;
@ -55,6 +61,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_erased{0};
@ -68,8 +83,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -77,17 +92,17 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
enable_exceptions();
thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
for (auto const& k : s) {
for (auto const& v : s) {
try {
auto count = x.erase_if(k.first,
[threshold](value_type& v) { return v.second.x_ > threshold; });
auto count = x.erase_if(get_key(v),
[threshold](arg_type& w) { return get_value(w).x_ > threshold; });
num_erased += count;
BOOST_TEST_LE(count, 1u);
BOOST_TEST_GE(count, 0u);
@ -104,7 +119,8 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * num_erased);
BOOST_TEST_EQ(
raii::destructor, old_d + value_type_cardinality * num_erased);
}
} lvalue_eraser_if;
@ -113,6 +129,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
auto const old_size = x.size();
@ -124,8 +149,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -133,7 +158,7 @@ namespace {
auto expected_erasures = 0u;
x.visit_all([&expected_erasures, threshold](value_type const& v) {
if (v.second.x_ > threshold) {
if (get_value(v).x_ > threshold) {
++expected_erasures;
}
});
@ -142,14 +167,14 @@ namespace {
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 256; ++i) {
try {
x.erase_if([threshold](value_type& v) {
x.erase_if([threshold](arg_type& v) {
static std::atomic<std::uint32_t> c{0};
auto t = ++c;
if (should_throw && (t % throw_threshold == 0)) {
throw exception_tag{};
}
return v.second.x_ > threshold;
return get_value(v).x_ > threshold;
});
} catch (...) {
}
@ -161,7 +186,9 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
BOOST_TEST_EQ(
raii::destructor,
old_d + value_type_cardinality * (old_size - x.size()));
}
} erase_if;
@ -170,6 +197,15 @@ namespace {
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
auto const old_size = x.size();
@ -181,8 +217,8 @@ namespace {
auto max = 0;
x.visit_all([&max](value_type const& v) {
if (v.second.x_ > max) {
max = v.second.x_;
if (get_value(v).x_ > max) {
max = get_value(v).x_;
}
});
@ -192,14 +228,14 @@ namespace {
thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
for (std::size_t i = 0; i < 256; ++i) {
try {
boost::unordered::erase_if(x, [threshold](value_type& v) {
boost::unordered::erase_if(x, [threshold](arg_type& v) {
static std::atomic<std::uint32_t> c{0};
auto t = ++c;
if (should_throw && (t % throw_threshold == 0)) {
throw exception_tag{};
}
return v.second.x_ > threshold;
return get_value(v).x_ > threshold;
});
} catch (...) {
@ -212,16 +248,18 @@ namespace {
BOOST_TEST_EQ(raii::copy_constructor, old_cc);
BOOST_TEST_EQ(raii::move_constructor, old_mc);
BOOST_TEST_EQ(raii::destructor, old_d + 2 * (old_size - x.size()));
BOOST_TEST_EQ(
raii::destructor,
old_d + value_type_cardinality * (old_size - x.size()));
}
} free_fn_erase_if;
template <class X, class G, class F>
void erase(X*, G gen, F eraser, test::random_generator rg)
template <class X, class GF, class F>
void erase(X*, GF gen_factory, F eraser, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
@ -231,13 +269,13 @@ namespace {
x.insert(v);
}
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
BOOST_TEST_EQ(raii::destructor, 0u);
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
eraser(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
check_raii_counts();
@ -245,6 +283,8 @@ namespace {
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
boost::unordered::concurrent_flat_set<raii, stateful_hash,
stateful_key_equal, stateful_allocator<raii> >* set;
} // namespace
@ -255,8 +295,9 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
erase,
((map))
((exception_value_type_generator)(exception_init_type_generator))
((map)(set))
((exception_value_type_generator_factory)
(exception_init_type_generator_factory))
((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if))
((default_generator)(sequential)(limited_range)))

View File

@ -1,14 +1,20 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_UNORDERED_TEST_CFOA_EXCEPTION_HELPERS_HPP
#define BOOST_UNORDERED_TEST_CFOA_EXCEPTION_HELPERS_HPP
#include "../helpers/generators.hpp"
#include "../helpers/test.hpp"
#include "common_helpers.hpp"
#include <boost/compat/latch.hpp>
#include <boost/container_hash/hash.hpp>
#include <boost/core/span.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <algorithm>
#include <atomic>
@ -308,16 +314,54 @@ std::size_t hash_value(raii const& r) noexcept
return hasher(r.x_);
}
struct exception_value_type_generator_type
template <typename K>
struct exception_value_generator
{
std::pair<raii const, raii> operator()(test::random_generator rg)
using value_type = raii;
value_type operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
return value_type(a);
}
};
template <typename K, typename V>
struct exception_value_generator<std::pair<K, V> >
{
static constexpr bool const_key = std::is_const<K>::value;
static constexpr bool const_mapped = std::is_const<V>::value;
using value_type = std::pair<
typename std::conditional<const_key, raii const, raii>::type,
typename std::conditional<const_mapped, raii const, raii>::type>;
value_type operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
int b = generate(p, rg);
return std::make_pair(raii{a}, raii{b});
}
} exception_value_type_generator;
};
struct exception_value_type_generator_factory_type
{
template <typename Container>
exception_value_generator<typename Container::value_type> get()
{
return {};
}
} exception_value_type_generator_factory;
struct exception_init_type_generator_factory_type
{
template <typename Container>
exception_value_generator<typename Container::init_type> get()
{
return {};
}
} exception_init_type_generator_factory;
struct exception_init_type_generator_type
{
@ -388,29 +432,6 @@ template <class T, class F> void thread_runner(std::vector<T>& values, F f)
}
}
template <class X, class Y>
void test_matches_reference(X const& x, Y const& reference_map)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}));
}
template <class X, class Y>
void test_fuzzy_matches_reference(
X const& x, Y const& reference_map, test::random_generator rg)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}
}));
}
template <class T> using span_value_type = typename T::value_type;
void check_raii_counts()
@ -442,3 +463,5 @@ auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
}
return v;
}
#endif // BOOST_UNORDERED_TEST_CFOA_EXCEPTION_HELPERS_HPP

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
@ -84,6 +86,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
x.reserve(values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
@ -92,11 +97,19 @@ namespace {
rvalue_inserter_type::operator()(values, x);
if (std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_EQ(raii::move_constructor, x.size());
if (std::is_same<typename X::key_type,
typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, x.size());
}
else {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_EQ(raii::move_constructor, x.size());
}
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * x.size());
}
}
} norehash_rvalue_inserter;
@ -246,6 +259,13 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
@ -253,7 +273,7 @@ namespace {
for (auto& r : s) {
try {
bool b =
x.insert_or_visit(r, [](typename X::value_type& v) { (void)v; });
x.insert_or_visit(r, [](arg_type& v) { (void)v; });
if (b) {
++num_inserts;
@ -306,6 +326,13 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_inserts{0};
enable_exceptions();
@ -313,7 +340,7 @@ namespace {
for (auto& r : s) {
try {
bool b = x.insert_or_visit(
std::move(r), [](typename X::value_type& v) { (void)v; });
std::move(r), [](arg_type& v) { (void)v; });
if (b) {
++num_inserts;
@ -377,14 +404,14 @@ namespace {
}
} iterator_range_insert_or_visit;
template <class X, class G, class F>
void insert(X*, G gen, F inserter, test::random_generator rg)
template <class X, class GF, class F>
void insert(X*, GF gen_factory, F inserter, test::random_generator rg)
{
disable_exceptions();
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
{
@ -392,13 +419,15 @@ namespace {
inserter(values, x);
test_fuzzy_matches_reference(x, reference_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
check_raii_counts();
}
boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
boost::unordered::concurrent_flat_set<raii, stateful_hash,
stateful_key_equal, stateful_allocator<raii> >* set;
} // namespace
@ -409,8 +438,9 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
insert,
((map))
((exception_value_type_generator)(exception_init_type_generator))
((map)(set))
((exception_value_type_generator_factory)
(exception_init_type_generator_factory))
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
(lvalue_insert_or_cvisit)(lvalue_insert_or_visit)
@ -421,7 +451,7 @@ UNORDERED_TEST(
UNORDERED_TEST(
insert,
((map))
((exception_init_type_generator))
((exception_init_type_generator_factory))
((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)
(rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign))
((default_generator)(sequential)(limited_range)))

View File

@ -1,29 +1,38 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "exception_helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
namespace {
test::seed_t initialize_seed(223333016);
template <class G> void merge(G gen, test::random_generator rg)
template <class X, class GF>
void merge(X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
@ -37,10 +46,10 @@ namespace {
for (unsigned i = 0; i < 5 * alloc_throw_threshold; ++i) {
disable_exceptions();
map_type x1(0, hasher(1), key_equal(2), allocator_type(3));
X x1(0, hasher(1), key_equal(2), allocator_type(3));
x1.insert(begin, mid);
map_type x2(0, hasher(2), key_equal(1), allocator_type(3));
X x2(0, hasher(2), key_equal(1), allocator_type(3));
x2.insert(mid, end);
enable_exceptions();
@ -51,8 +60,8 @@ namespace {
}
disable_exceptions();
test_fuzzy_matches_reference(x1, reference_map, rg);
test_fuzzy_matches_reference(x2, reference_map, rg);
test_fuzzy_matches_reference(x1, reference_cont, rg);
test_fuzzy_matches_reference(x2, reference_cont, rg);
}
BOOST_TEST_GT(num_throws, 0u);
@ -70,7 +79,8 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
merge,
((exception_value_type_generator))
((test_map)(test_set))
((exception_value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/config/workaround.hpp>
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
#include <boost/unordered/concurrent_flat_set_fwd.hpp>
#include <limits>
test::seed_t initialize_seed{32304628};
@ -34,37 +36,89 @@ bool unequal_call(boost::unordered::concurrent_flat_map<T, T>& x1,
return x1 != x2;
}
#include <boost/unordered/concurrent_flat_map.hpp>
using map_type = boost::unordered::concurrent_flat_map<int, int>;
#if !defined(BOOST_CLANG_VERSION) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 30700) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, >= 30800)
// clang-3.7 seems to have a codegen bug here so we workaround it
UNORDERED_AUTO_TEST (fwd_swap_call) {
map_type x1, x2;
swap_call(x1, x2);
template <class T>
void swap_call(boost::unordered::concurrent_flat_set<T>& x1,
boost::unordered::concurrent_flat_set<T>& x2)
{
swap(x1, x2);
}
#endif
template <class T>
bool equal_call(boost::unordered::concurrent_flat_set<T>& x1,
boost::unordered::concurrent_flat_set<T>& x2)
{
return x1 == x2;
}
UNORDERED_AUTO_TEST (fwd_equal_call) {
map_type x1, x2;
template <class T>
bool unequal_call(boost::unordered::concurrent_flat_set<T>& x1,
boost::unordered::concurrent_flat_set<T>& x2)
{
return x1 != x2;
}
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
using map_type = boost::unordered::concurrent_flat_map<int, int>;
using set_type = boost::unordered::concurrent_flat_map<int, int>;
map_type* test_map;
set_type* test_set;
template <typename X>
void fwd_swap_call(X*)
{
#if !defined(BOOST_CLANG_VERSION) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 30700) || \
BOOST_WORKAROUND(BOOST_CLANG_VERSION, >= 30800)
// clang-3.7 seems to have a codegen bug here so we workaround it
X x1, x2;
swap_call(x1, x2);
#endif
}
template <typename X>
void fwd_equal_call(X*)
{
X x1, x2;
BOOST_TEST(equal_call(x1, x2));
}
UNORDERED_AUTO_TEST (fwd_unequal_call) {
map_type x1, x2;
template <typename X>
void fwd_unequal_call(X*)
{
X x1, x2;
BOOST_TEST_NOT(unequal_call(x1, x2));
}
// this isn't the best place for this test but it's better than introducing a
// new file
UNORDERED_AUTO_TEST (max_size) {
map_type x1;
template <typename X>
void max_size(X*)
{
X x1;
BOOST_TEST_EQ(
x1.max_size(), std::numeric_limits<typename map_type::size_type>::max());
x1.max_size(), std::numeric_limits<typename X::size_type>::max());
}
// clang-format off
UNORDERED_TEST(
fwd_swap_call,
((test_map)(test_set)))
UNORDERED_TEST(
fwd_equal_call,
((test_map)(test_set)))
UNORDERED_TEST(
fwd_unequal_call,
((test_map)(test_set)))
UNORDERED_TEST(
max_size,
((test_map)(test_set)))
// clang-format on
RUN_TESTS()

View File

@ -1,4 +1,5 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
@ -7,11 +8,15 @@
#include "../helpers/generators.hpp"
#include "../helpers/test.hpp"
#include "common_helpers.hpp"
#include <boost/compat/latch.hpp>
#include <boost/container_hash/hash.hpp>
#include <boost/core/span.hpp>
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
#include <boost/unordered/concurrent_flat_set_fwd.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <algorithm>
#include <atomic>
@ -328,27 +333,48 @@ auto make_random_values(std::size_t count, F f) -> std::vector<decltype(f())>
return v;
}
struct value_type_generator_type
template <typename K>
struct value_generator
{
std::pair<raii const, raii> operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
int b = generate(p, rg);
return std::make_pair(raii{a}, raii{b});
}
} value_type_generator;
using value_type = raii;
struct init_type_generator_type
value_type operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
return value_type(a);
}
};
template <typename K, typename V>
struct value_generator<std::pair<K, V> >
{
std::pair<raii, raii> operator()(test::random_generator rg)
static constexpr bool const_key = std::is_const<K>::value;
static constexpr bool const_mapped = std::is_const<V>::value;
using value_type = std::pair<
typename std::conditional<const_key, raii const, raii>::type,
typename std::conditional<const_mapped, raii const, raii>::type>;
value_type operator()(test::random_generator rg)
{
int* p = nullptr;
int a = generate(p, rg);
int b = generate(p, rg);
return std::make_pair(raii{a}, raii{b});
}
} init_type_generator;
};
struct value_type_generator_factory_type
{
template <typename Container>
value_generator<typename Container::value_type> get() { return {}; }
} value_type_generator_factory;
struct init_type_generator_factory_type
{
template <typename Container>
value_generator<typename Container::init_type> get() { return {}; }
} init_type_generator_factory;
template <class T>
std::vector<boost::span<T> > split(
@ -408,29 +434,6 @@ template <class T, class F> void thread_runner(std::vector<T>& values, F f)
}
}
template <class X, class Y>
void test_matches_reference(X const& x, Y const& reference_map)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}));
}
template <class X, class Y>
void test_fuzzy_matches_reference(
X const& x, Y const& reference_map, test::random_generator rg)
{
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
}
}));
}
template <class T> using span_value_type = typename T::value_type;
void check_raii_counts()
@ -642,4 +645,4 @@ public:
fancy_allocator& operator=(fancy_allocator const&) { return *this; }
};
#endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP
#endif // BOOST_UNORDERED_TEST_CFOA_HELPERS_HPP

View File

@ -1,18 +1,27 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
struct raii_convertible
{
int x, y;
raii_convertible(int x_, int y_) : x{x_}, y{y_} {}
int x = 0, y = 0 ;
template <typename T>
raii_convertible(T const & t) : x{t.x_} {}
template <typename T, typename Q>
raii_convertible(std::pair<T, Q> const & p) : x{p.first.x_}, y{p.second.x_}
{}
operator raii() { return {x}; }
operator std::pair<raii const, raii>() { return {x, y}; }
};
@ -23,6 +32,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
thread_runner(values, [&x, &num_inserts](boost::span<T> s) {
for (auto const& r : s) {
@ -33,7 +45,8 @@ namespace {
}
});
BOOST_TEST_EQ(num_inserts, x.size());
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::copy_assignment, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
}
@ -43,9 +56,13 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
x.reserve(values.size());
lvalue_inserter_type::operator()(values, x);
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * x.size());
BOOST_TEST_EQ(raii::move_constructor, 0u);
}
} norehash_lvalue_inserter;
@ -67,7 +84,8 @@ namespace {
});
BOOST_TEST_EQ(num_inserts, x.size());
if (std::is_same<T, typename X::value_type>::value) {
if (std::is_same<T, typename X::value_type>::value &&
!std::is_same<typename X::key_type, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
@ -82,6 +100,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
x.reserve(values.size());
BOOST_TEST_EQ(raii::copy_constructor, 0u);
@ -90,11 +111,19 @@ namespace {
rvalue_inserter_type::operator()(values, x);
if (std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_EQ(raii::move_constructor, x.size());
if (std::is_same<typename X::key_type,
typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, x.size());
}
else {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_EQ(raii::move_constructor, x.size());
}
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_EQ(raii::move_constructor, 2 * x.size());
BOOST_TEST_EQ(
raii::move_constructor, value_type_cardinality * x.size());
}
}
} norehash_rvalue_inserter;
@ -103,17 +132,21 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::vector<raii_convertible> values2;
values2.reserve(values.size());
for (auto const& p : values) {
values2.push_back(raii_convertible(p.first.x_, p.second.x_));
for (auto const& v : values) {
values2.push_back(raii_convertible(v));
}
thread_runner(values2, [&x](boost::span<raii_convertible> s) {
x.insert(s.begin(), s.end());
});
BOOST_TEST_EQ(raii::default_constructor, 2 * values2.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values2.size());
#if BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 50300) && \
BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50500)
// some versions of old gcc have trouble eliding copies here
@ -253,6 +286,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
@ -273,7 +309,8 @@ namespace {
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
BOOST_TEST_EQ(
raii::copy_constructor, value_type_cardinality * x.size());
// don't check move construction count here because of rehashing
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
@ -284,12 +321,22 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b =
x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) {
x.insert_or_visit(r, [&num_invokes](arg_type& v) {
(void)v;
++num_invokes;
});
@ -304,7 +351,7 @@ namespace {
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 0u);
BOOST_TEST_EQ(raii::copy_constructor, 2 * x.size());
BOOST_TEST_EQ(raii::copy_constructor, value_type_cardinality * x.size());
// don't check move construction count here because of rehashing
BOOST_TEST_GT(raii::move_constructor, 0u);
BOOST_TEST_EQ(raii::move_assignment, 0u);
@ -315,6 +362,9 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
@ -337,11 +387,19 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
if (std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_GE(raii::move_constructor, x.size());
if (std::is_same<typename X::key_type,
typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, x.size());
}
else {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_GE(raii::move_constructor, x.size());
}
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_GE(
raii::move_constructor, value_type_cardinality * x.size());
}
}
} rvalue_insert_or_cvisit;
@ -350,12 +408,22 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_inserts{0};
std::atomic<std::uint64_t> num_invokes{0};
thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span<T> s) {
for (auto& r : s) {
bool b = x.insert_or_visit(
std::move(r), [&num_invokes](typename X::value_type& v) {
std::move(r), [&num_invokes](arg_type& v) {
(void)v;
++num_invokes;
});
@ -371,11 +439,19 @@ namespace {
BOOST_TEST_EQ(raii::default_constructor, 0u);
if (std::is_same<T, typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_GE(raii::move_constructor, x.size());
if (std::is_same<typename X::key_type,
typename X::value_type>::value) {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, x.size());
}
else {
BOOST_TEST_EQ(raii::copy_constructor, x.size());
BOOST_TEST_GE(raii::move_constructor, x.size());
}
} else {
BOOST_TEST_EQ(raii::copy_constructor, 0u);
BOOST_TEST_GE(raii::move_constructor, 2 * x.size());
BOOST_TEST_GE(
raii::move_constructor, value_type_cardinality * x.size());
}
}
} rvalue_insert_or_visit;
@ -384,10 +460,13 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::vector<raii_convertible> values2;
values2.reserve(values.size());
for (auto const& p : values) {
values2.push_back(raii_convertible(p.first.x_, p.second.x_));
for (auto const& v : values) {
values2.push_back(raii_convertible(v));
}
std::atomic<std::uint64_t> num_invokes{0};
@ -402,7 +481,8 @@ namespace {
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values2.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values2.size());
#if BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 50300) && \
BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50500)
// skip test
@ -417,10 +497,13 @@ namespace {
{
template <class T, class X> void operator()(std::vector<T>& values, X& x)
{
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
std::vector<raii_convertible> values2;
values2.reserve(values.size());
for (auto const& p : values) {
values2.push_back(raii_convertible(p.first.x_, p.second.x_));
for (auto const& v : values) {
values2.push_back(raii_convertible(v));
}
std::atomic<std::uint64_t> num_invokes{0};
@ -435,7 +518,8 @@ namespace {
BOOST_TEST_EQ(num_invokes, values.size() - x.size());
BOOST_TEST_EQ(raii::default_constructor, 2 * values2.size());
BOOST_TEST_EQ(
raii::default_constructor, value_type_cardinality * values2.size());
#if BOOST_WORKAROUND(BOOST_GCC_VERSION, >= 50300) && \
BOOST_WORKAROUND(BOOST_GCC_VERSION, < 50500)
// skip test
@ -446,12 +530,12 @@ namespace {
}
} iterator_range_insert_or_visit;
template <class X, class G, class F>
void insert(X*, G gen, F inserter, test::random_generator rg)
template <class X, class GF, class F>
void insert(X*, GF gen_factory, F inserter, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
{
@ -459,13 +543,13 @@ namespace {
inserter(values, x);
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
using value_type = typename X::value_type;
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
if (rg == test::sequential) {
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}
}));
}
@ -480,39 +564,21 @@ namespace {
raii::destructor);
}
template <class X> void insert_initializer_list(X*)
template <class X, class IL>
void insert_initializer_list(std::pair<X*, IL> p)
{
using value_type = typename X::value_type;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::initializer_list<value_type> values{
value_type{raii{0}, raii{0}},
value_type{raii{1}, raii{1}},
value_type{raii{2}, raii{2}},
value_type{raii{3}, raii{3}},
value_type{raii{4}, raii{4}},
value_type{raii{5}, raii{5}},
value_type{raii{6}, raii{6}},
value_type{raii{6}, raii{6}},
value_type{raii{7}, raii{7}},
value_type{raii{8}, raii{8}},
value_type{raii{9}, raii{9}},
value_type{raii{10}, raii{10}},
value_type{raii{9}, raii{9}},
value_type{raii{8}, raii{8}},
value_type{raii{7}, raii{7}},
value_type{raii{6}, raii{6}},
value_type{raii{5}, raii{5}},
value_type{raii{4}, raii{4}},
value_type{raii{3}, raii{3}},
value_type{raii{2}, raii{2}},
value_type{raii{1}, raii{1}},
value_type{raii{0}, raii{0}},
};
auto init_list = p.second;
std::vector<raii> dummy;
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(
init_list.begin(), init_list.end());
raii::reset_counts();
{
@ -520,13 +586,13 @@ namespace {
X x;
thread_runner(
dummy, [&x, &values](boost::span<raii>) { x.insert(values); });
dummy, [&x, &init_list](boost::span<raii>) { x.insert(init_list); });
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}));
}
@ -549,27 +615,27 @@ namespace {
X x;
thread_runner(dummy, [&x, &values, &num_invokes](boost::span<raii>) {
x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) {
thread_runner(dummy, [&x, &init_list, &num_invokes](boost::span<raii>) {
x.insert_or_visit(init_list, [&num_invokes](arg_type& v) {
(void)v;
++num_invokes;
});
x.insert_or_cvisit(
values, [&num_invokes](typename X::value_type const& v) {
init_list, [&num_invokes](typename X::value_type const& v) {
(void)v;
++num_invokes;
});
});
BOOST_TEST_EQ(num_invokes, (values.size() - x.size()) +
(num_threads - 1) * values.size() +
num_threads * values.size());
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(num_invokes, (init_list.size() - x.size()) +
(num_threads - 1) * init_list.size() +
num_threads * init_list.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map[kv.first]);
BOOST_TEST_EQ(x.size(), x.visit_all([&](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
}));
}
@ -606,6 +672,64 @@ namespace {
std::equal_to<raii>, fancy_allocator<std::pair<raii const, raii> > >*
fancy_map;
boost::unordered::concurrent_flat_set<raii>* set;
boost::unordered::concurrent_flat_set<raii, boost::hash<raii>,
std::equal_to<raii>, fancy_allocator<std::pair<raii const, raii> > >*
fancy_set;
std::initializer_list<std::pair<raii const, raii> > map_init_list{
{raii{0}, raii{0}},
{raii{1}, raii{1}},
{raii{2}, raii{2}},
{raii{3}, raii{3}},
{raii{4}, raii{4}},
{raii{5}, raii{5}},
{raii{6}, raii{6}},
{raii{6}, raii{6}},
{raii{7}, raii{7}},
{raii{8}, raii{8}},
{raii{9}, raii{9}},
{raii{10}, raii{10}},
{raii{9}, raii{9}},
{raii{8}, raii{8}},
{raii{7}, raii{7}},
{raii{6}, raii{6}},
{raii{5}, raii{5}},
{raii{4}, raii{4}},
{raii{3}, raii{3}},
{raii{2}, raii{2}},
{raii{1}, raii{1}},
{raii{0}, raii{0}},
};
std::initializer_list<raii> set_init_list{
raii{0},
raii{1},
raii{2},
raii{3},
raii{4},
raii{5},
raii{6},
raii{6},
raii{7},
raii{8},
raii{9},
raii{10},
raii{9},
raii{8},
raii{7},
raii{6},
raii{5},
raii{4},
raii{3},
raii{2},
raii{1},
raii{0},
};
auto map_and_init_list=std::make_pair(map,map_init_list);
auto set_and_init_list=std::make_pair(set,set_init_list);
} // namespace
using test::default_generator;
@ -615,12 +739,12 @@ using test::sequential;
// clang-format off
UNORDERED_TEST(
insert_initializer_list,
((map)))
((map_and_init_list)(set_and_init_list)))
UNORDERED_TEST(
insert,
((map)(fancy_map))
((value_type_generator)(init_type_generator))
((map)(fancy_map)(set)(fancy_set))
((value_type_generator_factory)(init_type_generator_factory))
((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter)
(norehash_lvalue_inserter)(norehash_rvalue_inserter)
(lvalue_insert_or_cvisit)(lvalue_insert_or_visit)
@ -631,7 +755,7 @@ UNORDERED_TEST(
UNORDERED_TEST(
insert,
((map))
((init_type_generator))
((init_type_generator_factory))
((lvalue_insert_or_assign_copy_assign)(lvalue_insert_or_assign_move_assign)
(rvalue_insert_or_assign_copy_assign)(rvalue_insert_or_assign_move_assign))
((default_generator)(sequential)(limited_range)))
@ -639,7 +763,7 @@ UNORDERED_TEST(
UNORDERED_TEST(
insert,
((trans_map))
((init_type_generator))
((init_type_generator_factory))
((trans_insert_or_assign_copy_assign)(trans_insert_or_assign_move_assign))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
test::seed_t initialize_seed{402031699};
@ -14,12 +16,25 @@ using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
using map_type = boost::unordered::concurrent_flat_map<raii, raii,
hasher, key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using map2_type = boost::unordered::concurrent_flat_map<raii, raii,
std::hash<raii>, std::equal_to<raii>,
stateful_allocator<std::pair<raii const, raii> > >;
using map_value_type = typename map_type::value_type;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
using set2_type = boost::unordered::concurrent_flat_set<raii, std::hash<raii>,
std::equal_to<raii>, stateful_allocator<raii> >;
map_type* test_map;
map2_type* test_map2;
auto test_maps=std::make_pair(test_map,test_map2);
set_type* test_set;
set2_type* test_set2;
auto test_sets=std::make_pair(test_set,test_set2);
struct
{
@ -40,18 +55,23 @@ struct
} rvalue_merge;
namespace {
template <class F, class G>
void merge_tests(F merger, G gen, test::random_generator rg)
template <typename X, typename Y, class F, class GF>
void merge_tests(
std::pair<X*, Y*>, F merger, GF gen_factory, test::random_generator rg)
{
auto values = make_random_values(1024 * 8, [&] { return gen(rg); });
using value_type = typename X::value_type;
static constexpr auto value_type_cardinality =
value_cardinality<value_type>::value;
using allocator_type = typename X::allocator_type;
auto ref_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 8, [&] { return gen(rg); });
auto reference_cont = reference_container<X>(values.begin(), values.end());
{
raii::reset_counts();
map_type x(values.size(), hasher(1), key_equal(2), allocator_type(3));
X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
auto const old_cc = +raii::copy_constructor;
@ -59,48 +79,50 @@ namespace {
std::atomic<unsigned long long> num_merged{0};
thread_runner(values, [&x, &expected_copies, &num_merged, merger](
boost::span<map_value_type> s) {
using map2_type = boost::unordered::concurrent_flat_map<raii, raii,
std::hash<raii>, std::equal_to<raii>, allocator_type>;
map2_type y(s.size(), allocator_type(3));
boost::span<value_type> s) {
Y y(s.size(), allocator_type(3));
for (auto const& v : s) {
y.insert(v);
}
expected_copies += 2 * y.size();
expected_copies += value_type_cardinality * y.size();
BOOST_TEST(x.get_allocator() == y.get_allocator());
num_merged += merger(x, y);
});
BOOST_TEST_EQ(raii::copy_constructor, old_cc + expected_copies);
BOOST_TEST_EQ(raii::move_constructor, 2 * ref_map.size());
BOOST_TEST_EQ(+num_merged, ref_map.size());
BOOST_TEST_EQ(
raii::move_constructor,
value_type_cardinality * reference_cont.size());
BOOST_TEST_EQ(+num_merged, reference_cont.size());
test_fuzzy_matches_reference(x, ref_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
check_raii_counts();
}
template <class G>
void insert_and_merge_tests(G gen, test::random_generator rg)
template <typename X, typename Y, class GF>
void insert_and_merge_tests(
std::pair<X*, Y*>, GF gen_factory, test::random_generator rg)
{
using map2_type = boost::unordered::concurrent_flat_map<raii, raii,
std::hash<raii>, std::equal_to<raii>, allocator_type>;
static constexpr auto value_type_cardinality =
value_cardinality<typename X::value_type>::value;
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
auto ref_map = boost::unordered_flat_map<raii, raii>();
ref_map.insert(vals1.begin(), vals1.end());
ref_map.insert(vals2.begin(), vals2.end());
auto reference_cont = reference_container<X>();
reference_cont.insert(vals1.begin(), vals1.end());
reference_cont.insert(vals2.begin(), vals2.end());
{
raii::reset_counts();
map_type x1(2 * vals1.size(), hasher(1), key_equal(2), allocator_type(3));
X x1(2 * vals1.size(), hasher(1), key_equal(2), allocator_type(3));
map2_type x2(2 * vals1.size(), allocator_type(3));
Y x2(2 * vals1.size(), allocator_type(3));
std::thread t1, t2, t3;
boost::compat::latch l(2);
@ -190,12 +212,13 @@ namespace {
if (num_merges > 0) {
// num merges is 0 most commonly in the cast of the limited_range
// generator as both maps will contains keys from 0 to 99
BOOST_TEST_EQ(+raii::move_constructor, 2 * num_merges);
BOOST_TEST_EQ(
+raii::move_constructor, value_type_cardinality * num_merges);
BOOST_TEST_GE(call_count, 1u);
}
x1.merge(x2);
test_fuzzy_matches_reference(x1, ref_map, rg);
test_fuzzy_matches_reference(x1, reference_cont, rg);
}
check_raii_counts();
@ -206,13 +229,15 @@ namespace {
// clang-format off
UNORDERED_TEST(
merge_tests,
((test_maps)(test_sets))
((lvalue_merge)(rvalue_merge))
((value_type_generator))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
insert_and_merge_tests,
((value_type_generator))
((test_maps)(test_sets))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -12,23 +12,36 @@ namespace boost {
// Caveat lector: a proper handler shouldn't throw as it may be executed
// within a noexcept function.
void assertion_failed_msg(
char const*, char const*, char const*, char const*, long)
{
reentrancy_detected = true;
throw 0;
}
void assertion_failed_msg(
char const*, char const*, char const*, char const*, long)
{
reentrancy_detected = true;
throw 0;
}
void assertion_failed(char const*, char const*, char const*, long) // LCOV_EXCL_START
{
std::abort();
} // LCOV_EXCL_STOP
// LCOV_EXCL_START
void assertion_failed(char const*, char const*, char const*, long)
{
std::abort();
}
// LCOV_EXCL_STOP
}
} // namespace boost
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/lightweight_test.hpp>
using test::default_generator;
using map_type = boost::unordered::concurrent_flat_map<raii, raii>;
using set_type = boost::unordered::concurrent_flat_set<raii>;
map_type* test_map;
set_type* test_set;
template<typename F>
void detect_reentrancy(F f)
{
@ -40,40 +53,61 @@ void detect_reentrancy(F f)
BOOST_TEST(reentrancy_detected);
}
int main()
{
using map = boost::concurrent_flat_map<int, int>;
using value_type = typename map::value_type;
namespace {
template <class X, class GF>
void reentrancy_tests(X*, GF gen_factory, test::random_generator rg)
{
using key_type = typename X::key_type;
map m1, m2;
m1.emplace(0, 0);
m2.emplace(1, 0);
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
detect_reentrancy([&] {
m1.visit_all([&](value_type&) { (void)m1.contains(0); });
}); // LCOV_EXCL_LINE
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
detect_reentrancy([&] {
m1.visit_all([&](value_type&) { m1.rehash(0); });
}); // LCOV_EXCL_LINE
X x1, x2;
x1.insert(values.begin(), values.end());
x2.insert(values.begin(), values.end());
detect_reentrancy([&] {
m1.visit_all([&](value_type&) {
m2.visit_all([&](value_type&) {
m1=m2;
}); // LCOV_EXCL_START
detect_reentrancy([&] {
x1.visit_all([&](arg_type&) { (void)x1.contains(key_type()); });
}); // LCOV_EXCL_LINE
detect_reentrancy([&] {
x1.visit_all([&](arg_type&) { x1.rehash(0); });
}); // LCOV_EXCL_LINE
detect_reentrancy([&] {
x1.visit_all([&](arg_type&) {
x2.visit_all([&](arg_type&) {
x1=x2;
}); // LCOV_EXCL_START
});
});
});
// LCOV_EXCL_STOP
// LCOV_EXCL_STOP
detect_reentrancy([&] {
m1.visit_all([&](value_type&) {
m2.visit_all([&](value_type&) {
m2=m1;
}); // LCOV_EXCL_START
detect_reentrancy([&] {
x1.visit_all([&](arg_type&) {
x2.visit_all([&](arg_type&) {
x2=x1;
}); // LCOV_EXCL_START
});
});
});
// LCOV_EXCL_STOP
// LCOV_EXCL_STOP
}
return boost::report_errors();
}
} // namespace
// clang-format off
UNORDERED_TEST(
reentrancy_tests,
((test_map)(test_set))
((value_type_generator_factory))
((default_generator)))
// clang-format on
RUN_TESTS()

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
using test::default_generator;
using test::limited_range;
@ -12,18 +14,25 @@ using test::sequential;
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using map_value_type = typename map_type::value_type;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
map_type* test_map;
set_type* test_set;
namespace {
test::seed_t initialize_seed{748775921};
UNORDERED_AUTO_TEST (rehash_no_insert) {
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
template <typename X>
void rehash_no_insert(X*)
{
using allocator_type = typename X::allocator_type;
X x(0, hasher(1), key_equal(2), allocator_type(3));
BOOST_TEST_EQ(x.bucket_count(), 0u);
x.rehash(1024);
@ -37,10 +46,13 @@ namespace {
BOOST_TEST_EQ(x.bucket_count(), 0u);
}
UNORDERED_AUTO_TEST (reserve_no_insert) {
using size_type = map_type::size_type;
template <typename X>
void reserve_no_insert(X*)
{
using allocator_type = typename X::allocator_type;
using size_type = typename X::size_type;
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
X x(0, hasher(1), key_equal(2), allocator_type(3));
auto f = [&x](double c) {
return static_cast<size_type>(std::ceil(c / x.max_load_factor()));
@ -59,9 +71,13 @@ namespace {
BOOST_TEST_EQ(x.bucket_count(), f(0.0));
}
template <class G>
void insert_and_erase_with_rehash(G gen, test::random_generator rg)
template <class X, class GF>
void insert_and_erase_with_rehash(
X*, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
auto erase_indices = std::vector<std::size_t>(vals1.size());
@ -70,13 +86,13 @@ namespace {
}
shuffle_values(erase_indices);
auto ref_map = boost::unordered_flat_map<raii, raii>();
ref_map.insert(vals1.begin(), vals1.end());
auto reference_cont = reference_container<X>();
reference_cont.insert(vals1.begin(), vals1.end());
{
raii::reset_counts();
map_type x(0, hasher(1), key_equal(2), allocator_type(3));
X x(0, hasher(1), key_equal(2), allocator_type(3));
std::thread t1, t2, t3;
boost::compat::latch l(2);
@ -121,7 +137,7 @@ namespace {
for (std::size_t idx = 0; idx < erase_indices.size(); ++idx) {
auto const& val = vals1[erase_indices[idx]];
x.erase(val.first);
x.erase(get_key(val));
if (idx % 100 == 0) {
std::this_thread::yield();
}
@ -161,7 +177,7 @@ namespace {
BOOST_TEST_GE(call_count, 1u);
test_fuzzy_matches_reference(x, ref_map, rg);
test_fuzzy_matches_reference(x, reference_cont, rg);
}
check_raii_counts();
@ -169,9 +185,18 @@ namespace {
} // namespace
// clang-format off
UNORDERED_TEST(
rehash_no_insert,
((test_map)(test_set)))
UNORDERED_TEST(
reserve_no_insert,
((test_map)(test_set)))
UNORDERED_TEST(
insert_and_erase_with_rehash,
((value_type_generator))
((test_map)(test_set))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -1,10 +1,12 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
test::seed_t initialize_seed{996130204};
@ -56,17 +58,12 @@ template <class T> struct pocs_allocator
using hasher = stateful_hash;
using key_equal = stateful_key_equal;
using allocator_type = stateful_allocator<std::pair<raii const, raii> >;
using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, allocator_type>;
key_equal, stateful_allocator<std::pair<raii const, raii> > >;
using map_value_type = typename map_type::value_type;
using pocs_allocator_type = pocs_allocator<std::pair<const raii, raii> >;
using pocs_map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
key_equal, pocs_allocator_type>;
using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
key_equal, stateful_allocator<raii> >;
template <class T> struct is_nothrow_member_swappable
{
@ -75,13 +72,21 @@ template <class T> struct is_nothrow_member_swappable
};
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<
boost::unordered::concurrent_flat_map<int, int, std::hash<int>,
std::equal_to<int>, std::allocator<std::pair<int const, int> > > >::value);
replace_allocator<map_type, std::allocator> >::value);
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<pocs_map_type>::value);
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<
replace_allocator<map_type, pocs_allocator> >::value);
BOOST_STATIC_ASSERT(!is_nothrow_member_swappable<map_type>::value);
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<
replace_allocator<set_type, std::allocator> >::value);
BOOST_STATIC_ASSERT(is_nothrow_member_swappable<
replace_allocator<set_type, pocs_allocator> >::value);
BOOST_STATIC_ASSERT(!is_nothrow_member_swappable<set_type>::value);
namespace {
struct
{
@ -97,31 +102,31 @@ namespace {
}
} free_fn_swap;
template <class X, class F, class G>
void swap_tests(X*, F swapper, G gen, test::random_generator rg)
template <class X, class F, class GF>
void swap_tests(X*, F swapper, GF gen_factory, test::random_generator rg)
{
using allocator = typename X::allocator_type;
using value_type = typename X::value_type;
using allocator_type = typename X::allocator_type;
bool const pocs =
boost::allocator_propagate_on_container_swap<allocator>::type::value;
boost::allocator_propagate_on_container_swap<
allocator_type>::type::value;
auto gen = gen_factory.template get<X>();
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
auto ref_map1 =
boost::unordered_flat_map<raii, raii>(vals1.begin(), vals1.end());
auto ref_map2 =
boost::unordered_flat_map<raii, raii>(vals2.begin(), vals2.end());
auto reference_cont1 = reference_container<X>(vals1.begin(), vals1.end());
auto reference_cont2 = reference_container<X>(vals2.begin(), vals2.end());
{
raii::reset_counts();
X x1(vals1.begin(), vals1.end(), vals1.size(), hasher(1), key_equal(2),
allocator(3));
allocator_type(3));
X x2(vals2.begin(), vals2.end(), vals2.size(), hasher(2), key_equal(1),
pocs ? allocator(4) : allocator(3));
pocs ? allocator_type(4) : allocator_type(3));
if (pocs) {
BOOST_TEST(x1.get_allocator() != x2.get_allocator());
@ -132,7 +137,7 @@ namespace {
auto const old_cc = +raii::copy_constructor;
auto const old_mc = +raii::move_constructor;
thread_runner(vals1, [&x1, &x2, swapper](boost::span<map_value_type> s) {
thread_runner(vals1, [&x1, &x2, swapper](boost::span<value_type> s) {
(void)s;
swapper(x1, x2);
@ -143,20 +148,20 @@ namespace {
BOOST_TEST_EQ(raii::move_constructor, old_mc);
if (pocs) {
if (x1.get_allocator() == allocator(3)) {
BOOST_TEST(x2.get_allocator() == allocator(4));
if (x1.get_allocator() == allocator_type(3)) {
BOOST_TEST(x2.get_allocator() == allocator_type(4));
} else {
BOOST_TEST(x1.get_allocator() == allocator(4));
BOOST_TEST(x2.get_allocator() == allocator(3));
BOOST_TEST(x1.get_allocator() == allocator_type(4));
BOOST_TEST(x2.get_allocator() == allocator_type(3));
}
} else {
BOOST_TEST(x1.get_allocator() == allocator(3));
BOOST_TEST(x1.get_allocator() == allocator_type(3));
BOOST_TEST(x1.get_allocator() == x2.get_allocator());
}
if (x1.size() == ref_map1.size()) {
test_matches_reference(x1, ref_map1);
test_matches_reference(x2, ref_map2);
if (x1.size() == reference_cont1.size()) {
test_matches_reference(x1, reference_cont1);
test_matches_reference(x2, reference_cont2);
BOOST_TEST_EQ(x1.hash_function(), hasher(1));
BOOST_TEST_EQ(x1.key_eq(), key_equal(2));
@ -164,8 +169,8 @@ namespace {
BOOST_TEST_EQ(x2.hash_function(), hasher(2));
BOOST_TEST_EQ(x2.key_eq(), key_equal(1));
} else {
test_matches_reference(x2, ref_map1);
test_matches_reference(x1, ref_map2);
test_matches_reference(x2, reference_cont1);
test_matches_reference(x1, reference_cont2);
BOOST_TEST_EQ(x1.hash_function(), hasher(2));
BOOST_TEST_EQ(x1.key_eq(), key_equal(1));
@ -177,17 +182,21 @@ namespace {
check_raii_counts();
}
template <class F, class G>
void insert_and_swap(F swapper, G gen, test::random_generator rg)
template <class X, class F, class GF>
void insert_and_swap(
X*, F swapper, GF gen_factory, test::random_generator rg)
{
using allocator_type = typename X::allocator_type;
auto gen = gen_factory.template get<X>();
auto vals1 = make_random_values(1024 * 8, [&] { return gen(rg); });
auto vals2 = make_random_values(1024 * 4, [&] { return gen(rg); });
{
raii::reset_counts();
map_type x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
map_type x2(vals2.size(), hasher(2), key_equal(1), allocator_type(3));
X x1(vals1.size(), hasher(1), key_equal(2), allocator_type(3));
X x2(vals2.size(), hasher(2), key_equal(1), allocator_type(3));
std::thread t1, t2, t3;
boost::compat::latch l(2);
@ -282,21 +291,25 @@ namespace {
}
map_type* map;
pocs_map_type* pocs_map;
replace_allocator<map_type, pocs_allocator>* pocs_map;
set_type* set;
replace_allocator<set_type, pocs_allocator>* pocs_set;
} // namespace
// clang-format off
UNORDERED_TEST(
swap_tests,
((map)(pocs_map))
((map)(pocs_map)(set)(pocs_set))
((member_fn_swap)(free_fn_swap))
((value_type_generator))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(insert_and_swap,
((map)(set))
((member_fn_swap)(free_fn_swap))
((value_type_generator))
((value_type_generator_factory))
((default_generator)(sequential)(limited_range)))
// clang-format on

View File

@ -1,38 +1,64 @@
// Copyright (C) 2023 Christian Mazakas
// Copyright (C) 2023 Joaquin M Lopez Munoz
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
#include "helpers.hpp"
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/concurrent_flat_set.hpp>
#include <boost/core/ignore_unused.hpp>
#include <array>
#include <functional>
#include <vector>
namespace {
test::seed_t initialize_seed(335740237);
auto non_present_keys = []
{
std::array<raii,128> a;
for(std::size_t i = 0; i < a.size(); ++i) {
a[i].x_ = -((int)i + 1);
}
return a;
}();
template<typename T>
raii const & get_non_present_key(T const & x)
{
return non_present_keys[get_key(x).x_ % non_present_keys.size()];
}
struct lvalue_visitor_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
using value_type = typename X::value_type;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_visits{0};
std::atomic<std::uint64_t> total_count{0};
auto mut_visitor = [&num_visits, &reference_map](value_type& v) {
BOOST_TEST(reference_map.contains(v.first));
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
auto mut_visitor = [&num_visits, &reference_cont](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
auto const_visitor = [&num_visits, &reference_map](value_type const& v) {
BOOST_TEST(reference_map.contains(v.first));
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
auto const_visitor =
[&num_visits, &reference_cont](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
@ -40,14 +66,14 @@ namespace {
thread_runner(
values, [&x, &mut_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.visit(val.first, mut_visitor);
auto count = x.visit(get_key(val), mut_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.visit(val.second, mut_visitor);
count = x.visit(get_non_present_key(val), mut_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -63,16 +89,16 @@ namespace {
thread_runner(
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto const& y = x;
auto count = y.visit(val.first, const_visitor);
auto count = y.visit(get_key(val), const_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = y.visit(val.second, const_visitor);
count = y.visit(get_non_present_key(val), const_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -88,15 +114,15 @@ namespace {
thread_runner(
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.cvisit(val.first, const_visitor);
auto count = x.cvisit(get_key(val), const_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.cvisit(val.second, const_visitor);
count = x.cvisit(get_non_present_key(val), const_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -111,14 +137,14 @@ namespace {
{
thread_runner(values, [&x, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.count(val.first);
auto count = x.count(get_key(val));
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.count(val.second);
count = x.count(get_non_present_key(val));
BOOST_TEST_EQ(count, 0u);
}
});
@ -132,13 +158,13 @@ namespace {
{
thread_runner(values, [&x](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto contains = x.contains(val.first);
auto contains = x.contains(get_key(val));
BOOST_TEST(contains);
contains = x.contains(val.second);
contains = x.contains(get_non_present_key(val));
BOOST_TEST(!contains);
}
});
@ -152,22 +178,29 @@ namespace {
struct transp_visitor_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
using value_type = typename X::value_type;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> num_visits{0};
std::atomic<std::uint64_t> total_count{0};
auto mut_visitor = [&num_visits, &reference_map](value_type& v) {
BOOST_TEST(reference_map.contains(v.first));
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
auto mut_visitor = [&num_visits, &reference_cont](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
auto const_visitor = [&num_visits, &reference_map](value_type const& v) {
BOOST_TEST(reference_map.contains(v.first));
BOOST_TEST_EQ(v.second, reference_map.find(v.first)->second);
auto const_visitor = [&num_visits, &reference_cont](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
@ -175,15 +208,15 @@ namespace {
thread_runner(
values, [&x, &mut_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.visit(val.first.x_, mut_visitor);
auto count = x.visit(get_key(val).x_, mut_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.visit(val.second.x_, mut_visitor);
count = x.visit(get_non_present_key(val).x_, mut_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -199,16 +232,16 @@ namespace {
thread_runner(
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto const& y = x;
auto count = y.visit(val.first.x_, const_visitor);
auto count = y.visit(get_key(val).x_, const_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = y.visit(val.second.x_, const_visitor);
count = y.visit(get_non_present_key(val).x_, const_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -224,15 +257,15 @@ namespace {
thread_runner(
values, [&x, &const_visitor, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.cvisit(val.first.x_, const_visitor);
auto count = x.cvisit(get_key(val).x_, const_visitor);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.cvisit(val.second.x_, const_visitor);
count = x.cvisit(get_non_present_key(val).x_, const_visitor);
BOOST_TEST_EQ(count, 0u);
}
});
@ -247,14 +280,14 @@ namespace {
{
thread_runner(values, [&x, &total_count](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto count = x.count(val.first.x_);
auto count = x.count(get_key(val).x_);
BOOST_TEST_EQ(count, 1u);
total_count += count;
count = x.count(val.second.x_);
count = x.count(get_non_present_key(val).x_);
BOOST_TEST_EQ(count, 0u);
}
});
@ -268,13 +301,13 @@ namespace {
{
thread_runner(values, [&x](boost::span<T> s) {
for (auto const& val : s) {
auto r = val.first.x_;
auto r = get_key(val).x_;
BOOST_TEST(r >= 0);
auto contains = x.contains(val.first.x_);
auto contains = x.contains(get_key(val).x_);
BOOST_TEST(contains);
contains = x.contains(val.second.x_);
contains = x.contains(get_non_present_key(val).x_);
BOOST_TEST(!contains);
}
});
@ -288,24 +321,31 @@ namespace {
struct visit_all_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
using value_type = typename X::value_type;
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
std::atomic<std::uint64_t> total_count{0};
auto mut_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
auto mut_visitor = [&reference_cont](std::atomic<uint64_t>& num_visits) {
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
};
auto const_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
auto const_visitor = [&reference_cont](std::atomic<uint64_t>& num_visits) {
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
};
@ -352,45 +392,52 @@ namespace {
struct visit_while_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
using value_type = typename X::value_type;
auto mut_truthy_visitor = [&reference_map](
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
auto mut_truthy_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return true;
};
};
auto const_truthy_visitor = [&reference_map](
auto const_truthy_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return true;
};
};
auto mut_falsey_visitor = [&reference_map](
auto mut_falsey_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
++num_visits;
return (kv.second.x_ % 100) == 0;
return (get_value(v).x_ % 100) == 0;
};
};
auto const_falsey_visitor = [&reference_map](
auto const_falsey_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
++num_visits;
return (kv.second.x_ % 100) == 0;
return (get_value(v).x_ % 100) == 0;
};
};
@ -452,23 +499,30 @@ namespace {
struct exec_policy_visit_all_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
using value_type = typename X::value_type;
auto mut_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
auto mut_visitor = [&reference_cont](std::atomic<uint64_t>& num_visits) {
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
};
auto const_visitor = [&reference_map](std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
auto const_visitor = [&reference_cont](std::atomic<uint64_t>& num_visits) {
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
};
};
@ -502,7 +556,7 @@ namespace {
#else
(void)values;
(void)x;
(void)reference_map;
(void)reference_cont;
#endif
}
} exec_policy_visit_all;
@ -510,48 +564,55 @@ namespace {
struct exec_policy_visit_while_type
{
template <class T, class X, class M>
void operator()(std::vector<T>& values, X& x, M const& reference_map)
void operator()(std::vector<T>& values, X& x, M const& reference_cont)
{
#if defined(BOOST_UNORDERED_PARALLEL_ALGORITHMS)
using value_type = typename X::value_type;
auto mut_truthy_visitor = [&reference_map](
// concurrent_flat_set visit is always const access
using arg_type = typename std::conditional<
std::is_same<typename X::key_type, typename X::value_type>::value,
typename X::value_type const,
typename X::value_type
>::type;
auto mut_truthy_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return true;
};
};
auto const_truthy_visitor = [&reference_map](
auto const_truthy_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return true;
};
};
auto mut_falsey_visitor = [&reference_map](
auto mut_falsey_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](arg_type& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return (kv.second.x_ % 100) == 0;
return (get_value(v).x_ % 100) == 0;
};
};
auto const_falsey_visitor = [&reference_map](
auto const_falsey_visitor = [&reference_cont](
std::atomic<uint64_t>& num_visits) {
return [&reference_map, &num_visits](value_type const& kv) {
BOOST_TEST(reference_map.contains(kv.first));
BOOST_TEST_EQ(kv.second, reference_map.find(kv.first)->second);
return [&reference_cont, &num_visits](value_type const& v) {
BOOST_TEST(reference_cont.contains(get_key(v)));
BOOST_TEST_EQ(v, *reference_cont.find(get_key(v)));
++num_visits;
return (kv.second.x_ % 100) == 0;
return (get_value(v).x_ % 100) == 0;
};
};
@ -616,24 +677,17 @@ namespace {
#else
(void)values;
(void)x;
(void)reference_map;
(void)reference_cont;
#endif
}
} exec_policy_visit_while;
template <class X, class G, class F>
void visit(X*, G gen, F visitor, test::random_generator rg)
template <class X, class GF, class F>
void visit(X*, GF gen_factory, F visitor, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
for (auto& val : values) {
if (val.second.x_ == 0) {
val.second.x_ = 1;
}
val.second.x_ *= -1;
}
auto reference_map =
boost::unordered_flat_map<raii, raii>(values.begin(), values.end());
auto reference_cont = reference_container<X>(values.begin(), values.end());
raii::reset_counts();
@ -642,7 +696,7 @@ namespace {
for (auto const& v : values) {
x.insert(v);
}
BOOST_TEST_EQ(x.size(), reference_map.size());
BOOST_TEST_EQ(x.size(), reference_cont.size());
std::uint64_t old_default_constructor = raii::default_constructor;
std::uint64_t old_copy_constructor = raii::copy_constructor;
@ -650,7 +704,7 @@ namespace {
std::uint64_t old_copy_assignment = raii::copy_assignment;
std::uint64_t old_move_assignment = raii::move_assignment;
visitor(values, x, reference_map);
visitor(values, x, reference_cont);
BOOST_TEST_EQ(old_default_constructor, raii::default_constructor);
BOOST_TEST_EQ(old_copy_constructor, raii::copy_constructor);
@ -669,9 +723,10 @@ namespace {
raii::destructor);
}
template <class X, class G>
void empty_visit(X*, G gen, test::random_generator rg)
template <class X, class GF>
void empty_visit(X*, GF gen_factory, test::random_generator rg)
{
auto gen = gen_factory.template get<X>();
auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
using values_type = decltype(values);
using span_value_type = typename values_type::value_type;
@ -696,7 +751,7 @@ namespace {
BOOST_TEST_EQ(num_visits, 0u);
for (auto const& val : s) {
auto count = x.visit(val.first,
auto count = x.visit(get_key(val),
[&num_visits](typename X::value_type const&) { ++num_visits; });
BOOST_TEST_EQ(count, 0u);
}
@ -716,8 +771,8 @@ namespace {
BOOST_TEST_EQ(raii::destructor, 0u);
}
template <class X, class G>
void insert_and_visit(X*, G gen, test::random_generator rg)
template <class X, class GF>
void insert_and_visit(X*, GF gen_factory, test::random_generator rg)
{
// here we attempt to ensure happens-before and synchronizes-with
// the visitation thread essentially chases the insertion one
@ -726,6 +781,7 @@ namespace {
BOOST_TEST(rg == test::sequential);
auto gen = gen_factory.template get<X>();
auto const values = make_random_values(1024 * 16, [&] { return gen(rg); });
{
@ -752,9 +808,9 @@ namespace {
for (std::size_t idx = 0; idx < values.size(); ++idx) {
std::atomic_bool b{false};
while (!b) {
x.cvisit(values[idx].first,
x.cvisit(get_key(values[idx]),
[&b, &strs, idx, &values](typename X::value_type const& v) {
BOOST_TEST_EQ(v.second, values[idx].second);
BOOST_TEST_EQ(get_value(v), get_value(values[idx]));
BOOST_TEST_EQ(strs[idx], "rawr");
b = true;
});
@ -771,6 +827,9 @@ namespace {
boost::unordered::concurrent_flat_map<raii, raii>* map;
boost::unordered::concurrent_flat_map<raii, raii, transp_hash,
transp_key_equal>* transp_map;
boost::unordered::concurrent_flat_set<raii>* set;
boost::unordered::concurrent_flat_set<raii, transp_hash,
transp_key_equal>* transp_set;
} // namespace
@ -782,29 +841,30 @@ using test::sequential;
UNORDERED_TEST(
visit,
((map))
((value_type_generator)(init_type_generator))
((lvalue_visitor)(visit_all)(visit_while)(exec_policy_visit_all)(exec_policy_visit_while))
((map)(set))
((value_type_generator_factory)(init_type_generator_factory))
((lvalue_visitor)(visit_all)(visit_while)(exec_policy_visit_all)
(exec_policy_visit_while))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
visit,
((transp_map))
((value_type_generator)(init_type_generator))
((transp_map)(transp_set))
((value_type_generator_factory)(init_type_generator_factory))
((transp_visitor))
((default_generator)(sequential)(limited_range)))
UNORDERED_TEST(
empty_visit,
((map)(transp_map))
((value_type_generator)(init_type_generator))
((map)(transp_map)(set)(transp_set))
((value_type_generator_factory)(init_type_generator_factory))
((default_generator)(sequential)(limited_range))
)
UNORDERED_TEST(
insert_and_visit,
((map))
((value_type_generator))
((map)(set))
((value_type_generator_factory))
((sequential))
)