1 /*
2 Copyright (c) 2005-2023 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #ifndef __TBB_test_common_concurrent_unordered_common
18 #define __TBB_test_common_concurrent_unordered_common
19
20 #define __TBB_UNORDERED_TEST 1
21
22 #include "test.h"
23 #include <memory>
24 #include "concurrent_associative_common.h"
25 #include "test_comparisons.h"
26
27 template<typename MyTable>
CheckContainerAllocator(MyTable & table,size_t expected_allocs,size_t expected_frees,bool exact)28 inline void CheckContainerAllocator(MyTable &table, size_t expected_allocs, size_t expected_frees, bool exact) {
29 typename MyTable::allocator_type a = table.get_allocator();
30 REQUIRE( a.items_allocated == a.allocations);
31 REQUIRE( a.items_freed == a.frees);
32 REQUIRE( a.items_allocated == a.items_freed );
33 CheckAllocator<MyTable>(a, expected_allocs, expected_frees, exact);
34 }
35
36 template<typename Container>
CheckNoAllocations(Container & cont)37 inline void CheckNoAllocations(Container &cont){
38 CheckContainerAllocator(cont, 0, 0, false);
39 }
40
41 template<typename T>
42 struct degenerate_hash {
operatordegenerate_hash43 size_t operator()(const T& /*a*/) const {
44 return 1;
45 }
46 };
47
48 template <typename T>
test_unordered_methods()49 void test_unordered_methods(){
50 T cont;
51 cont.insert(Value<T>::make(1));
52 cont.insert(Value<T>::make(2));
53 // unordered_specific
54 // void rehash(size_type n);
55 cont.rehash(16);
56
57 // float load_factor() const;
58 // float max_load_factor() const;
59 REQUIRE_MESSAGE(cont.load_factor() <= cont.max_load_factor(), "Load factor is invalid");
60
61 // void max_load_factor(float z);
62 cont.max_load_factor(16.0f);
63 REQUIRE_MESSAGE(cont.max_load_factor() == 16.0f, "Max load factor has not been changed properly");
64
65 // hasher hash_function() const;
66 cont.hash_function();
67
68 // key_equal key_eq() const;
69 cont.key_eq();
70
71 cont.clear();
72 CheckNoAllocations(cont);
73 for (int i = 0; i < 256; i++)
74 {
75 std::pair<typename T::iterator, bool> ins3 = cont.insert(Value<T>::make(i));
76 REQUIRE_MESSAGE((ins3.second == true && Value<T>::get(*(ins3.first)) == i), "Element 1 has not been inserted properly");
77 }
78 REQUIRE_MESSAGE(cont.size() == 256, "Wrong number of elements have been inserted");
79 // size_type unsafe_bucket_count() const;
80 REQUIRE_MESSAGE(cont.unsafe_bucket_count() == 16, "Wrong number of buckets");
81
82 // size_type unsafe_max_bucket_count() const;
83 //REQUIRE_MESSAGE(cont.unsafe_max_bucket_count() > 65536, "Wrong max number of buckets");
84
85 for (unsigned int i = 0; i < 256; i++)
86 {
87 typename T::size_type buck = cont.unsafe_bucket(i);
88
89 // size_type unsafe_bucket(const key_type& k) const;
90 REQUIRE_MESSAGE(buck < 16, "Wrong bucket mapping");
91 }
92
93 typename T::size_type bucketSizeSum = 0;
94 typename T::size_type iteratorSizeSum = 0;
95
96 for (unsigned int i = 0; i < 16; i++)
97 {
98 bucketSizeSum += cont.unsafe_bucket_size(i);
99 for (typename T::iterator bit = cont.unsafe_begin(i); bit != cont.unsafe_end(i); bit++) iteratorSizeSum++;
100 }
101 REQUIRE_MESSAGE(bucketSizeSum == 256, "sum of bucket counts incorrect");
102 REQUIRE_MESSAGE(iteratorSizeSum == 256, "sum of iterator counts incorrect");
103 }
104
105 template<typename Container, typename CheckElementState = std::false_type>
test_basic()106 void test_basic(){
107 test_basic_common<Container, CheckElementState>();
108 test_unordered_methods<Container>();
109 }
110
111 template <typename Container>
112 void test_concurrent( bool asymptotic = false ) {
113 test_concurrent_common<Container>(asymptotic);
114 }
115
116 struct UnorderedMoveTraitsBase {
117 static constexpr std::size_t expected_number_of_items_to_allocate_for_steal_move = 3; // TODO: check
118
119 template <typename UnorderedType, typename Iterator>
construct_containerUnorderedMoveTraitsBase120 static UnorderedType& construct_container( typename std::aligned_storage<sizeof(UnorderedType)>::type& storage,
121 Iterator begin, Iterator end )
122 {
123 UnorderedType* ptr = reinterpret_cast<UnorderedType*>(&storage);
124 new (ptr) UnorderedType(begin, end);
125 return *ptr;
126 }
127
128 template <typename UnorderedType, typename Iterator, typename Allocator>
construct_containerUnorderedMoveTraitsBase129 static UnorderedType& construct_container( typename std::aligned_storage<sizeof(UnorderedType)>::type& storage,
130 Iterator begin, Iterator end, const Allocator& alloc )
131 {
132 UnorderedType* ptr = reinterpret_cast<UnorderedType*>(&storage);
133 new (ptr) UnorderedType(begin, end, /*bucket_count = */4, alloc);
134 return *ptr;
135 }
136
137 template <typename UnorderedType, typename Iterator>
equalUnorderedMoveTraitsBase138 static bool equal( const UnorderedType& c, Iterator begin, Iterator end ) {
139 if (std::size_t(std::distance(begin, end)) != c.size()) {
140 return false;
141 }
142
143 for (Iterator it = begin; it != end; ++it) {
144 if (!c.contains(Value<UnorderedType>::key(*it))) {
145 return false;
146 }
147 }
148 return true;
149 }
150 }; // struct UnorderedMoveTraitsBase
151
152 template <bool DefCtorPresent, typename Table>
CustomExamine(Table c,const std::list<typename Table::value_type> & lst)153 void CustomExamine( Table c, const std::list<typename Table::value_type>& lst ) {
154 using size_type = typename Table::size_type;
155 const Table constC = c;
156
157 const size_type bucket_count = c.unsafe_bucket_count();
158 REQUIRE(c.unsafe_max_bucket_count() >= bucket_count);
159
160 size_type counter = 0;
161 for (size_type i = 0; i < bucket_count; ++i) {
162 const size_type size = c.unsafe_bucket_size(i);
163 using diff_type = typename Table::difference_type;
164
165 REQUIRE(std::distance(c.unsafe_begin(i), c.unsafe_end(i)) == diff_type(size));
166 REQUIRE(std::distance(c.unsafe_cbegin(i), c.unsafe_cend(i)) == diff_type(size));
167 REQUIRE(std::distance(constC.unsafe_begin(i), constC.unsafe_end(i)) == diff_type(size));
168 REQUIRE(std::distance(constC.unsafe_cbegin(i), constC.unsafe_cend(i)) == diff_type(size));
169 counter += size;
170 }
171
172 REQUIRE(counter == lst.size());
173
174 for (auto it = lst.begin(); it != lst.end();) {
175 const size_type index = c.unsafe_bucket(Value<Table>::key(*it));
176 auto prev_it = it++;
177 REQUIRE(std::search(c.unsafe_begin(index), c.unsafe_end(index), prev_it, it, utils::IsEqual()) != c.unsafe_end(index));
178 }
179
180 c.rehash(2*bucket_count);
181 REQUIRE(c.unsafe_bucket_count() > bucket_count);
182
183 auto count = 2 * c.max_load_factor() * c.unsafe_bucket_count();
184 c.reserve(size_type(count));
185 REQUIRE(c.max_load_factor() * c.unsafe_bucket_count() >= count);
186
187 REQUIRE(c.load_factor() <= c.max_load_factor());
188 c.max_load_factor(1.0f);
189 c.hash_function();
190 c.key_eq();
191 }
192
193 template <bool DefCtorPresent, typename Table>
Examine(Table c,const std::list<typename Table::value_type> & lst)194 void Examine( Table c, const std::list<typename Table::value_type>& lst ) {
195 CommonExamine<DefCtorPresent>(c, lst);
196 CustomExamine<DefCtorPresent>(c, lst);
197 }
198
199 // Necessary to avoid warnings about explicit copy assignment to itself
200 template <typename T>
self(T & obj)201 T& self( T& obj ) {
202 return obj;
203 }
204
205 template <bool DefCtorPresent, typename Table>
TypeTester(const std::list<typename Table::value_type> & lst)206 void TypeTester( const std::list<typename Table::value_type>& lst ) {
207 REQUIRE_MESSAGE(lst.size() >= 5, "Array should have at least 5 elements");
208 REQUIRE_MESSAGE(lst.size() <= 100, "The test hash O(n^2) complexity so a big number of elements can lead long execution time");
209
210 Table c1;
211 c1.insert(lst.begin(), lst.end());
212
213 Examine<DefCtorPresent>(c1, lst);
214
215 typename Table::size_type initial_bucket_number = 8;
216 typename Table::allocator_type allocator;
217 typename Table::hasher hasher;
218
219 auto it = lst.begin();
220 Table c2({*it++, *it++, *it++});
221 c2.insert(it, lst.end());
222 Examine<DefCtorPresent>(c2, lst);
223
224 it = lst.begin();
225 // Constructor from an std::initializer_list, default hasher and key_equal and non-default allocator
226 Table c2_alloc({*it++, *it++, *it++}, initial_bucket_number, allocator);
227 c2_alloc.insert(it, lst.end());
228 Examine<DefCtorPresent>(c2_alloc, lst);
229
230 it = lst.begin();
231 // Constructor from an std::initializer_list, default key_equal and non-default hasher and allocator
232 Table c2_hash_alloc({*it++, *it++, *it++}, initial_bucket_number, hasher, allocator);
233 c2_hash_alloc.insert(it, lst.end());
234 Examine<DefCtorPresent>(c2_hash_alloc, lst);
235
236 // Copy ctor
237 Table c3(c1);
238 Examine<DefCtorPresent>(c3, lst);
239
240 // Copy ctor with the allocator
241 Table c3_alloc(c1, allocator);
242 Examine<DefCtorPresent>(c3_alloc, lst);
243
244 // Construct an empty table with n preallocated buckets
245 Table c4(lst.size());
246 c4.insert(lst.begin(), lst.end());
247 Examine<DefCtorPresent>(c4, lst);
248
249 // Construct an empty table with n preallocated buckets, default hasher and key_equal and non-default allocator
250 Table c4_alloc(lst.size(), allocator);
251 c4_alloc.insert(lst.begin(), lst.end());
252 Examine<DefCtorPresent>(c4_alloc, lst);
253
254 // Construct an empty table with n preallocated buckets, default key_equal and non-default hasher and allocator
255 Table c4_hash_alloc(lst.size(), hasher, allocator);
256 c4_hash_alloc.insert(lst.begin(), lst.end());
257 Examine<DefCtorPresent>(c4_hash_alloc, lst);
258
259 // Construction from the iteration range
260 Table c5(c1.begin(), c1.end());
261 Examine<DefCtorPresent>(c5, lst);
262
263 // Construction from the iteration range, default hasher and key_equal and non-default allocator
264 Table c5_alloc(c1.begin(), c2.end(), initial_bucket_number, allocator);
265 Examine<DefCtorPresent>(c5_alloc, lst);
266
267 // Construction from the iteration range, default key_equal and non-default hasher and allocator
268 Table c5_hash_alloc(c1.begin(), c2.end(), initial_bucket_number, hasher, allocator);
269 Examine<DefCtorPresent>(c5_hash_alloc, lst);
270
271 // Copy assignment
272 Table c6;
273 c6 = c1;
274 Examine<DefCtorPresent>(c6, lst);
275
276 // Copy assignment to itself
277 c6 = self(c6);
278 Examine<DefCtorPresent>(c6, lst);
279
280 // Move assignment
281 Table c7;
282 c7 = std::move(c6);
283 Examine<DefCtorPresent>(c7, lst);
284
285 // Move assignment to itself
286 c7 = std::move(self(c7));
287 Examine<DefCtorPresent>(c7, lst);
288
289 // Assignment to the std::initializer_list
290 Table c8;
291 it = lst.begin();
292 c8 = {*it++, *it++, *it++};
293 c8.insert(it, lst.end());
294 Examine<DefCtorPresent>(c8, lst);
295 }
296
297 struct transparent_key_equality {
298 template <typename T>
operatortransparent_key_equality299 bool operator()(const T&, const T&) const {
300 return true;
301 }
302 using is_transparent = void;
303 };
304
305 struct hasher_with_transparent_key_equal {
306 template <typename T>
operatorhasher_with_transparent_key_equal307 std::size_t operator()(const T&) {
308 return 0;
309 }
310 using transparent_key_equal = transparent_key_equality;
311 };
312
313 template <template <typename...> class Container, typename... Args>
check_heterogeneous_functions_key_int()314 void check_heterogeneous_functions_key_int() {
315 check_heterogeneous_functions_key_int_impl<Container<Args..., hash_with_transparent_key_equal>>();
316 }
317
318 template <template <typename...> class Container, typename... Args>
check_heterogeneous_functions_key_string()319 void check_heterogeneous_functions_key_string() {
320 check_heterogeneous_functions_key_string_impl<Container<Args..., hash_with_transparent_key_equal>>();
321 }
322
323 template <typename Container>
test_comparisons_basic()324 void test_comparisons_basic() {
325 using comparisons_testing::testEqualityComparisons;
326 Container c1, c2;
327 testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
328
329 c1.insert(Value<Container>::make(1));
330 testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
331
332 c2.insert(Value<Container>::make(1));
333 testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
334
335 c2.insert(Value<Container>::make(2));
336 testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
337
338 c1.clear();
339 c2.clear();
340 testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
341 }
342
343 template <typename TwoWayComparableContainerType>
test_two_way_comparable_container()344 void test_two_way_comparable_container() {
345 TwoWayComparableContainerType c1, c2;
346 c1.insert(Value<TwoWayComparableContainerType>::make(1));
347 c2.insert(Value<TwoWayComparableContainerType>::make(1));
348 comparisons_testing::TwoWayComparable::reset();
349 REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result");
350 comparisons_testing::check_equality_comparison();
351 REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result");
352 comparisons_testing::check_equality_comparison();
353 }
354
355 template <template <typename...> class ContainerType>
test_map_comparisons()356 void test_map_comparisons() {
357 using integral_container = ContainerType<int, int>;
358 using two_way_comparable_container = ContainerType<comparisons_testing::TwoWayComparable,
359 comparisons_testing::TwoWayComparable>;
360 test_comparisons_basic<integral_container>();
361 test_comparisons_basic<two_way_comparable_container>();
362 test_two_way_comparable_container<two_way_comparable_container>();
363 }
364
365 template <template <typename...> class ContainerType>
test_set_comparisons()366 void test_set_comparisons() {
367 using integral_container = ContainerType<int>;
368 using two_way_comparable_container = ContainerType<comparisons_testing::TwoWayComparable>;
369
370 test_comparisons_basic<integral_container>();
371 test_comparisons_basic<two_way_comparable_container>();
372 test_two_way_comparable_container<two_way_comparable_container>();
373 }
374
375 template <typename Container>
test_reserve_regression()376 void test_reserve_regression() {
377 Container container;
378
379 float lf = container.max_load_factor();
380 std::size_t buckets = container.unsafe_bucket_count();
381 std::size_t capacity = std::size_t(buckets * lf);
382
383 for (std::size_t elements = 0; elements < capacity; ++elements) {
384 container.reserve(elements);
385 REQUIRE_MESSAGE(container.unsafe_bucket_count() == buckets,
386 "reserve() should not increase bucket count if the capacity is not reached");
387 }
388
389 container.reserve(capacity * 2);
390 REQUIRE_MESSAGE(container.unsafe_bucket_count() > buckets, "reserve() should increase bucket count if the capacity is reached");
391 }
392
393 #endif // __TBB_test_common_concurrent_unordered_common
394