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         for ( int i = 0; i < 2; ++i ) {
921             if ( i==0 )
922                 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread );
923             else
924                 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread );
925             CHECK(MyDataCount == m);
926             TraverseTable( table, n, m );
927             ParallelTraverseTable( table, n, m );
928 
929             DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread );
930             CHECK(MyDataCount == m);
931 
932             DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread );
933             CHECK(MyDataCount == m);
934 
935             EraseCount = 0;
936             DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread );
937             CHECK(EraseCount == m);
938             CHECK(MyDataCount == 0);
939             TraverseTable( table, n, 0 );
940 
941             table.clear();
942         }
943 
944         if( nthread > 1 ) {
945             YourTable ie_table;
946             for( int i=0; i<IE_SIZE; ++i )
947                 InsertEraseCount[i] = 0;
948             DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
949             for( int i=0; i<IE_SIZE; ++i )
950                 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i)));
951 
952             DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread);
953             utils::SpinBarrier barrier(nthread);
954             INFO("testing erase on fake exclusive accessor\n");
955             utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table));
956         }
957     }
958     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
959     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
960     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
961 }
962 
963 std::atomic<int> Counter;
964 
965 class AddToTable {
966     test_table_type& my_table;
967     const std::size_t my_nthread;
968     const int my_m;
969 public:
970     AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
971     void operator()( std::size_t ) const {
972         for( int i=0; i<my_m; ++i ) {
973             // Busy wait to synchronize threads
974             int j = 0;
975             while( Counter<i ) {
976                 if( ++j==1000000 ) {
977                     // If Counter<i after a million iterations, then we almost surely have
978                     // more logical threads than physical threads, and should yield in
979                     // order to let suspended logical threads make progress.
980                     j = 0;
981                     utils::yield();
982                 }
983             }
984             // Now all threads attempt to simultaneously insert a key.
985             int k;
986             {
987                 test_table_type::accessor a;
988                 MyKey key = MyKey::make(i);
989                 if( my_table.insert( a, key ) )
990                     a->second.set_value( 1 );
991                 else
992                     a->second.set_value( a->second.value_of()+1 );
993                 k = a->second.value_of();
994             }
995             if( std::size_t(k) == my_nthread )
996                 Counter=i+1;
997         }
998     }
999 };
1000 
1001 class RemoveFromTable {
1002     test_table_type& my_table;
1003     const int my_m;
1004 public:
1005     RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {}
1006     void operator()(std::size_t) const {
1007         for( int i=0; i<my_m; ++i ) {
1008             bool b;
1009             if(i&4) {
1010                 if(i&8) {
1011                     test_table_type::const_accessor a;
1012                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1013                 } else {
1014                     test_table_type::accessor a;
1015                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1016                 }
1017             } else
1018                 b = my_table.erase( MyKey::make(i) );
1019             if( b ) ++EraseCount;
1020         }
1021     }
1022 };
1023 
1024 void TestConcurrency( std::size_t nthread ) {
1025     INFO("testing multiple insertions/deletions of same key with " << nthread << " threads");
1026     test_allocator_type::init_counters();
1027     {
1028         CHECK( MyDataCount==0);
1029         test_table_type table;
1030         const int m = 1000;
1031         Counter = 0;
1032         oneapi::tbb::tick_count t0 = oneapi::tbb::tick_count::now();
1033         utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) );
1034         REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" );
1035 
1036         EraseCount = 0;
1037         t0 = oneapi::tbb::tick_count::now();
1038         utils::NativeParallelFor( nthread, RemoveFromTable(table,m) );
1039         REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1040         REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken");
1041 
1042     }
1043     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
1044     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
1045     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
1046     REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1047 }
1048 
1049 template<typename Key>
1050 struct non_default_constructible_hash_compare : oneapi::tbb::detail::d1::tbb_hash_compare<Key> {
1051     non_default_constructible_hash_compare() {
1052         REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument");
1053     }
1054 
1055     non_default_constructible_hash_compare(int) {}
1056 };
1057 
1058 void TestHashCompareConstructors() {
1059     using key_type = int;
1060     using map_type = oneapi::tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>;
1061 
1062     non_default_constructible_hash_compare<key_type> compare(0);
1063     map_type::allocator_type allocator;
1064 
1065     map_type map1(compare);
1066     map_type map2(compare, allocator);
1067 
1068     map_type map3(1, compare);
1069     map_type map4(1, compare, allocator);
1070 
1071     std::vector<map_type::value_type> reference_vector;
1072     map_type map5(reference_vector.begin(), reference_vector.end(), compare);
1073     map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator);
1074 
1075     map_type map7({}, compare);
1076     map_type map8({}, compare, allocator);
1077 }
1078 
1079 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1080 template <typename T>
1081 struct debug_hash_compare : public oneapi::tbb::detail::d1::tbb_hash_compare<T> {};
1082 
1083 template <template <typename...> typename TMap>
1084 void TestDeductionGuides() {
1085     using Key = int;
1086     using Value = std::string;
1087 
1088     using ComplexType = std::pair<Key, Value>;
1089     using ComplexTypeConst = std::pair<const Key, Value>;
1090 
1091     using DefaultCompare = oneapi::tbb::detail::d1::tbb_hash_compare<Key>;
1092     using Compare = debug_hash_compare<Key>;
1093     using DefaultAllocator = oneapi::tbb::tbb_allocator<ComplexTypeConst>;
1094     using Allocator = std::allocator<ComplexTypeConst>;
1095 
1096     std::vector<ComplexType> v;
1097     auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") };
1098     Compare compare;
1099     Allocator allocator;
1100 
1101     // check TMap(InputIterator, InputIterator)
1102     TMap m1(v.begin(), v.end());
1103     static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1104 
1105     // check TMap(InputIterator, InputIterator, HashCompare)
1106     TMap m2(v.begin(), v.end(), compare);
1107     static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value);
1108 
1109     // check TMap(InputIterator, InputIterator, HashCompare, Allocator)
1110     TMap m3(v.begin(), v.end(), compare, allocator);
1111     static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value);
1112 
1113     // check TMap(InputIterator, InputIterator, Allocator)
1114     TMap m4(v.begin(), v.end(), allocator);
1115     static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1116 
1117     // check TMap(std::initializer_list)
1118     TMap m5(l);
1119     static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1120 
1121     // check TMap(std::initializer_list, HashCompare)
1122     TMap m6(l, compare);
1123     static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value);
1124 
1125     // check TMap(std::initializer_list, HashCompare, Allocator)
1126     TMap m7(l, compare, allocator);
1127     static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value);
1128 
1129     // check TMap(std::initializer_list, Allocator)
1130     TMap m8(l, allocator);
1131     static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1132 
1133     // check TMap(TMap &)
1134     TMap m9(m1);
1135     static_assert(std::is_same<decltype(m9), decltype(m1)>::value);
1136 
1137     // check TMap(TMap &, Allocator)
1138     TMap m10(m4, allocator);
1139     static_assert(std::is_same<decltype(m10), decltype(m4)>::value);
1140 
1141     // check TMap(TMap &&)
1142     TMap m11(std::move(m1));
1143     static_assert(std::is_same<decltype(m11), decltype(m1)>::value);
1144 
1145     // check TMap(TMap &&, Allocator)
1146     TMap m12(std::move(m4), allocator);
1147     static_assert(std::is_same<decltype(m12), decltype(m4)>::value);
1148 }
1149 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1150 
1151 template <typename CHMapType>
1152 void test_comparisons_basic() {
1153     using comparisons_testing::testEqualityComparisons;
1154     CHMapType c1, c2;
1155     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1156 
1157     c1.emplace(1, 1);
1158     testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
1159 
1160     c2.emplace(1, 1);
1161     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1162 }
1163 
1164 template <typename TwoWayComparableContainerType>
1165 void test_two_way_comparable_chmap() {
1166     TwoWayComparableContainerType c1, c2;
1167     c1.emplace(1, 1);
1168     c2.emplace(1, 1);
1169     comparisons_testing::TwoWayComparable::reset();
1170     REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result");
1171     comparisons_testing::check_equality_comparison();
1172     REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result");
1173     comparisons_testing::check_equality_comparison();
1174 }
1175 
1176 void TestCHMapComparisons() {
1177     using integral_container = oneapi::tbb::concurrent_hash_map<int, int>;
1178     using two_way_comparable_container = oneapi::tbb::concurrent_hash_map<comparisons_testing::TwoWayComparable,
1179                                                                           comparisons_testing::TwoWayComparable>;
1180 
1181     test_comparisons_basic<integral_container>();
1182     test_comparisons_basic<two_way_comparable_container>();
1183     test_two_way_comparable_chmap<two_way_comparable_container>();
1184 }
1185 
1186 template <typename Iterator, typename CHMapType>
1187 void TestCHMapIteratorComparisonsBasic( CHMapType& chmap ) {
1188     REQUIRE_MESSAGE(!chmap.empty(), "Incorrect test setup");
1189     using namespace comparisons_testing;
1190     Iterator it1, it2;
1191     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1192     it1 = chmap.begin();
1193     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1194     it2 = chmap.begin();
1195     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1196     it2 = chmap.end();
1197     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1198 }
1199 
1200 void TestCHMapIteratorComparisons() {
1201     using chmap_type = oneapi::tbb::concurrent_hash_map<int, int>;
1202     using value_type = typename chmap_type::value_type;
1203     chmap_type chmap = {value_type{1, 1}, value_type{2, 2}, value_type{3, 3}};
1204     TestCHMapIteratorComparisonsBasic<typename chmap_type::iterator>(chmap);
1205     const chmap_type& cchmap = chmap;
1206     TestCHMapIteratorComparisonsBasic<typename chmap_type::const_iterator>(cchmap);
1207 }
1208 
1209 //! Test consruction with hash_compare
1210 //! \brief \ref interface \ref requirement
1211 TEST_CASE("testing consruction with hash_compare") {
1212     TestHashCompareConstructors();
1213 }
1214 
1215 //! Test concurrent_hash_map member types
1216 //! \brief \ref interface \ref requirement
1217 TEST_CASE("test types"){
1218     test_member_types<oneapi::tbb::concurrent_hash_map>();
1219 }
1220 
1221 //! Test swap and clear operations
1222 //! \brief \ref interface \ref requirement
1223 TEST_CASE("test copy operations") {
1224     TestCopy();
1225 }
1226 
1227 //! Test rehash operation
1228 //! \brief \ref interface \ref requirement
1229 TEST_CASE("test rehash operation") {
1230     TestRehash();
1231 }
1232 
1233 //! Test assignment operation
1234 //! \brief \ref interface \ref requirement
1235 TEST_CASE("test assignment operation") {
1236     TestAssignment();
1237 }
1238 
1239 //! Test iterators and ranges
1240 //! \brief \ref interface \ref requirement
1241 TEST_CASE("test iterators and ranges") {
1242     TestIteratorsAndRanges();
1243 }
1244 
1245 //! Test work with initializer_list
1246 //! \brief \ref interface \ref requirement
1247 TEST_CASE("test work with initializer_list") {
1248     TestInitList();
1249 }
1250 
1251 #if TBB_USE_EXCEPTIONS
1252 //! Test exception safety
1253 //! \brief \ref requirement
1254 TEST_CASE("test exception safety") {
1255     TestExceptions();
1256 }
1257 
1258 //! Test exceptions safety guarantees for move constructor
1259 //! \brief \ref requirement
1260 TEST_CASE("test move support with exceptions") {
1261     move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>();
1262     move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>();
1263 }
1264 #endif
1265 
1266 //! Test move constructor
1267 //! \brief \ref interface \ref requirement
1268 TEST_CASE("testing move constructor"){
1269     move_support_tests::test_move_constructor<hash_map_traits>();
1270 }
1271 
1272 //! Test move assign operator
1273 //! \brief \ref interface \ref requirement
1274 TEST_CASE("testing move assign operator"){
1275     move_support_tests::test_move_assignment<hash_map_traits>();
1276 }
1277 
1278 //! Test insert and empace
1279 //! \brief \ref interface \ref requirement
1280 TEST_CASE("testing concurrent insert and emplace"){
1281     int n=250000;
1282     {
1283         DataStateTrackedTable table;
1284         DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 );
1285     }
1286     {
1287         DataStateTrackedTable table;
1288         DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 );
1289     }
1290 }
1291 
1292 //! Test allocator traits
1293 //! \brief \ref requirement
1294 TEST_CASE("testing allocator traits") {
1295     test_allocator_traits_support<hash_map_traits>();
1296 }
1297 
1298 //! Test concurrent operations
1299 //! \brief \ref requirement
1300 TEST_CASE("testing concurrency"){
1301     for (std::size_t p = 1; p <= 4; ++p) {
1302         oneapi::tbb::global_control limit(oneapi::tbb::global_control::max_allowed_parallelism, p);
1303         TestInsertFindErase(p);
1304         TestConcurrency(p);
1305     }
1306 }
1307 
1308 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1309 //! Test deduction guides
1310 //! \brief \ref interface
1311 TEST_CASE("testing deduction guides") {
1312     TestDeductionGuides<oneapi::tbb::concurrent_hash_map>();
1313 }
1314 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1315 
1316 //! \brief \ref interface \ref requirement
1317 TEST_CASE("concurrent_hash_map comparisons") {
1318     TestCHMapComparisons();
1319 }
1320 
1321 //! \brief \ref interface \ref requirement
1322 TEST_CASE("concurrent_hash_map iterator comparisons") {
1323     TestCHMapIteratorComparisons();
1324 }
1325