From ffcae204eea4df7d052fd0810e0ffd4d2e62cfc2 Mon Sep 17 00:00:00 2001 From: Christian Mazakas Date: Fri, 19 May 2023 12:33:14 -0700 Subject: [PATCH] Add insert_exception_tests --- test/Jamfile.v2 | 1 + test/cfoa/exception_helpers.hpp | 448 +++++++++++++++++++++ test/cfoa/exception_insert_tests.cpp | 566 +++++++++++++++++++++++++++ 3 files changed, 1015 insertions(+) create mode 100644 test/cfoa/exception_helpers.hpp create mode 100644 test/cfoa/exception_insert_tests.cpp diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index a84bc78f..8cf8e40c 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -190,6 +190,7 @@ local CFOA_TESTS = rehash_tests equality_tests fwd_tests + exception_insert_tests ; for local test in $(CFOA_TESTS) diff --git a/test/cfoa/exception_helpers.hpp b/test/cfoa/exception_helpers.hpp new file mode 100644 index 00000000..9c82e506 --- /dev/null +++ b/test/cfoa/exception_helpers.hpp @@ -0,0 +1,448 @@ +// Copyright (C) 2023 Christian Mazakas +// 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 "latch.hpp" + +#include "../helpers/generators.hpp" +#include "../helpers/test.hpp" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static std::size_t const num_threads = + std::max(2u, std::thread::hardware_concurrency()); + +std::atomic_bool should_throw{false}; + +constexpr std::uint32_t threshold = 2500; + +void enable_exceptions() { should_throw = true; } +void disable_exceptions() { should_throw = false; } + +struct exception_tag +{ +}; + +struct stateful_hash +{ + int x_ = -1; + + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + stateful_hash() {} + stateful_hash(stateful_hash const& rhs) : x_(rhs.x_) {} + stateful_hash(stateful_hash&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_hash(int const x) : x_{x} {} + + template std::size_t operator()(T const& t) const + { + throw_helper(); + std::size_t h = static_cast(x_); + boost::hash_combine(h, t); + return h; + } + + bool operator==(stateful_hash const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<(std::ostream& os, stateful_hash const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } + + friend void swap(stateful_hash& lhs, stateful_hash& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } +}; + +std::atomic stateful_hash::c{0}; + +struct stateful_key_equal +{ + int x_ = -1; + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + stateful_key_equal() = default; + stateful_key_equal(stateful_key_equal const&) = default; + stateful_key_equal(stateful_key_equal&& rhs) noexcept + { + auto tmp = x_; + x_ = rhs.x_; + rhs.x_ = tmp; + } + + stateful_key_equal(int const x) : x_{x} {} + + template bool operator()(T const& t, U const& u) const + { + throw_helper(); + return t == u; + } + + bool operator==(stateful_key_equal const& rhs) const { return x_ == rhs.x_; } + + friend std::ostream& operator<<( + std::ostream& os, stateful_key_equal const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } + + friend void swap(stateful_key_equal& lhs, stateful_key_equal& rhs) noexcept + { + if (&lhs != &rhs) { + std::swap(lhs.x_, rhs.x_); + } + } +}; +std::atomic stateful_key_equal::c{0}; + +template struct stateful_allocator +{ + int x_ = -1; + static std::atomic c; + + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + using value_type = T; + + stateful_allocator() = default; + stateful_allocator(stateful_allocator const&) = default; + stateful_allocator(stateful_allocator&&) = default; + + stateful_allocator(int const x) : x_{x} {} + + template + stateful_allocator(stateful_allocator const& rhs) : x_{rhs.x_} + { + } + + T* allocate(std::size_t n) + { + throw_helper(); + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T* p, std::size_t) { ::operator delete(p); } + + bool operator==(stateful_allocator const& rhs) const { return x_ == rhs.x_; } + bool operator!=(stateful_allocator const& rhs) const { return x_ != rhs.x_; } +}; + +template std::atomic stateful_allocator::c{0}; + +struct raii +{ + static std::atomic default_constructor; + static std::atomic copy_constructor; + static std::atomic move_constructor; + static std::atomic destructor; + + static std::atomic copy_assignment; + static std::atomic move_assignment; + + static std::atomic c; + void throw_helper() const + { + ++c; + if (should_throw && (c % threshold == 0)) { + throw exception_tag{}; + } + } + + int x_ = -1; + + raii() + { + throw_helper(); + ++default_constructor; + } + + raii(int const x) : x_{x} + { + throw_helper(); + ++default_constructor; + } + + raii(raii const& rhs) : x_{rhs.x_} + { + throw_helper(); + ++copy_constructor; + } + raii(raii&& rhs) noexcept : x_{rhs.x_} + { + rhs.x_ = -1; + ++move_constructor; + } + ~raii() { ++destructor; } + + raii& operator=(raii const& rhs) + { + throw_helper(); + ++copy_assignment; + if (this != &rhs) { + x_ = rhs.x_; + } + return *this; + } + + raii& operator=(raii&& rhs) noexcept + { + ++move_assignment; + if (this != &rhs) { + x_ = rhs.x_; + rhs.x_ = -1; + } + return *this; + } + + friend bool operator==(raii const& lhs, raii const& rhs) + { + return lhs.x_ == rhs.x_; + } + + friend bool operator!=(raii const& lhs, raii const& rhs) + { + return !(lhs == rhs); + } + + friend bool operator==(raii const& lhs, int const x) { return lhs.x_ == x; } + friend bool operator!=(raii const& lhs, int const x) + { + return !(lhs.x_ == x); + } + + friend bool operator==(int const x, raii const& rhs) { return rhs.x_ == x; } + + friend bool operator!=(int const x, raii const& rhs) + { + return !(rhs.x_ == x); + } + + friend std::ostream& operator<<(std::ostream& os, raii const& rhs) + { + os << "{ x_: " << rhs.x_ << " }"; + return os; + } + + friend std::ostream& operator<<( + std::ostream& os, std::pair const& rhs) + { + os << "pair<" << rhs.first << ", " << rhs.second << ">"; + return os; + } + + static void reset_counts() + { + default_constructor = 0; + copy_constructor = 0; + move_constructor = 0; + destructor = 0; + copy_assignment = 0; + move_assignment = 0; + c = 0; + + stateful_hash::c = 0; + stateful_key_equal::c = 0; + stateful_allocator::c = 0; + } + + friend void swap(raii& lhs, raii& rhs) { std::swap(lhs.x_, rhs.x_); } +}; + +std::atomic raii::default_constructor{0}; +std::atomic raii::copy_constructor{0}; +std::atomic raii::move_constructor{0}; +std::atomic raii::destructor{0}; +std::atomic raii::copy_assignment{0}; +std::atomic raii::move_assignment{0}; +std::atomic raii::c{0}; + +std::size_t hash_value(raii const& r) noexcept +{ + boost::hash hasher; + return hasher(r.x_); +} + +struct exception_value_type_generator_type +{ + std::pair 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_init_type_generator_type +{ + std::pair 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_init_type_generator; + +template +std::vector > split( + boost::span s, std::size_t const nt /* num threads*/) +{ + std::vector > subslices; + subslices.reserve(nt); + + auto a = s.size() / nt; + auto b = a; + if (s.size() % nt != 0) { + ++b; + } + + auto num_a = nt; + auto num_b = std::size_t{0}; + + if (nt * b > s.size()) { + num_a = nt * b - s.size(); + num_b = nt - num_a; + } + + auto sub_b = s.subspan(0, num_b * b); + auto sub_a = s.subspan(num_b * b); + + for (std::size_t i = 0; i < num_b; ++i) { + subslices.push_back(sub_b.subspan(i * b, b)); + } + + for (std::size_t i = 0; i < num_a; ++i) { + auto const is_last = i == (num_a - 1); + subslices.push_back( + sub_a.subspan(i * a, is_last ? boost::dynamic_extent : a)); + } + + return subslices; +} + +template void thread_runner(std::vector& values, F f) +{ + boost::latch latch(static_cast(num_threads)); + + std::vector threads; + auto subslices = split(values, num_threads); + + for (std::size_t i = 0; i < num_threads; ++i) { + threads.emplace_back([&f, &subslices, i, &latch] { + latch.arrive_and_wait(); + + auto s = subslices[i]; + f(s); + }); + } + + for (auto& t : threads) { + t.join(); + } +} + +template +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 +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 using span_value_type = typename T::value_type; + +void check_raii_counts() +{ + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ( + raii::default_constructor + raii::copy_constructor + raii::move_constructor, + raii::destructor); +} + +template void shuffle_values(std::vector& v) +{ + std::random_device rd; + std::mt19937 g(rd()); + + std::shuffle(v.begin(), v.end(), g); +} + +template +auto make_random_values(std::size_t count, F f) -> std::vector +{ + using vector_type = std::vector; + + vector_type v; + v.reserve(count); + for (std::size_t i = 0; i < count; ++i) { + v.emplace_back(f()); + } + return v; +} diff --git a/test/cfoa/exception_insert_tests.cpp b/test/cfoa/exception_insert_tests.cpp new file mode 100644 index 00000000..533243bf --- /dev/null +++ b/test/cfoa/exception_insert_tests.cpp @@ -0,0 +1,566 @@ +// Copyright (C) 2023 Christian Mazakas +// 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 + +#include + +namespace { + test::seed_t initialize_seed(73987); + + struct lvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto const& r : s) { + try { + bool b = x.insert(r); + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + + disable_exceptions(); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_inserter; + + struct norehash_lvalue_inserter_type : public lvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + lvalue_inserter_type::operator()(values, x); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + } + } norehash_lvalue_inserter; + + struct rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + + enable_exceptions(); + + std::atomic num_inserts{0}; + thread_runner(values, [&x, &num_inserts](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert(std::move(r)); + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + + disable_exceptions(); + + if (!std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_inserter; + + struct norehash_rvalue_inserter_type : public rvalue_inserter_type + { + template void operator()(std::vector& values, X& x) + { + x.reserve(values.size()); + + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_EQ(raii::move_constructor, 0u); + + rvalue_inserter_type::operator()(values, x); + + if (std::is_same::value) { + 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()); + } + } + } norehash_rvalue_inserter; + + struct iterator_range_inserter_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + try { + x.insert(s.begin(), s.end()); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } iterator_range_inserter; + + struct lvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(r.first, r.second); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_assign_copy_assign; + + struct lvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + + x.insert_or_assign(r.first, std::move(r.second)); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } lvalue_insert_or_assign_move_assign; + + struct rvalue_insert_or_assign_copy_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(std::move(r.first), r.second); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, x.size()); // rehashing + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } rvalue_insert_or_assign_copy_assign; + + struct rvalue_insert_or_assign_move_assign_type + { + template void operator()(std::vector& values, X& x) + { + enable_exceptions(); + thread_runner(values, [&x](boost::span s) { + for (auto& r : s) { + try { + x.insert_or_assign(std::move(r.first), std::move(r.second)); + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_EQ(raii::copy_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::copy_assignment, 0u); + } + } rvalue_insert_or_assign_move_assign; + + struct lvalue_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_cvisit( + r, [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + BOOST_TEST_EQ(raii::default_constructor, 0u); + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_cvisit; + + struct lvalue_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = + x.insert_or_visit(r, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + + // don't check move construction count here because of rehashing + BOOST_TEST_GT(raii::move_constructor, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } lvalue_insert_or_visit; + + struct rvalue_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_cvisit( + std::move(r), [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + } + } rvalue_insert_or_cvisit; + + struct rvalue_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_inserts{0}; + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_inserts, &num_invokes](boost::span s) { + for (auto& r : s) { + try { + bool b = x.insert_or_visit( + std::move(r), [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + if (b) { + ++num_inserts; + } + } catch (...) { + } + } + }); + disable_exceptions(); + + BOOST_TEST_GT(num_inserts, 0u); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + if (!std::is_same::value) { + BOOST_TEST_EQ(raii::copy_constructor, 0u); + } + } + } rvalue_insert_or_visit; + + struct iterator_range_insert_or_cvisit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_invokes](boost::span s) { + try { + x.insert_or_cvisit(s.begin(), s.end(), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_or_cvisit; + + struct iterator_range_insert_or_visit_type + { + template void operator()(std::vector& values, X& x) + { + std::atomic num_invokes{0}; + + enable_exceptions(); + thread_runner(values, [&x, &num_invokes](boost::span s) { + try { + x.insert_or_visit(s.begin(), s.end(), + [&num_invokes](typename X::value_type const& v) { + (void)v; + ++num_invokes; + }); + } catch (...) { + } + }); + disable_exceptions(); + + BOOST_TEST_EQ(raii::default_constructor, 0u); + BOOST_TEST_GT(raii::move_constructor, 0u); + } + } iterator_range_insert_or_visit; + + template + void insert(X*, G gen, F inserter, test::random_generator rg) + { + disable_exceptions(); + + auto values = make_random_values(1024 * 16, [&] { return gen(rg); }); + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + X x; + + inserter(values, x); + + test_fuzzy_matches_reference(x, reference_map, rg); + } + + BOOST_TEST_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + } + + template void insert_initializer_list(X*) + { + using value_type = typename X::value_type; + + std::initializer_list 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}}, + }; + + std::vector dummy; + + auto reference_map = + boost::unordered_flat_map(values.begin(), values.end()); + raii::reset_counts(); + + { + { + X x; + + thread_runner( + dummy, [&x, &values](boost::span) { x.insert(values); }); + + BOOST_TEST_EQ(x.size(), reference_map.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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + + { + { + std::atomic num_invokes{0}; + + X x; + + thread_runner(dummy, [&x, &values, &num_invokes](boost::span) { + x.insert_or_visit(values, [&num_invokes](typename X::value_type& v) { + (void)v; + ++num_invokes; + }); + + x.insert_or_cvisit( + values, [&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(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_GE(raii::default_constructor, 0u); + BOOST_TEST_GE(raii::copy_constructor, 0u); + BOOST_TEST_GE(raii::move_constructor, 0u); + BOOST_TEST_GT(raii::destructor, 0u); + + BOOST_TEST_EQ(raii::default_constructor + raii::copy_constructor + + raii::move_constructor, + raii::destructor); + + BOOST_TEST_EQ(raii::copy_assignment, 0u); + BOOST_TEST_EQ(raii::move_assignment, 0u); + } + } + + boost::unordered::concurrent_flat_map > >* map; + +} // namespace + +using test::default_generator; +using test::limited_range; +using test::sequential; + +// clang-format off +UNORDERED_TEST( + insert_initializer_list, + ((map))) + +UNORDERED_TEST( + insert, + ((map)) + ((exception_value_type_generator)(exception_init_type_generator)) + ((lvalue_inserter)(rvalue_inserter)(iterator_range_inserter) + (norehash_lvalue_inserter)(norehash_rvalue_inserter) + (lvalue_insert_or_cvisit)(lvalue_insert_or_visit) + (rvalue_insert_or_cvisit)(rvalue_insert_or_visit) + (iterator_range_insert_or_cvisit)(iterator_range_insert_or_visit)) + ((default_generator)(sequential)(limited_range))) + +UNORDERED_TEST( + insert, + ((map)) + ((exception_init_type_generator)) + ((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))) + +// clang-format on + +RUN_TESTS()