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