// 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 "helpers.hpp" #include test::seed_t initialize_seed{996130204}; using test::default_generator; using test::limited_range; using test::sequential; template struct pocs_allocator { using propagate_on_container_swap = std::true_type; int x_ = -1; using value_type = T; pocs_allocator() = default; pocs_allocator(pocs_allocator const&) = default; pocs_allocator(pocs_allocator&&) = default; pocs_allocator(int const x) : x_{x} {} pocs_allocator& operator=(pocs_allocator const& rhs) { if (this != &rhs) { x_ = rhs.x_; } return *this; } template pocs_allocator(pocs_allocator const& rhs) : x_{rhs.x_} { } T* allocate(std::size_t n) { return static_cast(::operator new(n * sizeof(T))); } void deallocate(T* p, std::size_t) { ::operator delete(p); } bool operator==(pocs_allocator const& rhs) const { return x_ == rhs.x_; } bool operator!=(pocs_allocator const& rhs) const { return x_ != rhs.x_; } friend void swap(pocs_allocator& lhs, pocs_allocator& rhs) noexcept { std::swap(lhs.x_, rhs.x_); } }; using hasher = stateful_hash; using key_equal = stateful_key_equal; using allocator_type = stateful_allocator >; using map_type = boost::unordered::concurrent_flat_map; using map_value_type = typename map_type::value_type; using pocs_allocator_type = pocs_allocator >; using pocs_map_type = boost::unordered::concurrent_flat_map; template struct is_nothrow_member_swappable { static bool const value = noexcept(std::declval().swap(std::declval())); }; BOOST_STATIC_ASSERT(is_nothrow_member_swappable< boost::unordered::concurrent_flat_map, std::equal_to, std::allocator > > >::value); BOOST_STATIC_ASSERT(is_nothrow_member_swappable::value); BOOST_STATIC_ASSERT(!is_nothrow_member_swappable::value); namespace { template void swap_tests(X*, G gen, test::random_generator rg) { using allocator = typename X::allocator_type; bool const pocs = boost::allocator_propagate_on_container_swap::type::value; 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(vals1.begin(), vals1.end()); auto ref_map2 = boost::unordered_flat_map(vals2.begin(), vals2.end()); { raii::reset_counts(); X x1(vals1.begin(), vals1.end(), vals1.size(), hasher(1), key_equal(2), allocator(3)); X x2(vals2.begin(), vals2.end(), vals2.size(), hasher(2), key_equal(1), pocs ? allocator(4) : allocator(3)); if (pocs) { BOOST_TEST(x1.get_allocator() != x2.get_allocator()); } else { BOOST_TEST(x1.get_allocator() == x2.get_allocator()); } auto const old_cc = +raii::copy_constructor; auto const old_mc = +raii::move_constructor; thread_runner(vals1, [&x1, &x2](boost::span s) { (void)s; x1.swap(x2); x2.swap(x1); }); BOOST_TEST_EQ(raii::copy_constructor, old_cc); BOOST_TEST_EQ(raii::move_constructor, old_mc); if (pocs) { if (x1.get_allocator() == allocator(3)) { BOOST_TEST(x2.get_allocator() == allocator(4)); } else { BOOST_TEST(x1.get_allocator() == allocator(4)); BOOST_TEST(x2.get_allocator() == allocator(3)); } } else { BOOST_TEST(x1.get_allocator() == allocator(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); BOOST_TEST_EQ(x1.hash_function(), hasher(1)); BOOST_TEST_EQ(x1.key_eq(), key_equal(2)); 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); BOOST_TEST_EQ(x1.hash_function(), hasher(2)); BOOST_TEST_EQ(x1.key_eq(), key_equal(1)); BOOST_TEST_EQ(x2.hash_function(), hasher(1)); BOOST_TEST_EQ(x2.key_eq(), key_equal(2)); } } check_raii_counts(); } template void insert_and_swap(G gen, test::random_generator rg) { 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)); std::thread t1, t2, t3; boost::latch l(2); std::mutex m; std::condition_variable cv; std::atomic_bool done1{false}, done2{false}; std::atomic num_swaps{0}; t1 = std::thread([&x1, &vals1, &l, &done1, &cv] { l.arrive_and_wait(); for (std::size_t idx = 0; idx < vals1.size(); ++idx) { auto const& val = vals1[idx]; x1.insert(val); if (idx % 100 == 0) { cv.notify_all(); } } done1 = true; }); t2 = std::thread([&x2, &vals2, &l, &done2] { l.arrive_and_wait(); for (auto const& val : vals2) { x2.insert(val); } done2 = true; }); t3 = std::thread([&x1, &x2, &m, &cv, &done1, &done2, &num_swaps] { do { { std::unique_lock lk(m); cv.wait(lk, [] { return true; }); } x1.swap(x2); ++num_swaps; } while (!done1 || !done2); }); t1.join(); t2.join(); t3.join(); BOOST_TEST_GT(num_swaps, 0u); BOOST_TEST_NOT(x1.empty()); BOOST_TEST_NOT(x2.empty()); if (x1.hash_function() == hasher(1)) { BOOST_TEST_EQ(x1.key_eq(), key_equal(2)); BOOST_TEST_EQ(x2.hash_function(), hasher(2)); BOOST_TEST_EQ(x2.key_eq(), key_equal(1)); } else { BOOST_TEST_EQ(x1.hash_function(), hasher(2)); BOOST_TEST_EQ(x1.key_eq(), key_equal(1)); BOOST_TEST_EQ(x2.hash_function(), hasher(1)); BOOST_TEST_EQ(x2.key_eq(), key_equal(2)); } } check_raii_counts(); } map_type* map; pocs_map_type* pocs_map; } // namespace // clang-format off UNORDERED_TEST( swap_tests, ((map)(pocs_map)) ((value_type_generator)) ((default_generator)(sequential)(limited_range))) UNORDERED_TEST(insert_and_swap, ((value_type_generator)) ((default_generator)(sequential)(limited_range))) // clang-format on RUN_TESTS()