1 /*
2     Copyright (c) 2005-2021 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 #include <common/test.h>
18 #include <common/utils.h>
19 #include "common/utils_report.h"
20 #include <common/spin_barrier.h>
21 #include <common/state_trackable.h>
22 #include <common/container_move_support.h>
23 #include <common/containers_common.h>
24 #include <common/initializer_list_support.h>
25 #include <common/vector_types.h>
26 #include <common/test_comparisons.h>
27 #include "oneapi/tbb/concurrent_hash_map.h"
28 #include "oneapi/tbb/global_control.h"
29 #include "oneapi/tbb/parallel_for.h"
30 
31 //! \file conformance_concurrent_hash_map.cpp
32 //! \brief Test for [containers.concurrent_hash_map containers.tbb_hash_compare] specification
33 
34 /** Has tightly controlled interface so that we can verify
35     that concurrent_hash_map uses only the required interface. */
36 class MyException : public std::bad_alloc {
37 public:
38     virtual const char *what() const throw() override { return "out of items limit"; }
39     virtual ~MyException() throw() {}
40 };
41 
42 /** Has tightly controlled interface so that we can verify
43     that concurrent_hash_map uses only the required interface. */
44 class MyKey {
45 private:
46     int key;
47     friend class MyHashCompare;
48     friend class YourHashCompare;
49 public:
50     MyKey() = default;
51     MyKey( const MyKey& ) = default;
52     void operator=( const MyKey&  ) = delete;
53     static MyKey make( int i ) {
54         MyKey result;
55         result.key = i;
56         return result;
57     }
58 
59     int value_of() const { return key; }
60 };
61 
62 std::atomic<long> MyDataCount;
63 long MyDataCountLimit = 0;
64 
65 class MyData {
66 protected:
67     friend class MyData2;
68     int data;
69     enum state_t {
70         LIVE=0x1234,
71         DEAD=0x5678
72     } my_state;
73     void operator=( const MyData& );    // Deny access
74 public:
75     MyData(int i = 0) {
76         my_state = LIVE;
77         data = i;
78         if (MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
79             TBB_TEST_THROW(MyException{});
80         }
81         ++MyDataCount;
82     }
83 
84     MyData( const MyData& other ) {
85         CHECK(other.my_state==LIVE);
86         my_state = LIVE;
87         data = other.data;
88         if(MyDataCountLimit && MyDataCount + 1 >= MyDataCountLimit) {
89             TBB_TEST_THROW(MyException{});
90         }
91         ++MyDataCount;
92     }
93 
94     ~MyData() {
95         --MyDataCount;
96         my_state = DEAD;
97     }
98 
99     static MyData make( int i ) {
100         MyData result;
101         result.data = i;
102         return result;
103     }
104 
105     int value_of() const {
106         CHECK(my_state==LIVE);
107         return data;
108     }
109 
110     void set_value( int i ) {
111         CHECK(my_state==LIVE);
112         data = i;
113     }
114 
115     bool operator==( const MyData& other ) const {
116         CHECK(other.my_state==LIVE);
117         CHECK(my_state==LIVE);
118         return data == other.data;
119     }
120 };
121 
122 class MyData2 : public MyData {
123 public:
124     MyData2( ) {}
125 
126     MyData2( const MyData2& other ) : MyData() {
127         CHECK(other.my_state==LIVE);
128         CHECK(my_state==LIVE);
129         data = other.data;
130     }
131 
132     MyData2( const MyData& other ) {
133         CHECK(other.my_state==LIVE);
134         CHECK(my_state==LIVE);
135         data = other.data;
136     }
137 
138     void operator=( const MyData& other ) {
139         CHECK(other.my_state==LIVE);
140         CHECK(my_state==LIVE);
141         data = other.data;
142     }
143 
144     void operator=( const MyData2& other ) {
145         CHECK(other.my_state==LIVE);
146         CHECK(my_state==LIVE);
147         data = other.data;
148     }
149 
150     bool operator==( const MyData2& other ) const {
151         CHECK(other.my_state==LIVE);
152         CHECK(my_state==LIVE);
153         return data == other.data;
154     }
155 };
156 
157 class MyHashCompare {
158 public:
159     bool equal( const MyKey& j, const MyKey& k ) const {
160         return j.key==k.key;
161     }
162 
163     std::size_t hash( const MyKey& k ) const {
164         return k.key;
165     }
166 };
167 
168 class YourHashCompare {
169 public:
170     bool equal( const MyKey& j, const MyKey& k ) const {
171         return j.key==k.key;
172     }
173 
174     std::size_t hash( const MyKey& ) const {
175         return 1;
176     }
177 };
178 
179 using test_allocator_type = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData>>>;
180 using test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData, MyHashCompare, test_allocator_type>;
181 using other_test_table_type = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare>;
182 
183 template <template <typename...> class ContainerType>
184 void test_member_types() {
185     using container_type = ContainerType<int, int>;
186     static_assert(std::is_same<typename container_type::allocator_type, oneapi::tbb::tbb_allocator<std::pair<const int, int>>>::value,
187                   "Incorrect default template allocator");
188 
189     static_assert(std::is_same<typename container_type::key_type, int>::value,
190                   "Incorrect container key_type member type");
191     static_assert(std::is_same<typename container_type::value_type, std::pair<const int, int>>::value,
192                   "Incorrect container value_type member type");
193 
194     static_assert(std::is_unsigned<typename container_type::size_type>::value,
195                   "Incorrect container size_type member type");
196     static_assert(std::is_signed<typename container_type::difference_type>::value,
197                   "Incorrect container difference_type member type");
198 
199     using value_type = typename container_type::value_type;
200     static_assert(std::is_same<typename container_type::reference, value_type&>::value,
201                   "Incorrect container reference member type");
202     static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value,
203                   "Incorrect container const_reference member type");
204     using allocator_type = typename container_type::allocator_type;
205     static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value,
206                   "Incorrect container pointer member type");
207     static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value,
208                   "Incorrect container const_pointer member type");
209 
210     static_assert(utils::is_forward_iterator<typename container_type::iterator>::value,
211                   "Incorrect container iterator member type");
212     static_assert(!std::is_const<typename container_type::iterator::value_type>::value,
213                   "Incorrect container iterator member type");
214     static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value,
215                   "Incorrect container const_iterator member type");
216     static_assert(std::is_const<typename container_type::const_iterator::value_type>::value,
217                   "Incorrect container iterator member type");
218 }
219 
220 template<typename test_table_type>
221 void FillTable( test_table_type& x, int n ) {
222     for( int i=1; i<=n; ++i ) {
223         MyKey key( MyKey::make(-i) ); // hash values must not be specified in direct order
224         typename test_table_type::accessor a;
225         bool b = x.insert(a,key);
226         CHECK(b);
227         a->second.set_value( i*i );
228     }
229 }
230 
231 template<typename test_table_type>
232 static void CheckTable( const test_table_type& x, int n ) {
233     REQUIRE_MESSAGE( x.size()==size_t(n), "table is different size than expected" );
234     CHECK(x.empty()==(n==0));
235     CHECK(x.size()<=x.max_size());
236     for( int i=1; i<=n; ++i ) {
237         MyKey key( MyKey::make(-i) );
238         typename test_table_type::const_accessor a;
239         bool b = x.find(a,key);
240         CHECK(b);
241         CHECK(a->second.value_of()==i*i);
242     }
243     int count = 0;
244     int key_sum = 0;
245     for( typename test_table_type::const_iterator i(x.begin()); i!=x.end(); ++i ) {
246         ++count;
247         key_sum += -i->first.value_of();
248     }
249     CHECK(count==n);
250     CHECK(key_sum==n*(n+1)/2);
251 }
252 
253 void TestCopy() {
254     INFO("testing copy\n");
255     test_table_type t1;
256     for( int i=0; i<10000; i=(i<100 ? i+1 : i*3) ) {
257         MyDataCount = 0;
258 
259         FillTable(t1,i);
260         // Do not call CheckTable(t1,i) before copying, it enforces rehashing
261 
262         test_table_type t2(t1);
263         // Check that copy constructor did not mangle source table.
264         CheckTable(t1,i);
265         swap(t1, t2);
266         CheckTable(t1,i);
267         CHECK(!(t1 != t2));
268 
269         // Clear original table
270         t2.clear();
271         swap(t2, t1);
272         CheckTable(t1,0);
273 
274         // Verify that copy of t1 is correct, even after t1 is cleared.
275         CheckTable(t2,i);
276         t2.clear();
277         t1.swap( t2 );
278         CheckTable(t1,0);
279         CheckTable(t2,0);
280         REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
281     }
282 }
283 
284 void TestRehash() {
285     INFO("testing rehashing\n");
286     test_table_type w;
287     w.insert( std::make_pair(MyKey::make(-5), MyData()) );
288     w.rehash(); // without this, assertion will fail
289     test_table_type::iterator it = w.begin();
290     int i = 0; // check for non-rehashed buckets
291     for( ; it != w.end(); i++ )
292         w.count( (it++)->first );
293     CHECK(i == 1);
294     for( i=0; i<1000; i=(i<29 ? i+1 : i*2) ) {
295         for( int j=std::max(256+i, i*2); j<10000; j*=3 ) {
296             test_table_type v;
297             FillTable( v, i );
298             CHECK(int(v.size()) == i);
299             CHECK(int(v.bucket_count()) <= j);
300             v.rehash( j );
301             CHECK(int(v.bucket_count()) >= j);
302             CheckTable( v, i );
303         }
304     }
305 }
306 
307 void TestAssignment() {
308     INFO("testing assignment\n");
309     oneapi::tbb::concurrent_hash_map<int, int> test_map({{1, 2}, {2, 4}});
310     test_map.operator=(test_map); // suppress self assign warning
311     CHECK(!test_map.empty());
312 
313     for( int i=0; i<1000; i=(i<30 ? i+1 : i*5) ) {
314         for( int j=0; j<1000; j=(j<30 ? j+1 : j*7) ) {
315             test_table_type t1;
316             test_table_type t2;
317             FillTable(t1,i);
318             FillTable(t2,j);
319             CHECK((t1 == t2) == (i == j));
320             CheckTable(t2,j);
321 
322             test_table_type& tref = t2=t1;
323             CHECK(&tref==&t2);
324             CHECK(t1 == t2);
325             CheckTable(t1,i);
326             CheckTable(t2,i);
327 
328             t1.clear();
329             CheckTable(t1,0);
330             CheckTable(t2,i);
331             REQUIRE_MESSAGE( MyDataCount==i, "data leak?" );
332 
333             t2.clear();
334             CheckTable(t1,0);
335             CheckTable(t2,0);
336             REQUIRE_MESSAGE( MyDataCount==0, "data leak?" );
337         }
338     }
339 }
340 
341 template<typename Iterator, typename T>
342 void TestIteratorTraits() {
343     T x;
344     typename Iterator::reference xr = x;
345     typename Iterator::pointer xp = &x;
346     CHECK(&xr==xp);
347 }
348 
349 template<typename Iterator1, typename Iterator2>
350 void TestIteratorAssignment( Iterator2 j ) {
351     Iterator1 i(j), k;
352     CHECK(i==j);
353     CHECK(!(i!=j));
354     k = j;
355     CHECK(k==j);
356     CHECK(!(k!=j));
357 }
358 
359 template<typename Range1, typename Range2>
360 void TestRangeAssignment( Range2 r2 ) {
361     Range1 r1(r2); r1 = r2;
362 }
363 
364 void TestIteratorsAndRanges() {
365     INFO("testing iterators compliance\n");
366     TestIteratorTraits<test_table_type::iterator,test_table_type::value_type>();
367     TestIteratorTraits<test_table_type::const_iterator,const test_table_type::value_type>();
368 
369     test_table_type v;
370     CHECK(v.range().grainsize() == 1);
371     test_table_type const &u = v;
372 
373     TestIteratorAssignment<test_table_type::const_iterator>( u.begin() );
374     TestIteratorAssignment<test_table_type::const_iterator>( v.begin() );
375     TestIteratorAssignment<test_table_type::iterator>( v.begin() );
376     // doesn't compile as expected: TestIteratorAssignment<typename V::iterator>( u.begin() );
377 
378     // check for non-existing
379     CHECK(v.equal_range(MyKey::make(-1)) == std::make_pair(v.end(), v.end()));
380     CHECK(u.equal_range(MyKey::make(-1)) == std::make_pair(u.end(), u.end()));
381 
382     INFO("testing ranges compliance\n");
383     TestRangeAssignment<test_table_type::const_range_type>( u.range() );
384     TestRangeAssignment<test_table_type::range_type>( v.range() );
385     // doesn't compile as expected: TestRangeAssignment<typename V::range_type>( u.range() );
386 
387     INFO("testing construction and insertion from iterators range\n");
388     FillTable( v, 1000 );
389     other_test_table_type t(v.begin(), v.end());
390     v.rehash();
391     CheckTable(t, 1000);
392     t.insert(v.begin(), v.end()); // do nothing
393     CheckTable(t, 1000);
394     t.clear();
395     t.insert(v.begin(), v.end()); // restore
396     CheckTable(t, 1000);
397 
398     INFO("testing comparison\n");
399     using test_allocator_type2 = StaticSharedCountingAllocator<std::allocator<std::pair<const MyKey, MyData2>>>;
400     using YourTable1 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare, test_allocator_type2>;
401     using YourTable2 = oneapi::tbb::concurrent_hash_map<MyKey,MyData2,YourHashCompare>;
402     YourTable1 t1;
403     FillTable( t1, 10 );
404     CheckTable(t1, 10 );
405     YourTable2 t2(t1.begin(), t1.end());
406     MyKey key( MyKey::make(-5) ); MyData2 data;
407     CHECK(t2.erase(key));
408     YourTable2::accessor a;
409     CHECK(t2.insert(a, key));
410     data.set_value(0);   a->second = data;
411     CHECK(t1 != t2);
412     data.set_value(5*5); a->second = data;
413     CHECK(t1 == t2);
414 }
415 
416 struct test_insert {
417     template<typename container_type, typename element_type>
418     static void test( std::initializer_list<element_type> il, container_type const& expected ) {
419         container_type vd;
420         vd.insert( il );
421         REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" );
422     }
423 };
424 
425 struct ctor_test {
426  template<typename container_type, typename element_type>
427     static void test( std::initializer_list<element_type> il, container_type const& expected ) {
428         container_type vd(il, tbb::tbb_allocator<std::pair<element_type, element_type>>{});
429         REQUIRE_MESSAGE( vd == expected, "inserting with an initializer list failed" );
430     }
431 };
432 
433 void TestInitList(){
434     using namespace initializer_list_support_tests;
435     INFO("testing initializer_list methods \n");
436 
437     using ch_map_type = oneapi::tbb::concurrent_hash_map<int,int>;
438     std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}};
439 
440     test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il );
441     test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} );
442     test_initializer_list_support_without_assign<ch_map_type, ctor_test>(pairs_il);
443 }
444 
445 template <typename base_alloc_type>
446 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> {
447     using base_type = StaticSharedCountingAllocator<base_alloc_type>;
448     using base_traits = oneapi::tbb::detail::allocator_traits<base_alloc_type>;
449 public:
450     template<typename U>
451     struct rebind {
452         using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>;
453     };
454 
455     only_node_counting_allocator() : base_type() {}
456     only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {}
457 
458     template<typename U>
459     only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {}
460 
461     typename base_type::value_type* allocate(const std::size_t n) {
462         if ( n > 1) {
463             return base_alloc_type::allocate(n);
464         } else {
465             return base_type::allocate(n);
466         }
467     }
468 };
469 
470 #if TBB_USE_EXCEPTIONS
471 void TestExceptions() {
472     using allocator_type = only_node_counting_allocator<oneapi::tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>;
473     using throwing_table = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>;
474     enum methods {
475         zero_method = 0,
476         ctor_copy, op_assign, op_insert,
477         all_methods
478     };
479 
480     INFO("testing exception-safety guarantees\n");
481     throwing_table src;
482     FillTable( src, 1000 );
483     CHECK(MyDataCount==1000);
484 
485     try {
486         for(int t = 0; t < 2; t++) // exception type
487         for(int m = zero_method+1; m < all_methods; m++)
488         {
489             allocator_type a;
490             allocator_type::init_counters();
491             if(t) MyDataCountLimit = 101;
492             else a.set_limits(101);
493             throwing_table victim(a);
494             MyDataCount = 0;
495 
496             try {
497                 switch(m) {
498                 case ctor_copy: {
499                         throwing_table acopy(src, a);
500                     } break;
501                 case op_assign: {
502                         victim = src;
503                     } break;
504                 case op_insert: {
505                         // Insertion in cpp11 don't make copy constructions
506                         // during the insertion, so we need to decrement limit
507                         // to throw an exception in the right place and to prevent
508                         // successful insertion of one unexpected item
509                         if (MyDataCountLimit)
510                             --MyDataCountLimit;
511                         FillTable( victim, 1000 );
512                     } break;
513                 default:;
514                 }
515                 REQUIRE_MESSAGE(false, "should throw an exception");
516             } catch(std::bad_alloc &e) {
517                 MyDataCountLimit = 0;
518                 size_t size = victim.size();
519                 switch(m) {
520                 case op_assign:
521                     REQUIRE_MESSAGE( MyDataCount==100, "data leak?" );
522                     CHECK(size>=100);
523                     utils_fallthrough;
524                 case ctor_copy:
525                     CheckTable(src, 1000);
526                     break;
527                 case op_insert:
528                     CHECK(size==size_t(100-t));
529                     REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" );
530                     CheckTable(victim, 100-t);
531                     break;
532 
533                 default:; // nothing to check here
534                 }
535                 INFO("Exception "<< m << " : " << e.what() << "- ok ()");
536             }
537             catch ( ... ) {
538                 REQUIRE_MESSAGE( false, "Unrecognized exception" );
539             }
540         }
541     } catch(...) {
542         REQUIRE_MESSAGE(false, "unexpected exception");
543     }
544     src.clear(); MyDataCount = 0;
545     allocator_type::max_items = 0;
546 }
547 #endif
548 
549 struct default_container_traits {
550     template <typename container_type, typename iterator_type>
551     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
552         container_type* ptr = reinterpret_cast<container_type*>(&storage);
553         new (ptr) container_type(begin, end);
554         return *ptr;
555     }
556 
557     template <typename container_type, typename iterator_type, typename allocator_type>
558     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
559         container_type* ptr = reinterpret_cast<container_type*>(&storage);
560         new (ptr) container_type(begin, end, a);
561         return *ptr;
562     }
563 };
564 
565 struct hash_map_traits : default_container_traits {
566     enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
567 
568     template<typename T>
569     struct hash_compare {
570         bool equal( const T& lhs, const T& rhs ) const {
571             return lhs==rhs;
572         }
573         size_t hash( const T& k ) const {
574             return my_hash_func(k);
575         }
576         std::hash<T> my_hash_func;
577     };
578 
579     template <typename T, typename Allocator>
580     using container_type = oneapi::tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
581 
582     template <typename T>
583     using container_value_type = std::pair<const T, T>;
584 
585     template<typename element_type, typename allocator_type>
586     struct apply {
587         using type = oneapi::tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
588     };
589 
590     using init_iterator_type = move_support_tests::FooPairIterator;
591     template <typename hash_map_type, typename iterator>
592     static bool equal(hash_map_type const& c, iterator begin, iterator end){
593         bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
594         if (!equal_sizes)
595             return false;
596 
597         for (iterator it = begin; it != end; ++it ){
598             if (c.count( (*it).first) == 0){
599                 return false;
600             }
601         }
602         return true;
603     }
604 };
605 
606 using DataStateTrackedTable = oneapi::tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>;
607 
608 struct RvalueInsert {
609     static void apply( DataStateTrackedTable& table, int i ) {
610         DataStateTrackedTable::accessor a;
611         int next = i + 1;
612         REQUIRE_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))),
613             "already present while should not ?" );
614         CHECK((*a).second == next);
615         CHECK((*a).second.state == StateTrackableBase::MoveInitialized);
616     }
617 };
618 
619 struct Emplace {
620     template <typename Accessor>
621     static void apply_impl( DataStateTrackedTable& table, int i) {
622         Accessor a;
623         REQUIRE_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))),
624                 "already present while should not ?" );
625         CHECK((*a).second == i + 1);
626         CHECK((*a).second.state == StateTrackableBase::DirectInitialized);
627     }
628 
629     static void apply( DataStateTrackedTable& table, int i ) {
630         // TODO: investigate ability to rewrite apply methods with use apply_imp method.
631         if (i % 2) {
632             apply_impl<DataStateTrackedTable::accessor>(table, i);
633         } else {
634             apply_impl<DataStateTrackedTable::const_accessor>(table, i);
635         }
636     }
637 };
638 
639 template<typename Op, typename test_table_type>
640 class TableOperation {
641     test_table_type& my_table;
642 public:
643     void operator()( const oneapi::tbb::blocked_range<int>& range ) const {
644         for( int i=range.begin(); i!=range.end(); ++i )
645             Op::apply(my_table,i);
646     }
647     TableOperation( test_table_type& table ) : my_table(table) {}
648 };
649 
650 bool UseKey( size_t i ) {
651     return (i&3)!=3;
652 }
653 
654 struct Insert {
655     static void apply( test_table_type& table, int i ) {
656         if( UseKey(i) ) {
657             if( i&4 ) {
658                 test_table_type::accessor a;
659                 table.insert( a, MyKey::make(i) );
660                 if( i&1 )
661                     (*a).second.set_value(i*i);
662                 else
663                     a->second.set_value(i*i);
664             } else
665                 if( i&1 ) {
666                     test_table_type::accessor a;
667                     table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) );
668                     CHECK((*a).second.value_of()==i*i);
669                 } else {
670                     test_table_type::const_accessor ca;
671                     table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) );
672                     CHECK(ca->second.value_of()==i*i);
673                 }
674         }
675     }
676 };
677 
678 struct Find {
679     static void apply( test_table_type& table, int i ) {
680         test_table_type::accessor a;
681         const test_table_type::accessor& ca = a;
682         bool b = table.find( a, MyKey::make(i) );
683         CHECK(b==!a.empty());
684         if( b ) {
685             if( !UseKey(i) )
686                 REPORT("Line %d: unexpected key %d present\n",__LINE__,i);
687             CHECK(ca->second.value_of()==i*i);
688             CHECK((*ca).second.value_of()==i*i);
689             if( i&1 )
690                 ca->second.set_value( ~ca->second.value_of() );
691             else
692                 (*ca).second.set_value( ~ca->second.value_of() );
693         } else {
694             if( UseKey(i) )
695                 REPORT("Line %d: key %d missing\n",__LINE__,i);
696         }
697     }
698 };
699 
700 struct FindConst {
701     static void apply( const test_table_type& table, int i ) {
702         test_table_type::const_accessor a;
703         const test_table_type::const_accessor& ca = a;
704         bool b = table.find( a, MyKey::make(i) );
705         CHECK(b==(table.count(MyKey::make(i))>0));
706         CHECK(b==!a.empty());
707         CHECK(b==UseKey(i));
708         if( b ) {
709             CHECK(ca->second.value_of()==~(i*i));
710             CHECK((*ca).second.value_of()==~(i*i));
711         }
712     }
713 };
714 
715 struct InsertInitList {
716     static void apply( test_table_type& table, int i ) {
717         if ( UseKey( i ) ) {
718             // TODO: investigate why the following sequence causes an additional allocation sometimes:
719             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) );
720             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) );
721             std::initializer_list<test_table_type::value_type> il = {
722                 test_table_type::value_type( MyKey::make( i ), i*i )
723                 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */
724                                                                     };
725             table.insert( il );
726         }
727     }
728 };
729 
730 template<typename Op, typename TableType>
731 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) {
732     INFO("testing " << what << " with " << nthread << " threads");
733     oneapi::tbb::parallel_for(oneapi::tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table));
734 }
735 
736 //! Test traversing the table with an iterator.
737 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
738     INFO("testing traversal\n");
739     size_t actual_size = table.size();
740     CHECK(actual_size==expected_size);
741     size_t count = 0;
742     bool* array = new bool[n];
743     memset( array, 0, n*sizeof(bool) );
744     const test_table_type& const_table = table;
745     test_table_type::const_iterator ci = const_table.begin();
746     for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) {
747         // Check iterator
748         int k = i->first.value_of();
749         CHECK(UseKey(k));
750         CHECK((*i).first.value_of()==k);
751         REQUIRE_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" );
752         REQUIRE_MESSAGE( !array[k], "duplicate key" );
753         array[k] = true;
754         ++count;
755 
756         // Check lower/upper bounds
757         std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first);
758         std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first);
759         CHECK((cer.first == er.first && cer.second == er.second));
760         CHECK(cer.first == i);
761         CHECK(std::distance(cer.first, cer.second) == 1);
762 
763         // Check const_iterator
764         test_table_type::const_iterator cic = ci++;
765         CHECK(cic->first.value_of()==k);
766         CHECK((*cic).first.value_of()==k);
767     }
768     CHECK(ci==const_table.end());
769     delete[] array;
770     if (count != expected_size) {
771         INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size);
772     }
773 }
774 
775 std::atomic<int> EraseCount;
776 
777 struct Erase {
778     static void apply( test_table_type& table, int i ) {
779         bool b;
780         if(i&4) {
781             if(i&8) {
782                 test_table_type::const_accessor a;
783                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
784             } else {
785                 test_table_type::accessor a;
786                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
787             }
788         } else
789             b = table.erase( MyKey::make(i) );
790         if( b ) ++EraseCount;
791         CHECK(table.count(MyKey::make(i)) == 0);
792     }
793 };
794 
795 using YourTable = oneapi::tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>;
796 static const int IE_SIZE = 2;
797 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE];
798 
799 struct InsertErase  {
800     static void apply( YourTable& table, int i ) {
801         if ( i%3 ) {
802             int key = i%IE_SIZE;
803             if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) )
804                 ++InsertEraseCount[key];
805         } else {
806             int key = i%IE_SIZE;
807             if( i&1 ) {
808                 YourTable::accessor res;
809                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
810                     --InsertEraseCount[key];
811             } else {
812                 YourTable::const_accessor res;
813                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
814                     --InsertEraseCount[key];
815             }
816         }
817     }
818 };
819 
820 struct InnerInsert {
821     static void apply( YourTable& table, int i ) {
822         YourTable::accessor a1, a2;
823         if(i&1) utils::yield();
824         table.insert( a1, MyKey::make(1) );
825         utils::yield();
826         table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain
827         table.erase( a2 ); // if erase by key it would lead to deadlock for single thread
828     }
829 };
830 
831 struct FakeExclusive {
832     utils::SpinBarrier& barrier;
833     YourTable& table;
834     FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {}
835     void operator()( std::size_t i ) const {
836         if(i) {
837             YourTable::const_accessor real_ca;
838             // const accessor on non-const table acquired as reader (shared)
839             CHECK(table.find(real_ca,MyKey::make(1)));
840             barrier.wait(); // item can be erased
841             std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase
842             real_ca->second.value_of(); // check the state while holding accessor
843         } else {
844             YourTable::accessor fake_ca;
845             const YourTable &const_table = table;
846             // non-const accessor on const table acquired as reader (shared)
847             CHECK(const_table.find(fake_ca,MyKey::make(1)));
848             barrier.wait(); // readers acquired
849             // can mistakenly remove the item while other readers still refers to it
850             table.erase( fake_ca );
851         }
852     }
853 };
854 
855 using AtomicByte = std::atomic<unsigned char>;
856 
857 template<typename RangeType>
858 struct ParallelTraverseBody {
859     const size_t n;
860     AtomicByte* const array;
861     ParallelTraverseBody( AtomicByte array_[], size_t n_ ) :
862         n(n_),
863         array(array_)
864     {}
865     void operator()( const RangeType& range ) const {
866         for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) {
867             int k = i->first.value_of();
868             CHECK((0<=k && size_t(k)<n));
869             ++array[k];
870         }
871     }
872 };
873 
874 void Check( AtomicByte array[], size_t n, size_t expected_size ) {
875     if( expected_size )
876         for( size_t k=0; k<n; ++k ) {
877             if( array[k] != int(UseKey(k)) ) {
878                 REPORT("array[%d]=%d != %d=UseKey(%d)\n",
879                        int(k), int(array[k]), int(UseKey(k)), int(k));
880                 CHECK(false);
881             }
882         }
883 }
884 
885 //! Test traversing the table with a parallel range
886 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
887     INFO("testing parallel traversal\n");
888     CHECK(table.size()==expected_size);
889     AtomicByte* array = new AtomicByte[n];
890 
891     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
892     test_table_type::range_type r = table.range(10);
893     oneapi::tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n ));
894     Check( array, n, expected_size );
895 
896     const test_table_type& const_table = table;
897     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
898     test_table_type::const_range_type cr = const_table.range(10);
899     oneapi::tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n ));
900     Check( array, n, expected_size );
901 
902     delete[] array;
903 }
904 
905 void TestInsertFindErase( std::size_t nthread ) {
906     int n=250000;
907 
908     // compute m = number of unique keys
909     int m = 0;
910     for( int i=0; i<n; ++i )
911         m += UseKey(i);
912     {
913         test_allocator_type alloc;
914         test_allocator_type::init_counters();
915         CHECK(MyDataCount==0);
916         test_table_type table(alloc);
917         TraverseTable(table,n,0);
918         ParallelTraverseTable(table,n,0);
919 
920         int expected_allocs = 0, expected_frees = 100;
921         for ( int i = 0; i < 2; ++i ) {
922             if ( i==0 )
923                 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread );
924             else
925                 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread );
926             CHECK(MyDataCount == m);
927             TraverseTable( table, n, m );
928             ParallelTraverseTable( table, n, m );
929             expected_allocs += m;
930 
931             DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread );
932             CHECK(MyDataCount == m);
933 
934             DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread );
935             CHECK(MyDataCount == m);
936 
937             EraseCount = 0;
938             DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread );
939             CHECK(EraseCount == m);
940             CHECK(MyDataCount == 0);
941             TraverseTable( table, n, 0 );
942             expected_frees += m;
943 
944             table.clear();
945         }
946 
947         if( nthread > 1 ) {
948             YourTable ie_table;
949             for( int i=0; i<IE_SIZE; ++i )
950                 InsertEraseCount[i] = 0;
951             DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
952             for( int i=0; i<IE_SIZE; ++i )
953                 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i)));
954 
955             DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread);
956             utils::SpinBarrier barrier(nthread);
957             INFO("testing erase on fake exclusive accessor\n");
958             utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table));
959         }
960     }
961     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
962     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
963     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
964 }
965 
966 std::atomic<int> Counter;
967 
968 class AddToTable {
969     test_table_type& my_table;
970     const std::size_t my_nthread;
971     const int my_m;
972 public:
973     AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
974     void operator()( std::size_t ) const {
975         for( int i=0; i<my_m; ++i ) {
976             // Busy wait to synchronize threads
977             int j = 0;
978             while( Counter<i ) {
979                 if( ++j==1000000 ) {
980                     // If Counter<i after a million iterations, then we almost surely have
981                     // more logical threads than physical threads, and should yield in
982                     // order to let suspended logical threads make progress.
983                     j = 0;
984                     utils::yield();
985                 }
986             }
987             // Now all threads attempt to simultaneously insert a key.
988             int k;
989             {
990                 test_table_type::accessor a;
991                 MyKey key = MyKey::make(i);
992                 if( my_table.insert( a, key ) )
993                     a->second.set_value( 1 );
994                 else
995                     a->second.set_value( a->second.value_of()+1 );
996                 k = a->second.value_of();
997             }
998             if( std::size_t(k) == my_nthread )
999                 Counter=i+1;
1000         }
1001     }
1002 };
1003 
1004 class RemoveFromTable {
1005     test_table_type& my_table;
1006     const int my_m;
1007 public:
1008     RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {}
1009     void operator()(std::size_t) const {
1010         for( int i=0; i<my_m; ++i ) {
1011             bool b;
1012             if(i&4) {
1013                 if(i&8) {
1014                     test_table_type::const_accessor a;
1015                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1016                 } else {
1017                     test_table_type::accessor a;
1018                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1019                 }
1020             } else
1021                 b = my_table.erase( MyKey::make(i) );
1022             if( b ) ++EraseCount;
1023         }
1024     }
1025 };
1026 
1027 void TestConcurrency( std::size_t nthread ) {
1028     INFO("testing multiple insertions/deletions of same key with " << nthread << " threads");
1029     test_allocator_type::init_counters();
1030     {
1031         CHECK( MyDataCount==0);
1032         test_table_type table;
1033         const int m = 1000;
1034         Counter = 0;
1035         oneapi::tbb::tick_count t0 = oneapi::tbb::tick_count::now();
1036         utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) );
1037         REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" );
1038 
1039         EraseCount = 0;
1040         t0 = oneapi::tbb::tick_count::now();
1041         utils::NativeParallelFor( nthread, RemoveFromTable(table,m) );
1042         REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1043         REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken");
1044 
1045     }
1046     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
1047     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
1048     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
1049     REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1050 }
1051 
1052 template<typename Key>
1053 struct non_default_constructible_hash_compare : oneapi::tbb::detail::d1::tbb_hash_compare<Key> {
1054     non_default_constructible_hash_compare() {
1055         REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument");
1056     }
1057 
1058     non_default_constructible_hash_compare(int) {}
1059 };
1060 
1061 void TestHashCompareConstructors() {
1062     using key_type = int;
1063     using map_type = oneapi::tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>;
1064 
1065     non_default_constructible_hash_compare<key_type> compare(0);
1066     map_type::allocator_type allocator;
1067 
1068     map_type map1(compare);
1069     map_type map2(compare, allocator);
1070 
1071     map_type map3(1, compare);
1072     map_type map4(1, compare, allocator);
1073 
1074     std::vector<map_type::value_type> reference_vector;
1075     map_type map5(reference_vector.begin(), reference_vector.end(), compare);
1076     map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator);
1077 
1078     map_type map7({}, compare);
1079     map_type map8({}, compare, allocator);
1080 }
1081 
1082 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1083 template <typename T>
1084 struct debug_hash_compare : public oneapi::tbb::detail::d1::tbb_hash_compare<T> {};
1085 
1086 template <template <typename...> typename TMap>
1087 void TestDeductionGuides() {
1088     using Key = int;
1089     using Value = std::string;
1090 
1091     using ComplexType = std::pair<Key, Value>;
1092     using ComplexTypeConst = std::pair<const Key, Value>;
1093 
1094     using DefaultCompare = oneapi::tbb::detail::d1::tbb_hash_compare<Key>;
1095     using Compare = debug_hash_compare<Key>;
1096     using DefaultAllocator = oneapi::tbb::tbb_allocator<ComplexTypeConst>;
1097     using Allocator = std::allocator<ComplexTypeConst>;
1098 
1099     std::vector<ComplexType> v;
1100     auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") };
1101     Compare compare;
1102     Allocator allocator;
1103 
1104     // check TMap(InputIterator, InputIterator)
1105     TMap m1(v.begin(), v.end());
1106     static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1107 
1108     // check TMap(InputIterator, InputIterator, HashCompare)
1109     TMap m2(v.begin(), v.end(), compare);
1110     static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value);
1111 
1112     // check TMap(InputIterator, InputIterator, HashCompare, Allocator)
1113     TMap m3(v.begin(), v.end(), compare, allocator);
1114     static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value);
1115 
1116     // check TMap(InputIterator, InputIterator, Allocator)
1117     TMap m4(v.begin(), v.end(), allocator);
1118     static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1119 
1120     // check TMap(std::initializer_list)
1121     TMap m5(l);
1122     static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1123 
1124     // check TMap(std::initializer_list, HashCompare)
1125     TMap m6(l, compare);
1126     static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value);
1127 
1128     // check TMap(std::initializer_list, HashCompare, Allocator)
1129     TMap m7(l, compare, allocator);
1130     static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value);
1131 
1132     // check TMap(std::initializer_list, Allocator)
1133     TMap m8(l, allocator);
1134     static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1135 
1136     // check TMap(TMap &)
1137     TMap m9(m1);
1138     static_assert(std::is_same<decltype(m9), decltype(m1)>::value);
1139 
1140     // check TMap(TMap &, Allocator)
1141     TMap m10(m4, allocator);
1142     static_assert(std::is_same<decltype(m10), decltype(m4)>::value);
1143 
1144     // check TMap(TMap &&)
1145     TMap m11(std::move(m1));
1146     static_assert(std::is_same<decltype(m11), decltype(m1)>::value);
1147 
1148     // check TMap(TMap &&, Allocator)
1149     TMap m12(std::move(m4), allocator);
1150     static_assert(std::is_same<decltype(m12), decltype(m4)>::value);
1151 }
1152 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1153 
1154 template <typename CHMapType>
1155 void test_comparisons_basic() {
1156     using comparisons_testing::testEqualityComparisons;
1157     CHMapType c1, c2;
1158     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1159 
1160     c1.emplace(1, 1);
1161     testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
1162 
1163     c2.emplace(1, 1);
1164     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1165 }
1166 
1167 template <typename TwoWayComparableContainerType>
1168 void test_two_way_comparable_chmap() {
1169     TwoWayComparableContainerType c1, c2;
1170     c1.emplace(1, 1);
1171     c2.emplace(1, 1);
1172     comparisons_testing::TwoWayComparable::reset();
1173     REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result");
1174     comparisons_testing::check_equality_comparison();
1175     REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result");
1176     comparisons_testing::check_equality_comparison();
1177 }
1178 
1179 void TestCHMapComparisons() {
1180     using integral_container = oneapi::tbb::concurrent_hash_map<int, int>;
1181     using two_way_comparable_container = oneapi::tbb::concurrent_hash_map<comparisons_testing::TwoWayComparable,
1182                                                                           comparisons_testing::TwoWayComparable>;
1183 
1184     test_comparisons_basic<integral_container>();
1185     test_comparisons_basic<two_way_comparable_container>();
1186     test_two_way_comparable_chmap<two_way_comparable_container>();
1187 }
1188 
1189 template <typename Iterator, typename CHMapType>
1190 void TestCHMapIteratorComparisonsBasic( CHMapType& chmap ) {
1191     REQUIRE_MESSAGE(!chmap.empty(), "Incorrect test setup");
1192     using namespace comparisons_testing;
1193     Iterator it1, it2;
1194     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1195     it1 = chmap.begin();
1196     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1197     it2 = chmap.begin();
1198     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1199     it2 = chmap.end();
1200     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1201 }
1202 
1203 void TestCHMapIteratorComparisons() {
1204     using chmap_type = oneapi::tbb::concurrent_hash_map<int, int>;
1205     using value_type = typename chmap_type::value_type;
1206     chmap_type chmap = {value_type{1, 1}, value_type{2, 2}, value_type{3, 3}};
1207     TestCHMapIteratorComparisonsBasic<typename chmap_type::iterator>(chmap);
1208     const chmap_type& cchmap = chmap;
1209     TestCHMapIteratorComparisonsBasic<typename chmap_type::const_iterator>(cchmap);
1210 }
1211 
1212 //! Test consruction with hash_compare
1213 //! \brief \ref interface \ref requirement
1214 TEST_CASE("testing consruction with hash_compare") {
1215     TestHashCompareConstructors();
1216 }
1217 
1218 //! Test concurrent_hash_map member types
1219 //! \brief \ref interface \ref requirement
1220 TEST_CASE("test types"){
1221     test_member_types<oneapi::tbb::concurrent_hash_map>();
1222 }
1223 
1224 //! Test swap and clear operations
1225 //! \brief \ref interface \ref requirement
1226 TEST_CASE("test copy operations") {
1227     TestCopy();
1228 }
1229 
1230 //! Test rehash operation
1231 //! \brief \ref interface \ref requirement
1232 TEST_CASE("test rehash operation") {
1233     TestRehash();
1234 }
1235 
1236 //! Test assignment operation
1237 //! \brief \ref interface \ref requirement
1238 TEST_CASE("test assignment operation") {
1239     TestAssignment();
1240 }
1241 
1242 //! Test iterators and ranges
1243 //! \brief \ref interface \ref requirement
1244 TEST_CASE("test iterators and ranges") {
1245     TestIteratorsAndRanges();
1246 }
1247 
1248 //! Test work with initializer_list
1249 //! \brief \ref interface \ref requirement
1250 TEST_CASE("test work with initializer_list") {
1251     TestInitList();
1252 }
1253 
1254 #if TBB_USE_EXCEPTIONS
1255 //! Test exception safety
1256 //! \brief \ref requirement
1257 TEST_CASE("test exception safety") {
1258     TestExceptions();
1259 }
1260 
1261 //! Test exceptions safety guarantees for move constructor
1262 //! \brief \ref requirement
1263 TEST_CASE("test move support with exceptions") {
1264     move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>();
1265     move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>();
1266 }
1267 #endif
1268 
1269 //! Test move constructor
1270 //! \brief \ref interface \ref requirement
1271 TEST_CASE("testing move constructor"){
1272     move_support_tests::test_move_constructor<hash_map_traits>();
1273 }
1274 
1275 //! Test move assign operator
1276 //! \brief \ref interface \ref requirement
1277 TEST_CASE("testing move assign operator"){
1278     move_support_tests::test_move_assignment<hash_map_traits>();
1279 }
1280 
1281 //! Test insert and empace
1282 //! \brief \ref interface \ref requirement
1283 TEST_CASE("testing concurrent insert and emplace"){
1284     int n=250000;
1285     {
1286         DataStateTrackedTable table;
1287         DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 );
1288     }
1289     {
1290         DataStateTrackedTable table;
1291         DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 );
1292     }
1293 }
1294 
1295 //! Test allocator traits
1296 //! \brief \ref requirement
1297 TEST_CASE("testing allocator traits") {
1298     test_allocator_traits_support<hash_map_traits>();
1299 }
1300 
1301 //! Test concurrent operations
1302 //! \brief \ref requirement
1303 TEST_CASE("testing concurrency"){
1304     for (std::size_t p = 1; p <= 4; ++p) {
1305         oneapi::tbb::global_control limit(oneapi::tbb::global_control::max_allowed_parallelism, p);
1306         TestInsertFindErase(p);
1307         TestConcurrency(p);
1308     }
1309 }
1310 
1311 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1312 //! Test deduction guides
1313 //! \brief \ref interface
1314 TEST_CASE("testing deduction guides") {
1315     TestDeductionGuides<oneapi::tbb::concurrent_hash_map>();
1316 }
1317 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1318 
1319 //! \brief \ref interface \ref requirement
1320 TEST_CASE("concurrent_hash_map comparisons") {
1321     TestCHMapComparisons();
1322 }
1323 
1324 //! \brief \ref interface \ref requirement
1325 TEST_CASE("concurrent_hash_map iterator comparisons") {
1326     TestCHMapIteratorComparisons();
1327 }
1328