1// Copyright (C) 2023 Christian Mazakas
2// Copyright (C) 2023 Joaquin M Lopez Munoz
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6#include "exception_helpers.hpp"
7
8#include <boost/unordered/concurrent_flat_map.hpp>
9#include <boost/unordered/concurrent_flat_set.hpp>
10
11#include <boost/core/ignore_unused.hpp>
12
13namespace {
14 test::seed_t initialize_seed(3202923);
15
16 struct lvalue_eraser_type
17 {
18 template <class T, class X> void operator()(std::vector<T>& values, X& x)
19 {
20 static constexpr auto value_type_cardinality =
21 value_cardinality<typename X::value_type>::value;
22
23 std::atomic<std::uint64_t> num_erased{0};
24
25 auto const old_size = x.size();
26
27 auto const old_dc = +raii::default_constructor;
28 auto const old_cc = +raii::copy_constructor;
29 auto const old_mc = +raii::move_constructor;
30
31 auto const old_d = +raii::destructor;
32
33 enable_exceptions();
34 thread_runner(values, [&values, &num_erased, &x](boost::span<T>) {
35 for (auto const& v : values) {
36 try {
37 auto count = x.erase(get_key(v));
38 BOOST_TEST_LE(count, 1u);
39 BOOST_TEST_GE(count, 0u);
40
41 num_erased += count;
42 } catch (...) {
43 }
44 }
45 });
46 disable_exceptions();
47
48 BOOST_TEST_EQ(x.size(), old_size - num_erased);
49
50 BOOST_TEST_EQ(raii::default_constructor, old_dc);
51 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
52 BOOST_TEST_EQ(raii::move_constructor, old_mc);
53
54 BOOST_TEST_EQ(
55 raii::destructor, old_d + value_type_cardinality * num_erased);
56 }
57 } lvalue_eraser;
58
59 struct lvalue_eraser_if_type
60 {
61 template <class T, class X> void operator()(std::vector<T>& values, X& x)
62 {
63 using value_type = typename X::value_type;
64 static constexpr auto value_type_cardinality =
65 value_cardinality<value_type>::value;
66
67 // concurrent_flat_set visit is always const access
68 using arg_type = typename std::conditional<
69 std::is_same<typename X::key_type, typename X::value_type>::value,
70 typename X::value_type const,
71 typename X::value_type
72 >::type;
73
74 std::atomic<std::uint64_t> num_erased{0};
75
76 auto const old_size = x.size();
77
78 auto const old_dc = +raii::default_constructor;
79 auto const old_cc = +raii::copy_constructor;
80 auto const old_mc = +raii::move_constructor;
81
82 auto const old_d = +raii::destructor;
83
84 auto max = 0;
85 x.visit_all([&max](value_type const& v) {
86 if (get_value(v).x_ > max) {
87 max = get_value(v).x_;
88 }
89 });
90
91 auto threshold = max / 2;
92
93 auto expected_erasures = 0u;
94 x.visit_all([&expected_erasures, threshold](value_type const& v) {
95 if (get_value(v).x_ > threshold) {
96 ++expected_erasures;
97 }
98 });
99
100 enable_exceptions();
101 thread_runner(values, [&num_erased, &x, threshold](boost::span<T> s) {
102 for (auto const& v : s) {
103 try {
104 auto count = x.erase_if(get_key(v),
105 [threshold](arg_type& w) { return get_value(w).x_ > threshold; });
106 num_erased += count;
107 BOOST_TEST_LE(count, 1u);
108 BOOST_TEST_GE(count, 0u);
109 } catch (...) {
110 }
111 }
112 });
113 disable_exceptions();
114
115 BOOST_TEST_LE(num_erased, expected_erasures);
116 BOOST_TEST_EQ(x.size(), old_size - num_erased);
117
118 BOOST_TEST_EQ(raii::default_constructor, old_dc);
119 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
120 BOOST_TEST_EQ(raii::move_constructor, old_mc);
121
122 BOOST_TEST_EQ(
123 raii::destructor, old_d + value_type_cardinality * num_erased);
124 }
125 } lvalue_eraser_if;
126
127 struct erase_if_type
128 {
129 template <class T, class X> void operator()(std::vector<T>& values, X& x)
130 {
131 using value_type = typename X::value_type;
132 static constexpr auto value_type_cardinality =
133 value_cardinality<value_type>::value;
134
135 // concurrent_flat_set visit is always const access
136 using arg_type = typename std::conditional<
137 std::is_same<typename X::key_type, typename X::value_type>::value,
138 typename X::value_type const,
139 typename X::value_type
140 >::type;
141
142 auto const old_size = x.size();
143
144 auto const old_dc = +raii::default_constructor;
145 auto const old_cc = +raii::copy_constructor;
146 auto const old_mc = +raii::move_constructor;
147
148 auto const old_d = +raii::destructor;
149
150 auto max = 0;
151 x.visit_all([&max](value_type const& v) {
152 if (get_value(v).x_ > max) {
153 max = get_value(v).x_;
154 }
155 });
156
157 auto threshold = max / 2;
158
159 auto expected_erasures = 0u;
160 x.visit_all([&expected_erasures, threshold](value_type const& v) {
161 if (get_value(v).x_ > threshold) {
162 ++expected_erasures;
163 }
164 });
165
166 enable_exceptions();
167 thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
168 for (std::size_t i = 0; i < 256; ++i) {
169 try {
170 x.erase_if([threshold](arg_type& v) {
171 static std::atomic<std::uint32_t> c{0};
172 auto t = ++c;
173 if (should_throw && (t % throw_threshold == 0)) {
174 throw exception_tag{};
175 }
176
177 return get_value(v).x_ > threshold;
178 });
179 } catch (...) {
180 }
181 }
182 });
183 disable_exceptions();
184
185 BOOST_TEST_EQ(raii::default_constructor, old_dc);
186 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
187 BOOST_TEST_EQ(raii::move_constructor, old_mc);
188
189 BOOST_TEST_EQ(
190 raii::destructor,
191 old_d + value_type_cardinality * (old_size - x.size()));
192 }
193 } erase_if;
194
195 struct free_fn_erase_if_type
196 {
197 template <class T, class X> void operator()(std::vector<T>& values, X& x)
198 {
199 using value_type = typename X::value_type;
200 static constexpr auto value_type_cardinality =
201 value_cardinality<value_type>::value;
202
203 // concurrent_flat_set visit is always const access
204 using arg_type = typename std::conditional<
205 std::is_same<typename X::key_type, typename X::value_type>::value,
206 typename X::value_type const,
207 typename X::value_type
208 >::type;
209
210 auto const old_size = x.size();
211
212 auto const old_dc = +raii::default_constructor;
213 auto const old_cc = +raii::copy_constructor;
214 auto const old_mc = +raii::move_constructor;
215
216 auto const old_d = +raii::destructor;
217
218 auto max = 0;
219 x.visit_all([&max](value_type const& v) {
220 if (get_value(v).x_ > max) {
221 max = get_value(v).x_;
222 }
223 });
224
225 auto threshold = max / 2;
226
227 enable_exceptions();
228 thread_runner(values, [&x, threshold](boost::span<T> /* s */) {
229 for (std::size_t i = 0; i < 256; ++i) {
230 try {
231 boost::unordered::erase_if(x, [threshold](arg_type& v) {
232 static std::atomic<std::uint32_t> c{0};
233 auto t = ++c;
234 if (should_throw && (t % throw_threshold == 0)) {
235 throw exception_tag{};
236 }
237
238 return get_value(v).x_ > threshold;
239 });
240
241 } catch (...) {
242 }
243 }
244 });
245 disable_exceptions();
246
247 BOOST_TEST_EQ(raii::default_constructor, old_dc);
248 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
249 BOOST_TEST_EQ(raii::move_constructor, old_mc);
250
251 BOOST_TEST_EQ(
252 raii::destructor,
253 old_d + value_type_cardinality * (old_size - x.size()));
254 }
255 } free_fn_erase_if;
256
257 template <class X, class GF, class F>
258 void erase(X*, GF gen_factory, F eraser, test::random_generator rg)
259 {
260 auto gen = gen_factory.template get<X>();
261 auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
262 auto reference_cont = reference_container<X>(values.begin(), values.end());
263
264 raii::reset_counts();
265
266 {
267 X x(values.size());
268 for (auto const& v : values) {
269 x.insert(v);
270 }
271
272 BOOST_TEST_EQ(x.size(), reference_cont.size());
273 BOOST_TEST_EQ(raii::destructor, 0u);
274
275 test_fuzzy_matches_reference(x, reference_cont, rg);
276
277 eraser(values, x);
278 test_fuzzy_matches_reference(x, reference_cont, rg);
279 }
280
281 check_raii_counts();
282 }
283
284 boost::unordered::concurrent_flat_map<raii, raii, stateful_hash,
285 stateful_key_equal, stateful_allocator<std::pair<raii const, raii> > >* map;
286 boost::unordered::concurrent_flat_set<raii, stateful_hash,
287 stateful_key_equal, stateful_allocator<raii> >* set;
288
289} // namespace
290
291using test::default_generator;
292using test::limited_range;
293using test::sequential;
294
295// clang-format off
296UNORDERED_TEST(
297 erase,
298 ((map)(set))
299 ((exception_value_type_generator_factory)
300 (exception_init_type_generator_factory))
301 ((lvalue_eraser)(lvalue_eraser_if)(erase_if)(free_fn_erase_if))
302 ((default_generator)(sequential)(limited_range)))
303
304// clang-format on
305
306RUN_TESTS()
307

source code of boost/libs/unordered/test/cfoa/exception_erase_tests.cpp