From 94608e4cd9295304878800e8562017f7939aa1a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ion=20Gazta=C3=B1aga?= Date: Fri, 5 Jun 2026 23:50:25 +0200 Subject: [PATCH] Add hub tests and example --- example/doc_hub_basic.cpp | 31 + test/hub_utility.hpp | 137 ++++ test/test_hub_api.cpp | 780 +++++++++++++++++++++ test/test_hub_empty_sort.cpp | 32 + test/test_hub_exception_safety.cpp | 824 +++++++++++++++++++++++ test/test_hub_new_extended_alignment.cpp | 124 ++++ test/test_hub_pocxx.cpp | 111 +++ test/test_hub_sort.cpp | 93 +++ test/test_hub_stability.cpp | 252 +++++++ test/test_hub_uses_allocator.cpp | 62 ++ 10 files changed, 2446 insertions(+) create mode 100644 example/doc_hub_basic.cpp create mode 100644 test/hub_utility.hpp create mode 100644 test/test_hub_api.cpp create mode 100644 test/test_hub_empty_sort.cpp create mode 100644 test/test_hub_exception_safety.cpp create mode 100644 test/test_hub_new_extended_alignment.cpp create mode 100644 test/test_hub_pocxx.cpp create mode 100644 test/test_hub_sort.cpp create mode 100644 test/test_hub_stability.cpp create mode 100644 test/test_hub_uses_allocator.cpp diff --git a/example/doc_hub_basic.cpp b/example/doc_hub_basic.cpp new file mode 100644 index 0000000..6cfb544 --- /dev/null +++ b/example/doc_hub_basic.cpp @@ -0,0 +1,31 @@ +/* Basic example of use of boost::container::hub. + * + * Copyright 2026 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 +#ifdef NDEBUG +# undef NDEBUG +#endif +#include + +int main() +{ + boost::container::hub h; + + // Insert some elements and keep an iterator to one of them + for(int i = 0; i < 100; ++i) h.insert(i); + auto it = h.insert(100); + for(int i = 101; i < 200; ++i) h.insert(i); + + // Erase some of the elements + erase_if(h, [](int x) { return x % 2 != 0;}); + assert(*it == 100); // iterator still valid + + // Insert many more elements + for(int i = 200; i < 10000; ++i) h.insert(i); + assert(*it == 100); // iterator still valid +} diff --git a/test/hub_utility.hpp b/test/hub_utility.hpp new file mode 100644 index 0000000..cb627cd --- /dev/null +++ b/test/hub_utility.hpp @@ -0,0 +1,137 @@ +/* Copyright 2025-2026 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_HUB_TEST_UTILITY_HPP +#define BOOST_HUB_TEST_UTILITY_HPP + +#include +#include +#include +#include +#include + +template +std::vector make_range(std::size_t n) +{ + std::vector res; + T i = T(); + while(n--) { + res.push_back(i); + i += T(1); + } + return res; +} + +struct null_callback +{ + template + void operator()(const T&) const {} +}; + +template +void puncture(Container& x, EraseCallback callback = EraseCallback()) +{ + for(auto first = x.begin(); first != x.end(); ) { + if(!(*first % 7)) { + callback(first); + first = x.erase(first); + } + else ++first; + } +} + +template +struct rebind_value_type; + +template< + template class Hub, typename T, typename Allocator, + typename U +> +struct rebind_value_type, U> +{ + using type = Hub>; +}; + +template +using rebind_value_type_t = typename rebind_value_type::type; + +template +struct rebind_allocator; + +template< + template class Hub, typename T, typename Allocator, + typename OtherAllocator +> +struct rebind_allocator, OtherAllocator> +{ + using type = Hub>; +}; + +template +using rebind_allocator_t = + typename rebind_allocator::type; + +template struct reference_or_void { using type = T&; }; +template<> struct reference_or_void { using type = void; }; +template<> struct reference_or_void { using type = const void; }; + +template< + typename T, + typename Propagate = std::false_type, typename AlwaysEqual = std::false_type +> +struct stateful_allocator +{ + using value_type = T; + using propagate_on_container_copy_assignment = Propagate; + using propagate_on_container_move_assignment = Propagate; + using propagate_on_container_swap = Propagate; + using is_always_equal = AlwaysEqual; + + /* typedefs and rebind required by not quite C++11-conformant + * GCC < 5 stdlib. + */ + using pointer = T*; + using const_pointer = const T*; + using void_pointer = void*; + using reference = typename reference_or_void::type; + using const_reference = typename reference_or_void::type; + using const_void_pointer = const void*; + using difference_type = std::ptrdiff_t; + using size_type = std::size_t; + + template + struct rebind + { + using other = stateful_allocator; + }; + + stateful_allocator(int state_ = 0): state{state_} {} + + template + stateful_allocator(const stateful_allocator& x): + state{x.state}, num_allocations{x.num_allocations} {} + + T* allocate(std::size_t n) + { + auto p = static_cast(::operator new(n * sizeof(T))); + ++num_allocations; + return p; + } + + void deallocate(T* p, std::size_t) { ::operator delete(p); } + + bool operator==(const stateful_allocator& x) const + { + return AlwaysEqual::value || (state == x.state); + } + + bool operator!=(const stateful_allocator& x) const { return !(*this == x); } + + int state; + int num_allocations = 0; +}; + +#endif diff --git a/test/test_hub_api.cpp b/test/test_hub_api.cpp new file mode 100644 index 0000000..0b97e1c --- /dev/null +++ b/test/test_hub_api.cpp @@ -0,0 +1,780 @@ +/* Copyright 2025-2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* GCC on Darwin cannot parse the system header + * (xnu_static_assert_struct_size uses Clang-only extensions). + */ +#if defined(__GNUC__) && !defined(__clang__) && defined(__APPLE__) +#define BOOST_CONTAINER_HUB_TEST_API_NO_INTERPROCESS +#endif + +#if !defined(BOOST_CONTAINER_HUB_TEST_API_NO_INTERPROCESS) +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "hub_utility.hpp" + +enum tracked_provenance { ab_ovo = 0, from_copy, from_move }; + +template +struct tracked +{ + tracked(const T& x_): x{x_}, origin{from_copy} {} + tracked(T&& x_): x{std::move(x_)}, origin{from_move} {} + tracked(const tracked& x_): x{x_.x}, origin{x_.origin}, last_op{from_copy} {} + tracked(tracked&& x_): + x{std::move(x_.x)}, origin{x_.origin}, last_op{from_move} {} + + tracked& operator=(const tracked& x_) + { + x = x_.x; + origin = x_.origin; + last_op = from_copy; + return *this; + } + + tracked& operator=(tracked&& x_) + { + x = x_.x; + origin = x_.origin; + last_op = from_move; + return *this; + } + + T x; + tracked_provenance origin, last_op = ab_ovo; +}; + +template +Hub noalloc_construct( + std::true_type, const typename Hub::allocator_type&, Args&&... args) +{ + return Hub(std::forward(args)...); +} + +template +Hub noalloc_construct( + std::false_type, const typename Hub::allocator_type& al, Args&&... args) +{ + return Hub(std::forward(args)..., al); +} + +template +Hub noalloc_construct( + const typename Hub::allocator_type& al, Args&&... args) +{ + return noalloc_construct( + std::is_default_constructible{}, + al, std::forward(args)...); +} + +template +void test_equal(const Container1& x, const Container2& y) +{ + BOOST_TEST_EQ(x.size(), y.size()); + BOOST_TEST(std::equal(x.begin(), x.end(), y.begin())); +} + +template +void test_traversal(Iterator first, Iterator last, const Mirror& data) +{ + std::size_t n = 0; + for(auto it = first; it != last; ++it, ++n) + { + BOOST_TEST(*it == data[n]); + BOOST_TEST((first == it) == (0 == n)); + BOOST_TEST((first != it) == (0 != n)); + + auto it1 = it, it2 = ++it1, it3 = --it2, it4 = it3++, it5 = it3-- ; + BOOST_TEST(it1 == std::next(it)); + BOOST_TEST(it2 == it); + BOOST_TEST(it3 == it); + BOOST_TEST(it4 == it); + BOOST_TEST(it5 == std::next(it)); + } +} + +template +void test_global_erase(const R& rng, const typename Hub::allocator_type& al) +{ + using value_type = typename Hub::value_type; + using size_type = typename Hub::size_type; + + Hub x{al}; + auto even = [](const value_type& v) { return (int)(v) % 2 == 0; }; + const auto& odd_value = *std::find_if_not(rng.begin(), rng.end(), even); + + BOOST_TEST_EQ(erase(x, odd_value), 0); + BOOST_TEST_EQ(x.size(), 0); + BOOST_TEST_EQ(erase_if(x, even), 0); + BOOST_TEST_EQ(x.size(), 0); + + x.insert(rng.begin(), rng.end()); + auto s = x.size(); + auto n = erase(x, odd_value); + BOOST_TEST_EQ( + n, (size_type)std::count(rng.begin(), rng.end(), odd_value)); + BOOST_TEST_EQ(std::count(x.begin(), x.end(), odd_value), 0); + BOOST_TEST_EQ(x.size(), s - n); + s = x.size(); + n = erase_if(x, even); + BOOST_TEST_EQ( + n, (size_type)std::count_if(rng.begin(), rng.end(), even)); + BOOST_TEST_EQ(std::count_if(x.begin(), x.end(), even), 0); + BOOST_TEST_EQ(x.size(), s - n); +} + +template void avoid_unused_local_typedef() {} + +template +void test(const typename Hub::allocator_type& al = {}) +{ + using value_type = typename Hub::value_type; + using allocator_type= typename Hub::allocator_type; + using pointer = typename Hub::pointer; + using const_pointer = typename Hub::const_pointer; + using reference = typename Hub::reference; + using const_reference = typename Hub::const_reference; + using size_type = typename Hub::size_type; + using difference_type = typename Hub::difference_type; + using iterator = typename Hub::iterator; + using const_iterator = typename Hub::const_iterator; + using reverse_iterator = typename Hub::reverse_iterator; + using const_reverse_iterator = typename Hub::const_reverse_iterator; + + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + avoid_unused_local_typedef(); + +#if !defined(BOOST_NO_CXX20_HDR_CONCEPTS) + static_assert(std::bidirectional_iterator); + static_assert(std::bidirectional_iterator); +#endif + + auto rng = make_range(200); + std::initializer_list il{rng[5], rng[1], rng[7]}; + std::vector zeros(70, value_type()); + std::vector repeated(100, rng[10]); + + /* construct/copy/destroy */ + + { + Hub x = noalloc_construct(al), y{al}; + BOOST_TEST(x.empty()); + BOOST_TEST(y.empty()); + } + { + Hub x = noalloc_construct(al, zeros.size()), + y{zeros.size(), al}; + test_equal(x, zeros); + test_equal(y, zeros); + } + { + Hub x = noalloc_construct(al, repeated.size(), repeated.front()), + y{repeated.size(), repeated.front(), al}; + test_equal(x, repeated); + test_equal(y, repeated); + } + { + Hub x = noalloc_construct(al, repeated.size(), repeated[0]), + y{repeated.size(), repeated[0], al}; + test_equal(x, repeated); + test_equal(y, repeated); + } + { + /* [sequence.reqmts/69.1] */ + + using hub2 = rebind_value_type_t; + + hub2 x = noalloc_construct(al, 20u, 20u); + BOOST_TEST_EQ(x.size(), 20u); + } + { + Hub x = noalloc_construct(al, rng.begin(), rng.end()), + y{rng.begin(), rng.end(), al}; + test_equal(x, rng); + test_equal(y, rng); + } +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + { + Hub x = noalloc_construct(al, boost::container::from_range, rng), + y{boost::container::from_range, rng, al}; + test_equal(x, rng); + test_equal(y, rng); + } +#endif + { + const Hub x{rng.begin(), rng.end(), al}; + Hub y{x}, z{y, al}; + test_equal(x, y); + test_equal(x, z); + } + { + Hub x{rng.begin(), rng.end(), al}; + Hub y{std::move(x)}; + BOOST_TEST(x.empty()); + test_equal(y, rng); + + Hub z{std::move(y), al}; + BOOST_TEST(y.empty()); + test_equal(z, rng); + } + { + /* move construction with unequal allocators */ + using hub2 = rebind_allocator_t>; + using allocator_type2 = typename hub2::allocator_type; + + hub2 x{rng.begin(), rng.end(), allocator_type2{0}}, + y{std::move(x), allocator_type2{1}}; + BOOST_TEST_EQ(x.get_allocator().state, 0); + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(y.get_allocator().state, 1); + test_equal(y, rng); + } + { + Hub x = noalloc_construct(al, il), y{il, al}; + test_equal(x, il); + test_equal(y, il); + } + { + Hub x{rng.begin(), rng.end(), al}, y = noalloc_construct(al); + Hub& ry = (y = x); + BOOST_TEST_EQ(&ry, &y); + test_equal(x, y); + } + { + Hub x{rng.begin(), rng.end(), al}, y = noalloc_construct(al); + Hub& ry = (y = std::move(x)); + BOOST_TEST_EQ(&ry, &y); + BOOST_TEST(x.empty()); + test_equal(y, rng); + } + { + Hub x{al}; + Hub& rx = (x = il); + BOOST_TEST_EQ(&rx, &x); + test_equal(x, il); + } + { + Hub x{rng.begin(), rng.begin() + (difference_type)(rng.size() / 2), al}; + puncture(x); + x.assign(rng.begin(), rng.end()); + test_equal(x, rng); + } +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + { + Hub x(zeros.size(), al); + x.assign_range(rng); + test_equal(x, rng); + } +#endif + { + Hub x(zeros.size(), al); + puncture(x); + x.assign(repeated.size(), repeated[0]); + test_equal(x, repeated); + } + { + Hub x(zeros.size(), al); + puncture(x); + x.assign(il); + test_equal(x, il); + } + { + const Hub x{al}; + BOOST_TEST(x.get_allocator() == al); + } + + /* iterators */ + + { + auto data = rng; + Hub x{data.begin(), data.end(), al}; + const Hub& cx=x; + puncture(data); + puncture(x); + + BOOST_TEST(x.rbegin().base() == x.end()); + BOOST_TEST(cx.rbegin().base() == cx.end()); + BOOST_TEST(x.rend().base() == x.begin()); + BOOST_TEST(cx.rend().base() == cx.begin()); + BOOST_TEST(cx.cbegin() == cx.begin()); + BOOST_TEST(cx.cend() == cx.end()); + BOOST_TEST(cx.crbegin() == cx.rbegin()); + BOOST_TEST(cx.crend() == cx.rend()); + + iterator it = x.begin(), it2 = x.end(); + const_iterator cit = it; + BOOST_TEST(cit == it); + cit = it2; + BOOST_TEST(cit == it2); + it = it2; + BOOST_TEST(it == it2); + + test_traversal(x.begin(), x.end(), data); + test_traversal(x.cbegin(), x.cend(), data); + } + { + /* operator-> */ + + rebind_value_type_t> x(al); + x.emplace(18, 42); + BOOST_TEST_EQ(x.begin()->first, 18); + BOOST_TEST_EQ(x.cbegin()->second, 42); + } + + /* capacity */ + + { + Hub x{al}; + const Hub& cx = x; + + x.reserve(1000); + x.insert(rng.begin(), rng.end()); + BOOST_TEST(!cx.empty()); + BOOST_TEST_EQ(cx.size(), rng.size()); + BOOST_TEST_GT(cx.max_size(), 0u); + BOOST_TEST_GE(cx.capacity(), 1000u); + + Hub x2 = x; + x.shrink_to_fit(); + test_equal(x, x2); + BOOST_TEST_EQ(cx.size(), rng.size()); + BOOST_TEST_GE(cx.capacity(), rng.size()); + + auto c = cx.capacity(); + x.reserve(c + 1000); + x.trim_capacity(c + 500); + BOOST_TEST_LT(cx.capacity(), c + 1000); + BOOST_TEST_GE(cx.capacity(), c + 500); + x.trim_capacity(); + BOOST_TEST_EQ(cx.capacity(), c); + test_equal(x, x2); + + if(cx.max_size() < (size_type)(-1)) { + BOOST_TEST_THROWS(x.reserve(cx.max_size() + 1), std::length_error); + } + } + + /* available list partitioned in (non-empty)|(empty) */ + + { + static std::size_t N = 64; /* implementation defined */ + + Hub x{N + 1, value_type(), al}; + x.reserve(10 * N); + for(std::size_t i = 0; i < N; ++i) x.erase(x.begin()); + x.trim_capacity(); + BOOST_TEST_EQ(x.capacity(), N); + + x = Hub{2 * N + 1, value_type(), al}; + x.reserve(10 * N); + x.erase(x.begin(), std::next(x.begin(), (int)(2 * N))); + x.trim_capacity(); + BOOST_TEST_EQ(x.capacity(), N); + + x = Hub{3 * N, value_type(), al}; + x.reserve(10 * N); + auto pos0 = x.begin(), + pos1 = std::next(x.begin(), (int)(N)), + pos2 = std::next(x.begin(), (int)(2 * N)); + x.erase(pos2, std::next(pos2, (int)(N / 2))); + x.erase(pos1, std::next(pos1, (int)(N / 2))); + x.erase(pos0, std::next(pos0, (int)(N / 2))); + x.shrink_to_fit(); + BOOST_TEST_EQ(x.capacity(), 2 * N); + } + + /* modifiers */ + + using tracked_value_type = tracked; + using tracked_hub = rebind_value_type_t; + + { + tracked_hub x{al}; + tracked_value_type v{value_type{}}; + + auto it = x.emplace(v.x); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->origin == from_copy); + + v.x += value_type(1); + it = x.emplace(std::move(v.x)); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->origin == from_move); + + v.x += value_type(1); + it = x.emplace_hint(x.cbegin(), v); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->last_op == from_copy); + + v.x += value_type(1); + it = x.emplace_hint(x.cbegin(), std::move(v)); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->last_op == from_move); + + v.x += value_type(1); + it = x.insert(v); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->last_op == from_copy); + + v.x += value_type(1); + it = x.insert(std::move(v.x)); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->origin == from_move); + + v.x += value_type(1); + it = x.insert(x.cbegin(), v); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->last_op == from_copy); + + v.x += value_type(1); + it = x.insert(x.cbegin(), std::move(v.x)); + BOOST_TEST(it->x == v.x); + BOOST_TEST(it->origin == from_move); + } + { + Hub x{al}; + + x.insert(il); + test_equal(x, il); + } +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + { + Hub x{al}; + x.insert_range(rng); + test_equal(x, rng); + x.insert_range(rng); + BOOST_TEST_EQ(x.size(), 2 * rng.size()); + } +#endif + { + Hub x{al}; + + x.insert(rng.begin(), rng.begin()); + BOOST_TEST(x.empty()); + + x.insert(rng.begin(), rng.end()); + test_equal(x, rng); + } + { + /* boundary conditions in range assignment */ + Hub x{al}; + + x.assign(1, 1); + x.assign(0, 1); + BOOST_TEST_EQ(x.size(), 0); + + x.assign(65, 1); + x.assign(65, 1); + BOOST_TEST_EQ(x.size(), 65); + } + { + Hub x{rng.begin(), rng.end(), al}; + + auto it = x.erase(x.cbegin()); + BOOST_TEST_EQ(x.size(), rng.size() - 1); + BOOST_TEST(*it == rng[1]); + + it = x.erase(x.cend(), x.cend()); + BOOST_TEST_EQ(x.size(), rng.size() - 1); + BOOST_TEST(it == x.cend()); + + it = x.erase(std::prev(x.cend()), std::prev(x.cend())); + BOOST_TEST_EQ(x.size(), rng.size() - 1); + BOOST_TEST(it == std::prev(x.cend())); + + it = x.erase( + std::next(x.cbegin(), (difference_type)(x.size() / 2)), x.cend()); + BOOST_TEST_EQ(x.size(), (difference_type)(rng.size() - 1) / 2); + BOOST_TEST(it == x.cend()); + } + { + Hub x0{rng.begin(), rng.end(), al}, + y0{rng.begin(), rng.begin() + (difference_type)(rng.size() / 2), al}, + x = x0, y = y0; + + x.swap(x); + test_equal(x, x0); + + swap(x, x); + test_equal(x, x0); + + x.swap(y); + test_equal(x, y0); + test_equal(y, x0); + + swap(x, y); + test_equal(x, x0); + test_equal(y, y0); + } + { + Hub x{rng.begin(), rng.end(), al}; + + x.clear(); + BOOST_TEST(x.empty()); + x.clear(); + BOOST_TEST(x.empty()); + } + + /* hive operations */ + + { + Hub x{rng.begin(), rng.end(), al}, y{x}; + + auto it = y.begin(); + y.reserve(y.capacity() + 100); + x.splice(y); + BOOST_TEST_EQ(x.size(), 2 * rng.size()); + BOOST_TEST(y.empty()); + BOOST_TEST_GE(y.capacity(), 100u); + BOOST_TEST(*it == rng[0]); + + y.splice(std::move(x)); + BOOST_TEST(x.empty()); + BOOST_TEST_EQ(y.size(), 2 * rng.size()); + BOOST_TEST(*it == rng[0]); + } + { + Hub x{al}; + for(const auto& v: rng) { + x.insert(v); + x.insert(v); + } + + auto s = x.unique(std::equal_to{}); + BOOST_TEST_EQ(s, rng.size()); + BOOST_TEST_EQ(x.size(), rng.size()); + } + { + Hub x{al}; + x.insert(rng.begin(), rng.end()); + x.insert(rng.begin(), rng.end()); + + x.sort(std::less{}); + BOOST_TEST(std::is_sorted(x.begin(), x.end())); + + x.sort(std::greater{}); + BOOST_TEST(std::is_sorted(x.rbegin(), x.rend())); + } + { + Hub x{rng.begin(), rng.end(), al}, y{x}; + const Hub& cx=x; + + for(auto it = x.cbegin(); it != x.cend(); ++it) { + auto p = boost::pointer_traits::pointer_to(*it); + BOOST_TEST(x.get_iterator(p) == it); + BOOST_TEST(cx.get_iterator(p) == it); + } + } + + test_global_erase(rng, al); + + /* visitation */ + + { + Hub x{rng.begin(), rng.end(), al}; + const Hub& cx=x; + puncture(x); + + unsigned int res = 0; + auto f = [&] (value_type& v) { res += (unsigned int)v;}; + auto cf = [&] (const value_type& v) { res += (unsigned int)v;}; + + for(std::size_t i = 0; i < x.size() / 2; ++i) { + auto first = std::next(x.begin(), (int)i), + last = std::prev(x.end(), (int)i); + auto cfirst = std::next(x.cbegin(), (int)i), + clast = std::prev(x.cend(), (int)i); + + res = 0; + decltype(f) ret1 = boost::container::for_each(first, last, f); + (void)ret1; + auto res1 = res; + res = 0; + decltype(cf) ret2 = boost::container::for_each(cfirst, clast, cf); + (void)ret2; + auto res2 = res; + res = 0; + std::for_each(first, last, f); + auto res3 = res; + BOOST_TEST_EQ(res1, res3); + BOOST_TEST_EQ(res2, res3); + } + + res = 0; + decltype(f) ret1 = for_each(x, f); + (void)ret1; + auto res1 = res; + res = 0; + decltype(cf) ret2 = for_each(cx, cf); + (void)ret2; + auto res2 = res; + res = 0; + std::for_each(x.begin(), x.end(), f); + auto res3 = res; + BOOST_TEST_EQ(res1, res3); + BOOST_TEST_EQ(res2, res3); + } + { + Hub x{rng.begin(), rng.end(), al}; + const Hub& cx=x; + puncture(x); + + unsigned int res = 0; + std::size_t n = 0; + auto f = [&] (value_type& v) { + if(!n--) return false; + res += (unsigned int)v; + return true; + }; + auto cf = [&] (const value_type& v) { + if(!n--) return false; + res += (unsigned int)v; + return true; + }; + + for(std::size_t i = 0; i <= x.size(); ++i) { + auto first = std::next(x.begin(), (int)i); + auto cfirst = std::next(x.cbegin(), (int)i); + + res = 0; + n = (std::size_t)std::distance(first, x.end()) / 2; + std::pair ret1 = + boost::container::for_each_while(first, x.end(), f); + auto it1 = ret1.first; + auto res1 = res; + res = 0; + n = (std::size_t)std::distance(first, x.end()) / 2; + std::pair ret2 = + boost::container::for_each_while(cfirst, cx.end(), cf); + auto it2 = ret2.first; + auto res2 = res; + res = 0; + n = (std::size_t)std::distance(first, x.end()) / 2; + auto it3 = std::find_if_not(first, x.end(), f); + auto res3 = res; + BOOST_TEST(it1 == it3); + BOOST_TEST_EQ(res1, res3); + BOOST_TEST(it2 == it3); + BOOST_TEST_EQ(res2, res3); + } + + res = 0; + n = x.size(); + std::pair ret1 = for_each_while(x, f); + auto it1 = ret1.first; + auto res1 = res; + res = 0; + n = x.size(); + std::pair ret2 = for_each_while(cx, cf); + auto it2 = ret2.first; + auto res2 = res; + res = 0; + n = x.size(); + auto it3 = std::find_if_not(x.begin(), x.end(), f); + auto res3 = res; + BOOST_TEST(it1 == it3); + BOOST_TEST_EQ(res1, res3); + BOOST_TEST(it2 == it3); + BOOST_TEST_EQ(res2, res3); + } +} + +template class Hub> +void test_ctad() +{ +#if !defined(BOOST_NO_CXX17_DEDUCTION_GUIDES) && \ + !BOOST_WORKAROUND(BOOST_CLANG_VERSION, < 90001) + { + std::vector rng({0, 1, 2, 3}); + Hub x1({0, 1, 2, 3}); + Hub x2({0, 1, 2, 3}, std::allocator{}); + Hub x3(rng.begin(), rng.end()); + Hub x4(rng.begin(), rng.end(), std::allocator{}); + + test_equal(x1, rng); + test_equal(x2, rng); + test_equal(x3, rng); + test_equal(x4, rng); + } +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + { + std::vector rng({0, 1, 2, 3}); + Hub x{boost::container::from_range, rng}; + Hub y{boost::container::from_range, rng, std::allocator{}}; + test_equal(x, rng); + test_equal(y, rng); + } +#endif +#endif +} + +int main() +{ + test>(); + test>(); + +#if !defined(BOOST_CONTAINER_HUB_TEST_API_NO_INTERPROCESS) + namespace bip = boost::interprocess; + using segment_manager = bip::managed_shared_memory::segment_manager; + using shared_int_allocator = bip::allocator; + using shared_int_hub = boost::container::hub; + + static auto segment_name_str = + std::string("boost_hub_test_api_shmem_segment") + + to_string(boost::uuids::random_generator()()); + static auto segment_name = segment_name_str.c_str(); + static struct segment_remover { + segment_remover() { bip::shared_memory_object::remove(segment_name); } + ~segment_remover() { bip::shared_memory_object::remove(segment_name); } + } remover; (void)remover; + bip::managed_shared_memory segment( + bip::create_only, segment_name, 64 * 1024); + + test(shared_int_allocator(segment.get_segment_manager())); +#endif + +#if !defined(BOOST_NO_CXX17_HDR_MEMORY_RESOURCE) + test>(); +#endif + + test_ctad(); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_empty_sort.cpp b/test/test_hub_empty_sort.cpp new file mode 100644 index 0000000..94a56dd --- /dev/null +++ b/test/test_hub_empty_sort.cpp @@ -0,0 +1,32 @@ +/* Copyright 2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include + + +int main() +{ + /* Warning reported in + * https://lists.boost.org/archives/list/boost@lists.boost.org/ + * message/ZTL2D2UMIHAAC6ZLHLH4Y3XETTQXAHBZ/ . + */ + boost::container::hub h{}; + h.sort(); + BOOST_TEST(h.empty()); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_exception_safety.cpp b/test/test_hub_exception_safety.cpp new file mode 100644 index 0000000..0285697 --- /dev/null +++ b/test/test_hub_exception_safety.cpp @@ -0,0 +1,824 @@ +/* Copyright 2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hub_utility.hpp" + +template +void check_valid(const boost::container::hub& h) +{ + BOOST_TEST_GE(h.capacity(), h.size()); + BOOST_TEST_EQ((std::size_t)std::distance(h.begin(), h.end()), h.size()); + for(const auto& x: h) check_valid(x); +} + +template +void check_equal(const Container1& x, const Container2& y) +{ + auto first1 = x.begin(), last1 = x.end(); + auto first2 = y.begin(), last2 = y.end(); + while(first1 != last1 && first2 != last2) { + BOOST_TEST(*first1++ == *first2++); + } + BOOST_TEST(first1 == last1); + BOOST_TEST(first2 == last2); +} + +template +void fill_till_capacity(boost::container::hub& h) +{ + using value_type = typename boost::container::hub::value_type; + + while(h.size() < h.capacity()) h.insert(value_type{0}); +} + +template +void test_basic_exception_safety(ThrowingHub& h, F f) +{ + try { + f(); + BOOST_ERROR("Expected exception was not thrown"); + ThrowingHub::value_type::countdown_to_throw(0); + ThrowingHub::allocator_type::countdown_to_throw(0); + } + catch(...) { + check_valid(h); + } +} + +template +void test_basic_exception_safety(ThrowingHub& h, F f, Fs... fs) +{ + test_basic_exception_safety(h, f); + test_basic_exception_safety(h, fs...); +} + +template +void test_strong_exception_safety(ThrowingHub& h, F f) +{ + auto c = h.capacity(); + std::vector backup{h.begin(), h.end()}; + try { + f(); + BOOST_ERROR("Expected exception was not thrown"); + ThrowingHub::value_type::countdown_to_throw(0); + ThrowingHub::allocator_type::countdown_to_throw(0); + } + catch(...) { + check_valid(h); + BOOST_TEST_EQ(h.capacity(), c); + check_equal(h, backup); + } +} + +template +void test_strong_exception_safety(ThrowingHub& h, F f, Fs... fs) +{ + test_strong_exception_safety(h, f); + test_strong_exception_safety(h, fs...); +} + +/* Sentinel exception used for fault injection. It deliberately carries no + * heap-allocated message: std::runtime_error stores its message in a + * refcounted buffer allocated with operator new but, on some libc++/libc++abi + * combinations, freed with free() in the destructor, which AddressSanitizer + * flags as an alloc-dealloc-mismatch when the caught exception is destroyed. + * An empty type sidesteps that entirely. + */ +struct injected_exception {}; + +struct throwing_int +{ + throwing_int(int n_ = 0) { maybe_throw(); n = n_; } + throwing_int(const throwing_int& x) { maybe_throw(); n = x.n; } + throwing_int& operator=(const throwing_int& x) + { maybe_throw(); n = x.n; return *this; } + ~throwing_int() { n = INT_MIN; } + + throwing_int& operator+=(int m) { n += m; return *this; } + + operator int() const { return n; } + bool operator==(int n_) const { return n == n_; } + bool operator<(int n_) const { return n < n_; } + + static void countdown_to_throw(int n) { countdown = n; } + + struct no_dangling_objects_guard + { + ~no_dangling_objects_guard() + { + BOOST_TEST_EQ(outstanding_objects, n); + } + + std::size_t n; + }; + + static no_dangling_objects_guard check_no_dangling_objects_on_exit() + { + return {outstanding_objects}; + } + +private: + static int countdown; + static std::size_t outstanding_objects; + + static void maybe_throw() + { + if(countdown && !--countdown) throw injected_exception{}; + } + + friend void check_valid(const throwing_int& x) + { + BOOST_TEST_NE(x.n, INT_MIN); + } + + int n = INT_MIN; +}; + +int throwing_int::countdown = 0; +std::size_t throwing_int::outstanding_objects = 0; + +template +struct make_bigger: Class +{ + make_bigger(const Class& x): Class{x} {} + + unsigned char extra_space[ExtraSpace] = {}; +}; + +int throwing_allocator_countdown = 0; +std::size_t throwing_allocator_outstanding_allocations = 0; + +struct throwing_allocator_no_leaks_guard +{ + ~throwing_allocator_no_leaks_guard() + { + BOOST_TEST_EQ(throwing_allocator_outstanding_allocations, n); + } + + std::size_t n; +}; + +template< + typename T, + typename Propagate = std::false_type, typename AlwaysEqual = std::false_type +> +struct throwing_allocator +{ + using value_type = T; + using propagate_on_container_copy_assignment = Propagate; + using propagate_on_container_move_assignment = Propagate; + using propagate_on_container_swap = Propagate; + using is_always_equal = AlwaysEqual; + + template + struct rebind + { + using other = throwing_allocator; + }; + + throwing_allocator(int state_ = 0): state{state_} {} + + template + throwing_allocator(const throwing_allocator& x): + state{x.state} {} + + T* allocate(std::size_t n) + { + maybe_throw(); + auto p = static_cast(::operator new(n * sizeof(T))); + ++throwing_allocator_outstanding_allocations; + return p; + } + + void deallocate(T* p, std::size_t) + { + --throwing_allocator_outstanding_allocations; + ::operator delete(p); + } + + bool operator==(const throwing_allocator& x) const + { + return AlwaysEqual::value || (state == x.state); + } + + bool operator!=(const throwing_allocator& x) const { return !(*this == x); } + + static void countdown_to_throw(int n) { throwing_allocator_countdown = n; } + + static throwing_allocator_no_leaks_guard check_no_leaks_on_exit() + { + return {throwing_allocator_outstanding_allocations}; + } + + int state; + +private: + static void maybe_throw() + { + if(throwing_allocator_countdown && !--throwing_allocator_countdown) { + throw injected_exception{}; + } + } +}; + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4127) /* conditional expression is constant */ +#endif + +#if BOOST_WORKAROUND(BOOST_GCC, >= 60000 && BOOST_GCC < 80000) +/* https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80947 */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +#endif + +template +void test_allocator_ops(const Data& original_hubs) +{ + using hub = rebind_allocator_t< + Hub, throwing_allocator>; + using value_type = typename hub::value_type; + using allocator_type = typename hub::allocator_type; + + std::vector hubs; + for(const auto& oh: original_hubs) { + if(!oh.empty()) hubs.emplace_back(oh.begin(), oh.end()); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << "Allocator propagate: " << Propagate::value + << ", always_equal: " << AlwaysEqual::value << "\n"; + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << " Copy/move ctors, value_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + value_type::countdown_to_throw((int)(ch.size() / 2)); + BOOST_TEST_THROWS((void)hub(ch), injected_exception); + + if(!AlwaysEqual::value) { + auto h = ch; + test_basic_exception_safety(h, + [&h] { + value_type::countdown_to_throw((int)(h.size() / 2)); + (void)hub{std::move(h), allocator_type{1}}; + }); + } + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << + " Copy/move ctors, allocator_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + allocator_type::countdown_to_throw((int)(ch.size() / 64 * 2) + 1); + BOOST_TEST_THROWS((void)hub(ch), injected_exception); + allocator_type::countdown_to_throw((int)(ch.size() / 64 * 2) + 2); + BOOST_TEST_THROWS((void)hub(ch), injected_exception); + + if(!AlwaysEqual::value) { + auto h = ch; + test_basic_exception_safety(h, + [&h] { + allocator_type::countdown_to_throw((int)(h.size() / 64 * 2) + 1); + (void)hub{std::move(h), allocator_type{1}}; + }); + + h = ch; + test_basic_exception_safety(h, + [&h] { + allocator_type::countdown_to_throw((int)(h.size() / 64 * 2) + 2); + (void)hub{std::move(h), allocator_type{1}}; + }); + } + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << + " Copy/move assignment, value_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + hub dst0{allocator_type{1}}; + test_basic_exception_safety(dst0, + [&dst0, &ch] { + value_type::countdown_to_throw((int)(ch.size() / 2)); + dst0 = ch; + }); + + if(!AlwaysEqual::value && !Propagate::value) { + hub src1{ch}, dst1{allocator_type{1}}; + test_basic_exception_safety(src1, + [&src1, &dst1] { + test_basic_exception_safety(dst1, + [&src1, &dst1] { + value_type::countdown_to_throw((int)(src1.size() / 2)); + dst1 = std::move(src1); + }); + throw injected_exception{}; + }); + } + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << + " Copy/move assignment, allocator_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + hub dst0{allocator_type{1}}; + test_basic_exception_safety(dst0, + [&dst0, &ch] { + allocator_type::countdown_to_throw((int)(ch.size() / 64 * 2) + 1); + dst0 = ch; + }); + + hub dst1{allocator_type{1}}; + test_basic_exception_safety(dst1, + [&dst1, &ch] { + allocator_type::countdown_to_throw((int)(ch.size() / 64 * 2) + 2); + dst1 = ch; + }); + + if(!AlwaysEqual::value && !Propagate::value) { + hub src2{ch}, dst2{allocator_type{1}}; + test_basic_exception_safety(src2, + [&src2, &dst2] { + test_basic_exception_safety(dst2, + [&src2, &dst2] { + allocator_type::countdown_to_throw( + (int)(src2.size() / 64 * 2) + 1); + dst2 = std::move(src2); + }); + throw injected_exception{}; + }); + + hub src3{ch}, dst3{allocator_type{1}}; + test_basic_exception_safety(src3, + [&src3, &dst3] { + test_basic_exception_safety(dst3, + [&src3, &dst3] { + allocator_type::countdown_to_throw( + (int)(src3.size() / 64 * 2) + 2); + dst3 = std::move(src3); + }); + throw injected_exception{}; + }); + } + } +} + +#if BOOST_WORKAROUND(BOOST_GCC, >= 60000 && BOOST_GCC < 80000) +#pragma GCC diagnostic pop +#endif + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4127 */ +#endif + +template +void test() +{ + using value_type = typename Hub::value_type; + using allocator_type = typename Hub::allocator_type; + + std::vector hubs; + hubs.emplace_back(); + hubs.emplace_back(Hub{0, 2, 1}); + hubs.emplace_back(Hub{64, 5}); /* capacity() - size() == 0 */ + hubs.emplace_back([] { + Hub h; + for(int i = 0; i < 1000; ++i) h.insert(-i); + puncture(h); + return h; + }()); + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << + "Non copy/move ctors and assignment, value_type throws\n"; + { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + value_type::countdown_to_throw(100); + BOOST_TEST_THROWS((void)Hub(200), injected_exception); + + value_type::countdown_to_throw(100); + BOOST_TEST_THROWS((void)Hub(200, value_type{42}), injected_exception); + + auto rng = make_range(200); + value_type::countdown_to_throw(100); + BOOST_TEST_THROWS((void)Hub(rng.begin(), rng.end()), injected_exception); + +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + value_type::countdown_to_throw(100); + BOOST_TEST_THROWS( + (void)Hub(boost::container::from_range, rng), injected_exception); +#endif + + std::initializer_list il = {0, 1, 2, 3}; + value_type::countdown_to_throw(2); + BOOST_TEST_THROWS((void)Hub(il), injected_exception); + + Hub h0; + test_basic_exception_safety(h0, + [&h0]{ + std::initializer_list il0 = {0, 1, 2, 3}; + value_type::countdown_to_throw(2); + h0 = il0; + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << + "Non copy/move ctors and assignment, allocator_type throws\n"; + { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + allocator_type::countdown_to_throw(3); + BOOST_TEST_THROWS((void)Hub(200), injected_exception); + allocator_type::countdown_to_throw(4); + BOOST_TEST_THROWS((void)Hub(200), injected_exception); + + allocator_type::countdown_to_throw(3); + BOOST_TEST_THROWS((void)Hub(200, value_type{42}), injected_exception); + allocator_type::countdown_to_throw(4); + BOOST_TEST_THROWS((void)Hub(200, value_type{42}), injected_exception); + + auto rng = make_range(200); + allocator_type::countdown_to_throw(3); + BOOST_TEST_THROWS((void)Hub(rng.begin(), rng.end()), injected_exception); + allocator_type::countdown_to_throw(4); + BOOST_TEST_THROWS((void)Hub(rng.begin(), rng.end()), injected_exception); + +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + allocator_type::countdown_to_throw(3); + BOOST_TEST_THROWS( + (void)Hub(boost::container::from_range, rng), injected_exception); + allocator_type::countdown_to_throw(4); + BOOST_TEST_THROWS( + (void)Hub(boost::container::from_range, rng), injected_exception); +#endif + + std::initializer_list il = {0, 1, 2, 3}; + allocator_type::countdown_to_throw(1); + BOOST_TEST_THROWS((void)Hub(il), injected_exception); + allocator_type::countdown_to_throw(2); + BOOST_TEST_THROWS((void)Hub(il), injected_exception); + + Hub h0; + test_basic_exception_safety(h0, + [&h0]{ + std::initializer_list il0 = {0, 1, 2, 3}; + allocator_type::countdown_to_throw(1); + h0 = il0; + }); + + Hub h1; + test_basic_exception_safety(h1, + [&h1]{ + std::initializer_list il1 = {0, 1, 2, 3}; + allocator_type::countdown_to_throw(2); + h1 = il1; + }); + } + + test_allocator_ops(hubs); + test_allocator_ops(hubs); + test_allocator_ops(hubs); + test_allocator_ops(hubs); + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "assign[_range], value_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + + test_basic_exception_safety(h, + [&h] { + auto rng = make_range(200); + value_type::countdown_to_throw(100); + h.assign(rng.begin(), rng.end()); + }, +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + [&h] { + auto rng = make_range(200); + value_type::countdown_to_throw(100); + h.assign_range(rng); + }, +#endif + [&h] { + std::initializer_list il = {0, 1, 2, 3}; + value_type::countdown_to_throw(2); + h.assign(il); + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "assign[_range], allocator_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + + test_basic_exception_safety(h, + [&h] { + auto rng = make_range(h.capacity() + 200); + allocator_type::countdown_to_throw(3); + h.assign(rng.begin(), rng.end()); + }, + [&h] { + auto rng = make_range(h.capacity() + 200); + allocator_type::countdown_to_throw(4); + h.assign(rng.begin(), rng.end()); + }, +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + [&h] { + auto rng = make_range(h.capacity() + 200); + allocator_type::countdown_to_throw(3); + h.assign_range(rng); + }, + [&h] { + auto rng = make_range(h.capacity() + 200); + allocator_type::countdown_to_throw(4); + h.assign_range(rng); + }, +#endif + [&h] { + h.clear(); + h.shrink_to_fit(); + std::initializer_list il = {0, 1, 2, 3}; + allocator_type::countdown_to_throw(1); + h.assign(il); + }, + [&h] { + h.clear(); + h.shrink_to_fit(); + std::initializer_list il = {0, 1, 2, 3}; + allocator_type::countdown_to_throw(2); + h.assign(il); + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "reserve, allocator_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + + test_basic_exception_safety(h, + [&h] { + allocator_type::countdown_to_throw(3); + h.reserve(h.capacity() + 200); + }, + [&h] { + allocator_type::countdown_to_throw(4); + h.reserve(h.capacity() + 200); + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "shrink_to_fit, value_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + + test_basic_exception_safety(h, [&h] { + value_type::countdown_to_throw(10); + h.shrink_to_fit(); /* may not throw depending on h */ + value_type::countdown_to_throw(0); + throw injected_exception{}; + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "emplace/insert, value_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + + test_strong_exception_safety(h, + [&h] { + value_type::countdown_to_throw(1); + h.emplace(3); + }, + [&h] { + value_type::countdown_to_throw(1); + h.emplace_hint(h.end(), 3); + }, + [&h] { + value_type::countdown_to_throw(2); + h.insert(3); + }, + [&h] { + value_type::countdown_to_throw(2); + auto x = value_type{3}; + h.insert(std::move(x)); + }, + [&h] { + value_type::countdown_to_throw(2); + h.insert(h.begin(), 3); + }); + + test_basic_exception_safety(h, + [&h] { + value_type::countdown_to_throw(2); + auto x = value_type{3}; + h.insert(h.begin(), std::move(x)); + }, + [&h] { + std::initializer_list il = {0, 1, 2}; + value_type::countdown_to_throw(2); + h.insert(il); + }, +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + [&h] { + std::vector v = {0, 1, 2}; + value_type::countdown_to_throw(2); + h.insert_range(v); + }, +#endif + [&h] { + std::vector v = {0, 1, 2}; + value_type::countdown_to_throw(2); + h.insert(v.begin(), v.end()); + }, + [&h] { + value_type::countdown_to_throw(50); + h.insert(100, value_type{42}); + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "emplace/insert, allocator_type throws\n"; + for(const auto& ch: hubs) { + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + auto h = ch; + fill_till_capacity(h); + + test_strong_exception_safety(h, + [&h] { + allocator_type::countdown_to_throw(1); + h.emplace(3); + }, + [&h] { + allocator_type::countdown_to_throw(2); + h.emplace(3); + }, + [&h] { + allocator_type::countdown_to_throw(1); + h.emplace_hint(h.end(), 3); + }, + [&h] { + allocator_type::countdown_to_throw(2); + h.emplace_hint(h.end(), 3); + }, + [&h] { + allocator_type::countdown_to_throw(1); + h.insert(3); + }, + [&h] { + allocator_type::countdown_to_throw(2); + h.insert(3); + }, + [&h] { + allocator_type::countdown_to_throw(1); + auto x = value_type{3}; + h.insert(std::move(x)); + }, + [&h] { + allocator_type::countdown_to_throw(2); + auto x = value_type{3}; + h.insert(std::move(x)); + }, + [&h] { + allocator_type::countdown_to_throw(1); + h.insert(h.begin(), 3); + }, + [&h] { + allocator_type::countdown_to_throw(2); + h.insert(h.begin(), 3); + }, + [&h] { + allocator_type::countdown_to_throw(1); + auto x = value_type{3}; + h.insert(h.begin(), std::move(x)); + }, + [&h] { + allocator_type::countdown_to_throw(2); + auto x = value_type{3}; + h.insert(h.begin(), std::move(x)); + }); + + test_basic_exception_safety(h, + [&h] { + std::initializer_list il = {0, 1, 2}; + allocator_type::countdown_to_throw(1); + h.insert(il); + }, + [&h] { + std::initializer_list il = {0, 1, 2}; + allocator_type::countdown_to_throw(2); + h.insert(il); + }, +#if !defined(BOOST_CONTAINER_HUB_NO_RANGES) + [&h] { + std::vector v = {0, 1, 2}; + allocator_type::countdown_to_throw(1); + h.insert_range(v); + }, + [&h] { + std::vector v = {0, 1, 2}; + allocator_type::countdown_to_throw(2); + h.insert_range(v); + }, +#endif + [&h] { + std::vector v = {0, 1, 2}; + allocator_type::countdown_to_throw(1); + h.insert(v.begin(), v.end()); + }, + [&h] { + std::vector v = {0, 1, 2}; + allocator_type::countdown_to_throw(2); + h.insert(v.begin(), v.end()); + }, + [&h] { + allocator_type::countdown_to_throw(3); + h.insert(100, value_type{42}); + }, + [&h] { + allocator_type::countdown_to_throw(4); + h.insert(100, value_type{42}); + }); + } + + BOOST_LIGHTWEIGHT_TEST_OSTREAM << "sort, value_type throws\n"; + for(const auto& ch: hubs) { + if(std::is_sorted(ch.begin(), ch.end())) continue; + auto guard0 = allocator_type::check_no_leaks_on_exit(); + auto guard1 = value_type::check_no_dangling_objects_on_exit(); + + /* transfer_sort */ + auto h0 = ch; + test_basic_exception_safety(h0, + [&h0] { + value_type::countdown_to_throw((int)(h0.size()/2)); + h0.sort(); + }); + + /* proxy_sort */ + using bigger_element_hub = + rebind_value_type_t>; + + bigger_element_hub h1{ch.begin(), ch.end()}; + test_basic_exception_safety(h1, + [&h1] { + value_type::countdown_to_throw(1); + h1.sort(); + }); + + /* compact_sort */ + bigger_element_hub h2; + while(h2.size() < 2 * 1024 * 1024 / sizeof(void*)) { + h2.insert(ch.begin(), ch.end()); + } + test_basic_exception_safety(h2, + [&h2] { + value_type::countdown_to_throw((int)(h2.size() / 2)); + h2.sort(); + }); + } +} + +int main() +{ + test< + boost::container::hub>>(); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_new_extended_alignment.cpp b/test/test_hub_new_extended_alignment.cpp new file mode 100644 index 0000000..dfc8ccf --- /dev/null +++ b/test/test_hub_new_extended_alignment.cpp @@ -0,0 +1,124 @@ +/* Copyright 2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#if !defined(__cpp_aligned_new) || __cpp_aligned_new < 201606L +#include + +BOOST_PRAGMA_MESSAGE("Test skipped because aligned new is not available.") + +int main() +{ +} +#else +#include +#include +#include +#include +#include + +template +struct aligned_new_allocator +{ + using value_type = T; + + aligned_new_allocator() = default; + template + aligned_new_allocator(const aligned_new_allocator&) {} + + T* allocate(std::size_t n) + { + return static_cast( + ::operator new(n * sizeof(T), std::align_val_t{alignof(T)})); + } + + void deallocate(T* p, std::size_t) + { + ::operator delete(p, std::align_val_t{alignof(T)}); + } + + bool operator==(const aligned_new_allocator&) const { return true; } + bool operator!=(const aligned_new_allocator&) const { return false; } +}; + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4324) /* structure padded due to alignment specifier */ +#endif + +struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2) +new_extended_aligned_object +{ + new_extended_aligned_object(int n_): n{n_} { check_alignment(); } + new_extended_aligned_object(const new_extended_aligned_object& x): + n{x.n} { check_alignment(); } + + new_extended_aligned_object& operator=(const new_extended_aligned_object&) + = default; + + void check_alignment() + { + constexpr auto mask = + static_cast(alignof(new_extended_aligned_object) - 1); + + BOOST_TEST_EQ(reinterpret_cast(this) & mask, 0); + } + + bool operator<(const new_extended_aligned_object& x) const + { return n < x.n; } + + int n; +}; + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4324 */ +#endif + +using new_extended_alignment_hub = boost::container::hub< + new_extended_aligned_object, + aligned_new_allocator>; + +/* The only internal sort function of hub that allocates auxiliary memory + * for T is transfer_sort: this function, however, is never called when T has + * new-extended alignment because in this case sizeof(T) exceeds the limit from + * which other sort functions are selected. + * For robustness, we test transfer_sort even in this impossible situation by + * illegally forcing its use through the famous http://www.gotw.ca/gotw/076.htm + * trick. + */ + +namespace boost { +namespace container { + +template<> +template<> +void new_extended_alignment_hub::sort( + std::less cmp) +{ + transfer_sort(cmp); +} + +} /* namespace container */ +} /* namespace boost */ + +int main() +{ + new_extended_alignment_hub h; + for(int i = 5 ; i >= 0; --i) h.emplace(i); + h.sort(); + + return boost::report_errors(); +} +#endif + +#endif diff --git a/test/test_hub_pocxx.cpp b/test/test_hub_pocxx.cpp new file mode 100644 index 0000000..81d7964 --- /dev/null +++ b/test/test_hub_pocxx.cpp @@ -0,0 +1,111 @@ +/* Copyright 2025-2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include +#include "hub_utility.hpp" + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4127) /* conditional expression is constant */ +#endif + +template +void test() +{ + using hub = rebind_allocator_t< + Hub, stateful_allocator>; + using value_type = typename hub::value_type; + using allocator_type = typename hub::allocator_type; + static constexpr auto pocca = + boost::allocator_propagate_on_container_copy_assignment_t< + allocator_type>::value; + static constexpr auto pocma = + boost::allocator_propagate_on_container_move_assignment_t< + allocator_type>::value; + static constexpr auto pocs = + boost::allocator_propagate_on_container_swap_t::value; + + auto rng = make_range(200), + long_rng = make_range(400); + allocator_type al0{0}, al1{1}; + + { + hub x(long_rng.begin(), long_rng.end(), al0), + y(rng.begin(), rng.end(), al1); + auto nx = x.get_allocator().num_allocations, + ny = y.get_allocator().num_allocations; + + x = y; + BOOST_TEST(x.get_allocator().state == (pocca? al1: al0).state); + auto nx1 = x.get_allocator().num_allocations; + BOOST_TEST( + pocca && al0 == al1? nx1 == ny: + pocca && al0 != al1? nx1 > ny : + /* !pocca */ nx1 == nx); + } + { + hub x(rng.begin(), rng.end(), al0), + y(long_rng.begin(), long_rng.end(), al1); + auto nx = x.get_allocator().num_allocations, + ny = y.get_allocator().num_allocations; + + x = std::move(y); + BOOST_TEST(x.get_allocator().state == (pocma? al1: al0).state); + auto nx1 = x.get_allocator().num_allocations; + BOOST_TEST( + !pocma && al0 == al1? nx1 == nx: + !pocma && al0 != al1? nx1 > nx: + /* pocma */ nx1 == ny); + } + if(pocs || al0 == al1) { + hub x(rng.begin(), rng.end(), al0), + y(long_rng.begin(), long_rng.end(), al1); + auto nx = x.get_allocator().num_allocations, + ny = y.get_allocator().num_allocations; + + x.swap(y); + BOOST_TEST(x.get_allocator().state == (pocs? al1: al0).state); + BOOST_TEST(y.get_allocator().state == (pocs? al0: al1).state); + auto nx1 = x.get_allocator().num_allocations; + auto ny1 = y.get_allocator().num_allocations; + BOOST_TEST(pocs? nx1 == ny: nx1 == nx); + BOOST_TEST(pocs? ny1 == nx: ny1 == ny); + } +} + +#if defined(BOOST_MSVC) +#pragma warning(pop) /* C4127 */ +#endif + +template +void test() +{ + test(); + test(); + test(); + test(); +} + +int main() +{ + test>(); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_sort.cpp b/test/test_hub_sort.cpp new file mode 100644 index 0000000..4c5e103 --- /dev/null +++ b/test/test_hub_sort.cpp @@ -0,0 +1,93 @@ +/* Copyright 2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include +#include + +struct big_nontrivial_int +{ + big_nontrivial_int(int n_ = 0): n{n_} {} + big_nontrivial_int(const big_nontrivial_int& x): n{x.n} {} + ~big_nontrivial_int() {} + + big_nontrivial_int& operator=(const big_nontrivial_int& x) + { + n = x.n; + return *this; + } + + operator int() const { return n; } + + int n; + unsigned char stuff[2 * sizeof(void*)]; +}; + +static_assert( + !std::is_trivially_destructible::value +#if !BOOST_WORKAROUND(BOOST_LIBSTDCXX_VERSION, < 50000) + && !std::is_trivially_copy_constructible::value + && !std::is_trivially_assignable< + big_nontrivial_int, big_nontrivial_int>::value +#endif + , + "internal check on big_nontrivial_int"); + +template> +void test(std::size_t n, double erase_rate, Compare comp = Compare()) +{ + using value_type = typename Hub::value_type; + + std::size_t m = (std::size_t)((double)n / (1.0 - erase_rate)); + std::uint64_t erase_cut = + (std::uint64_t)(erase_rate * (double)(std::uint64_t)(-1)); + Hub h; + + boost::detail::splitmix64 rng; + for(std::size_t i = 0; i < m; ++i) h.insert((int)rng()); + erase_if(h, [&](value_type&) { return rng() < erase_cut; }); + h.sort(comp); + BOOST_TEST(std::is_sorted(h.begin(), h.end(), comp)); +} + +template +void test() +{ + using value_type = typename Hub::value_type; + constexpr std::size_t small_n = 1000, + large_n = 300000; /* enough to trigger compact_sort */ + + test(0, 0.0); + test(1, 0.0); + + for(double erase_rate: {0.0, 0.00001, 0.2, 0.8}) { + test(small_n, erase_rate); + test(large_n, erase_rate); + test(small_n, erase_rate, std::greater{}); + test(large_n, erase_rate, std::greater{}); + } +} + +int main() +{ + test>(); + test>(); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_stability.cpp b/test/test_hub_stability.cpp new file mode 100644 index 0000000..193684f --- /dev/null +++ b/test/test_hub_stability.cpp @@ -0,0 +1,252 @@ +/* Copyright 2025-2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "hub_utility.hpp" + +struct tidy_int +{ + tidy_int(int n_ = 0): n{n_} {} + ~tidy_int() { n = 0x0BADBEEF; } + + operator int() const { return n; } + + tidy_int& operator+=(const tidy_int& x) + { + n += x.n; + return *this; + } + + int n; +}; + +template +using erase_callback = std::function; + +template +struct track_info +{ + typename Hub::iterator it; + typename Hub::pointer pointer; + typename Hub::value_type value; + + bool valid() const + { + return std::addressof(*it) == pointer && *it == value; + } +}; + +template +using track_info_vector = std::vector>; + +template +void call_optionally_with_impl(F& f, Arg, ...) { f(); } + +template< + typename F, typename Arg, + typename = typename std::enable_if< + sizeof(std::declval()(std::declval()), 0) != 0 + >::type +> +void call_optionally_with_impl(F& f, Arg arg, int) { f(arg); } + +template +void call_optionally_with(F& f, Arg arg) +{ + call_optionally_with_impl(f, arg, 0); +} + +template +void save_track_info(Hub& x, track_info_vector& track) +{ + for(auto it = x.begin(); it != x.end(); ++it) { + track.push_back(track_info{it, std::addressof(*it), *it}); + } +} + +#if BOOST_WORKAROUND(BOOST_GCC, < 70000) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wattributes" +#endif + +template +bool check_stability(F f, track_info_vector&& track = {}) +{ + using iterator = typename Hub::iterator; + + call_optionally_with( + f, + erase_callback{[&] (iterator it) { + track.erase(std::find_if( + track.begin(), track.end(), + [&] (const track_info& info) { return info.it == it; })); + }}); + for(const auto& info: track) if(!info.valid()) return false; + return true; +} + +#if BOOST_WORKAROUND(BOOST_GCC, < 70000) +#pragma GCC diagnostic pop +#endif + +template +bool check_stability(Hub& x, F f, track_info_vector&& track = {}) +{ + auto last = x.end(); + save_track_info(x, track); + if(!check_stability(f, std::move(track))) return false; + return (x.end() == last); +} + +template +bool check_stability(Hub& x, Hub& y, F f, track_info_vector&& track = {}) +{ + auto last = x.end(); + save_track_info(x, track); + if(!check_stability(y, f, std::move(track))) return false; + return (x.end() == last); +} + +template +void test() +{ + using value_type = typename Hub::value_type; + using difference_type = typename Hub::difference_type; + using erase_callback = ::erase_callback; + + auto rng = make_range(200); + + { + Hub x(rng.begin(), rng.end()); + BOOST_TEST(check_stability(x, [&] (erase_callback callback) { + puncture(x, callback); + x.insert(rng.begin(), rng.end()); + })); + } + { + Hub x(rng.begin(), rng.end()); + std::shared_ptr py; + BOOST_TEST(check_stability(x, [&] { + py = std::make_shared(std::move(x)); + })); + } + { + Hub x(rng.begin(), rng.end()), y; + BOOST_TEST(check_stability(x, y, [&] { + y = std::move(x); + })); + } + { + Hub x(rng.begin(), rng.end()); + BOOST_TEST(check_stability(x, [&] { + x.reserve(x.capacity() + 100); + })); + } + { + Hub x(rng.begin(), rng.end()); + puncture(x); + x.shrink_to_fit(); + BOOST_TEST(check_stability(x, [&] { + x.shrink_to_fit(); + })); + } + { + Hub x(rng.begin(), rng.end()); + puncture(x); + auto c = x.capacity(); + x.reserve(c + 1000); + BOOST_TEST(check_stability(x, [&] { + x.trim_capacity(c+500); + })); + BOOST_TEST(check_stability(x, [&] { + x.trim_capacity(); + })); + } + { + Hub x(rng.begin(), rng.end()); + BOOST_TEST(check_stability(x, [&] (erase_callback callback) { + callback(x.begin()); + x.erase(x.begin()); + })); + } + { + Hub x(rng.begin(), rng.end()); + BOOST_TEST(check_stability(x, [&] (erase_callback callback) { + auto first = std::next(x.begin(), (difference_type)(x.size() / 3)), + last = std::next(x.begin(), (difference_type)(x.size() * 2 / 3)); + for(auto it = first; it != last; ++it) callback(it); + x.erase(first,last); + })); + } + { + Hub x(rng.begin(), rng.begin() + (difference_type)(rng.size() / 2)), + y(rng.begin() + (difference_type)(rng.size() / 2), rng.end()); + BOOST_TEST(check_stability(x, y, [&] { + x.swap(y); + })); + } + { + Hub x(rng.begin(), rng.begin() + (difference_type)(rng.size() / 2)), + y(rng.begin() + (difference_type)(rng.size() / 2), rng.end()); + BOOST_TEST(check_stability(x, y, [&] { + x.splice(y); + })); + BOOST_TEST(check_stability(x, y, [&] { + y.splice(std::move(x)); + })); + } + { + Hub x; + x.insert(rng.begin(), rng.end()); + x.insert(rng.begin(), rng.end()); + x.sort(); + BOOST_TEST(check_stability(x, [&] (erase_callback callback) { + for(auto it = x.begin(); it != x.end(); ) { + auto next = std::next(it); + while(next != x.end() && *next == *it) callback(next++); + it = next; + } + x.unique(); + })); + } + { + Hub x(rng.begin(), rng.end()); + auto is_even = [] (const value_type& v) { return v % 2 == 0; }; + BOOST_TEST(check_stability(x, [&] (erase_callback callback) { + for(auto it = x.begin(); it != x.end(); ++it) { + if(is_even(*it)) callback(it); + } + erase_if(x, is_even); + })); + } +} + +int main() +{ + test>(); + test>(); + + return boost::report_errors(); +} + +#endif diff --git a/test/test_hub_uses_allocator.cpp b/test/test_hub_uses_allocator.cpp new file mode 100644 index 0000000..1772940 --- /dev/null +++ b/test/test_hub_uses_allocator.cpp @@ -0,0 +1,62 @@ +/* Copyright 2026 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 + +#if BOOST_CXX_VERSION < 201103L + +int main() { return 0; } + +#else + +#include +#include +#include +#include +#include "hub_utility.hpp" + +struct small_allocator_user +{ + using allocator_type = stateful_allocator; + + small_allocator_user(const allocator_type& al_): al{al_} {} + small_allocator_user( + const small_allocator_user&, const allocator_type& al_): al{al_} {} + + operator int() const { return 0; } + + allocator_type al; +}; + +int main() +{ + { + using string = std::basic_string< + char, std::char_traits, stateful_allocator>; + using hub = boost::container::hub< + string, std::scoped_allocator_adaptor>>; + using allocator_type = hub::allocator_type; + + hub h(allocator_type(42)); + h.emplace("hello"); + BOOST_TEST(*h.begin() == "hello"); + BOOST_TEST_EQ(h.begin()->get_allocator().state, 42); + } + { + using hub = boost::container::hub< + small_allocator_user, + std::scoped_allocator_adaptor>>; + using allocator_type = hub::allocator_type; + + hub h(10, small_allocator_user{allocator_type(0)}, allocator_type(42)); + h.sort(); + BOOST_TEST_EQ(h.begin()->al.state, 42); + } + + return boost::report_errors(); +} + +#endif