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 void TestInitList(){
426     using namespace initializer_list_support_tests;
427     INFO("testing initializer_list methods \n");
428 
429     using ch_map_type = oneapi::tbb::concurrent_hash_map<int,int>;
430     std::initializer_list<ch_map_type::value_type> pairs_il = {{1,1},{2,2},{3,3},{4,4},{5,5}};
431 
432     test_initializer_list_support_without_assign<ch_map_type, test_insert>( pairs_il );
433     test_initializer_list_support_without_assign<ch_map_type, test_insert>( {} );
434 }
435 
436 template <typename base_alloc_type>
437 class only_node_counting_allocator : public StaticSharedCountingAllocator<base_alloc_type> {
438     using base_type = StaticSharedCountingAllocator<base_alloc_type>;
439     using base_traits = oneapi::tbb::detail::allocator_traits<base_alloc_type>;
440 public:
441     template<typename U>
442     struct rebind {
443         using other = only_node_counting_allocator<typename base_traits::template rebind_alloc<U>>;
444     };
445 
446     only_node_counting_allocator() : base_type() {}
447     only_node_counting_allocator(const only_node_counting_allocator& a) : base_type(a) {}
448 
449     template<typename U>
450     only_node_counting_allocator(const only_node_counting_allocator<U>& a) : base_type(a) {}
451 
452     typename base_type::value_type* allocate(const std::size_t n) {
453         if ( n > 1) {
454             return base_alloc_type::allocate(n);
455         } else {
456             return base_type::allocate(n);
457         }
458     }
459 };
460 
461 #if TBB_USE_EXCEPTIONS
462 void TestExceptions() {
463     using allocator_type = only_node_counting_allocator<oneapi::tbb::tbb_allocator<std::pair<const MyKey, MyData2>>>;
464     using throwing_table = oneapi::tbb::concurrent_hash_map<MyKey, MyData2, MyHashCompare, allocator_type>;
465     enum methods {
466         zero_method = 0,
467         ctor_copy, op_assign, op_insert,
468         all_methods
469     };
470 
471     INFO("testing exception-safety guarantees\n");
472     throwing_table src;
473     FillTable( src, 1000 );
474     CHECK(MyDataCount==1000);
475 
476     try {
477         for(int t = 0; t < 2; t++) // exception type
478         for(int m = zero_method+1; m < all_methods; m++)
479         {
480             allocator_type a;
481             allocator_type::init_counters();
482             if(t) MyDataCountLimit = 101;
483             else a.set_limits(101);
484             throwing_table victim(a);
485             MyDataCount = 0;
486 
487             try {
488                 switch(m) {
489                 case ctor_copy: {
490                         throwing_table acopy(src, a);
491                     } break;
492                 case op_assign: {
493                         victim = src;
494                     } break;
495                 case op_insert: {
496                         // Insertion in cpp11 don't make copy constructions
497                         // during the insertion, so we need to decrement limit
498                         // to throw an exception in the right place and to prevent
499                         // successful insertion of one unexpected item
500                         if (MyDataCountLimit)
501                             --MyDataCountLimit;
502                         FillTable( victim, 1000 );
503                     } break;
504                 default:;
505                 }
506                 REQUIRE_MESSAGE(false, "should throw an exception");
507             } catch(std::bad_alloc &e) {
508                 MyDataCountLimit = 0;
509                 size_t size = victim.size();
510                 switch(m) {
511                 case op_assign:
512                     REQUIRE_MESSAGE( MyDataCount==100, "data leak?" );
513                     CHECK(size>=100);
514                     utils_fallthrough;
515                 case ctor_copy:
516                     CheckTable(src, 1000);
517                     break;
518                 case op_insert:
519                     CHECK(size==size_t(100-t));
520                     REQUIRE_MESSAGE( MyDataCount==100-t, "data leak?" );
521                     CheckTable(victim, 100-t);
522                     break;
523 
524                 default:; // nothing to check here
525                 }
526                 INFO("Exception "<< m << " : " << e.what() << "- ok ()");
527             }
528             catch ( ... ) {
529                 REQUIRE_MESSAGE( false, "Unrecognized exception" );
530             }
531         }
532     } catch(...) {
533         REQUIRE_MESSAGE(false, "unexpected exception");
534     }
535     src.clear(); MyDataCount = 0;
536     allocator_type::max_items = 0;
537 }
538 #endif
539 
540 struct default_container_traits {
541     template <typename container_type, typename iterator_type>
542     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end){
543         container_type* ptr = reinterpret_cast<container_type*>(&storage);
544         new (ptr) container_type(begin, end);
545         return *ptr;
546     }
547 
548     template <typename container_type, typename iterator_type, typename allocator_type>
549     static container_type& construct_container(typename std::aligned_storage<sizeof(container_type)>::type& storage, iterator_type begin, iterator_type end, allocator_type const& a){
550         container_type* ptr = reinterpret_cast<container_type*>(&storage);
551         new (ptr) container_type(begin, end, a);
552         return *ptr;
553     }
554 };
555 
556 struct hash_map_traits : default_container_traits {
557     enum{ expected_number_of_items_to_allocate_for_steal_move = 0 };
558 
559     template<typename T>
560     struct hash_compare {
561         bool equal( const T& lhs, const T& rhs ) const {
562             return lhs==rhs;
563         }
564         size_t hash( const T& k ) const {
565             return my_hash_func(k);
566         }
567         std::hash<T> my_hash_func;
568     };
569 
570     template <typename T, typename Allocator>
571     using container_type = oneapi::tbb::concurrent_hash_map<T, T, hash_compare<T>, Allocator>;
572 
573     template <typename T>
574     using container_value_type = std::pair<const T, T>;
575 
576     template<typename element_type, typename allocator_type>
577     struct apply {
578         using type = oneapi::tbb::concurrent_hash_map<element_type, element_type, hash_compare<element_type>, allocator_type>;
579     };
580 
581     using init_iterator_type = move_support_tests::FooPairIterator;
582     template <typename hash_map_type, typename iterator>
583     static bool equal(hash_map_type const& c, iterator begin, iterator end){
584         bool equal_sizes = ( static_cast<size_t>(std::distance(begin, end)) == c.size() );
585         if (!equal_sizes)
586             return false;
587 
588         for (iterator it = begin; it != end; ++it ){
589             if (c.count( (*it).first) == 0){
590                 return false;
591             }
592         }
593         return true;
594     }
595 };
596 
597 using DataStateTrackedTable = oneapi::tbb::concurrent_hash_map<MyKey, move_support_tests::Foo, MyHashCompare>;
598 
599 struct RvalueInsert {
600     static void apply( DataStateTrackedTable& table, int i ) {
601         DataStateTrackedTable::accessor a;
602         int next = i + 1;
603         REQUIRE_MESSAGE((table.insert( a, std::make_pair(MyKey::make(i), move_support_tests::Foo(next)))),
604             "already present while should not ?" );
605         CHECK((*a).second == next);
606         CHECK((*a).second.state == StateTrackableBase::MoveInitialized);
607     }
608 };
609 
610 struct Emplace {
611     template <typename Accessor>
612     static void apply_impl( DataStateTrackedTable& table, int i) {
613         Accessor a;
614         REQUIRE_MESSAGE((table.emplace( a, MyKey::make(i), (i + 1))),
615                 "already present while should not ?" );
616         CHECK((*a).second == i + 1);
617         CHECK((*a).second.state == StateTrackableBase::DirectInitialized);
618     }
619 
620     static void apply( DataStateTrackedTable& table, int i ) {
621         // TODO: investigate ability to rewrite apply methods with use apply_imp method.
622         if (i % 2) {
623             apply_impl<DataStateTrackedTable::accessor>(table, i);
624         } else {
625             apply_impl<DataStateTrackedTable::const_accessor>(table, i);
626         }
627     }
628 };
629 
630 template<typename Op, typename test_table_type>
631 class TableOperation {
632     test_table_type& my_table;
633 public:
634     void operator()( const oneapi::tbb::blocked_range<int>& range ) const {
635         for( int i=range.begin(); i!=range.end(); ++i )
636             Op::apply(my_table,i);
637     }
638     TableOperation( test_table_type& table ) : my_table(table) {}
639 };
640 
641 bool UseKey( size_t i ) {
642     return (i&3)!=3;
643 }
644 
645 struct Insert {
646     static void apply( test_table_type& table, int i ) {
647         if( UseKey(i) ) {
648             if( i&4 ) {
649                 test_table_type::accessor a;
650                 table.insert( a, MyKey::make(i) );
651                 if( i&1 )
652                     (*a).second.set_value(i*i);
653                 else
654                     a->second.set_value(i*i);
655             } else
656                 if( i&1 ) {
657                     test_table_type::accessor a;
658                     table.insert( a, std::make_pair(MyKey::make(i), MyData(i*i)) );
659                     CHECK((*a).second.value_of()==i*i);
660                 } else {
661                     test_table_type::const_accessor ca;
662                     table.insert( ca, std::make_pair(MyKey::make(i), MyData(i*i)) );
663                     CHECK(ca->second.value_of()==i*i);
664                 }
665         }
666     }
667 };
668 
669 struct Find {
670     static void apply( test_table_type& table, int i ) {
671         test_table_type::accessor a;
672         const test_table_type::accessor& ca = a;
673         bool b = table.find( a, MyKey::make(i) );
674         CHECK(b==!a.empty());
675         if( b ) {
676             if( !UseKey(i) )
677                 REPORT("Line %d: unexpected key %d present\n",__LINE__,i);
678             CHECK(ca->second.value_of()==i*i);
679             CHECK((*ca).second.value_of()==i*i);
680             if( i&1 )
681                 ca->second.set_value( ~ca->second.value_of() );
682             else
683                 (*ca).second.set_value( ~ca->second.value_of() );
684         } else {
685             if( UseKey(i) )
686                 REPORT("Line %d: key %d missing\n",__LINE__,i);
687         }
688     }
689 };
690 
691 struct FindConst {
692     static void apply( const test_table_type& table, int i ) {
693         test_table_type::const_accessor a;
694         const test_table_type::const_accessor& ca = a;
695         bool b = table.find( a, MyKey::make(i) );
696         CHECK(b==(table.count(MyKey::make(i))>0));
697         CHECK(b==!a.empty());
698         CHECK(b==UseKey(i));
699         if( b ) {
700             CHECK(ca->second.value_of()==~(i*i));
701             CHECK((*ca).second.value_of()==~(i*i));
702         }
703     }
704 };
705 
706 struct InsertInitList {
707     static void apply( test_table_type& table, int i ) {
708         if ( UseKey( i ) ) {
709             // TODO: investigate why the following sequence causes an additional allocation sometimes:
710             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i ) );
711             // table.insert( test_table_type::value_type( MyKey::make( i ), i*i+1 ) );
712             std::initializer_list<test_table_type::value_type> il = {
713                 test_table_type::value_type( MyKey::make( i ), i*i )
714                 /*, test_table_type::value_type( MyKey::make( i ), i*i+1 ) */
715                                                                     };
716             table.insert( il );
717         }
718     }
719 };
720 
721 template<typename Op, typename TableType>
722 void DoConcurrentOperations( TableType& table, int n, const char* what, std::size_t nthread ) {
723     INFO("testing " << what << " with " << nthread << " threads");
724     oneapi::tbb::parallel_for(oneapi::tbb::blocked_range<int>(0, n ,100), TableOperation<Op, TableType>(table));
725 }
726 
727 //! Test traversing the table with an iterator.
728 void TraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
729     INFO("testing traversal\n");
730     size_t actual_size = table.size();
731     CHECK(actual_size==expected_size);
732     size_t count = 0;
733     bool* array = new bool[n];
734     memset( array, 0, n*sizeof(bool) );
735     const test_table_type& const_table = table;
736     test_table_type::const_iterator ci = const_table.begin();
737     for( test_table_type::iterator i = table.begin(); i!=table.end(); ++i ) {
738         // Check iterator
739         int k = i->first.value_of();
740         CHECK(UseKey(k));
741         CHECK((*i).first.value_of()==k);
742         REQUIRE_MESSAGE((0<=k && size_t(k)<n), "out of bounds key" );
743         REQUIRE_MESSAGE( !array[k], "duplicate key" );
744         array[k] = true;
745         ++count;
746 
747         // Check lower/upper bounds
748         std::pair<test_table_type::iterator, test_table_type::iterator> er = table.equal_range(i->first);
749         std::pair<test_table_type::const_iterator, test_table_type::const_iterator> cer = const_table.equal_range(i->first);
750         CHECK((cer.first == er.first && cer.second == er.second));
751         CHECK(cer.first == i);
752         CHECK(std::distance(cer.first, cer.second) == 1);
753 
754         // Check const_iterator
755         test_table_type::const_iterator cic = ci++;
756         CHECK(cic->first.value_of()==k);
757         CHECK((*cic).first.value_of()==k);
758     }
759     CHECK(ci==const_table.end());
760     delete[] array;
761     if (count != expected_size) {
762         INFO("Line " << __LINE__ << ": count=" << count << " but should be " << expected_size);
763     }
764 }
765 
766 std::atomic<int> EraseCount;
767 
768 struct Erase {
769     static void apply( test_table_type& table, int i ) {
770         bool b;
771         if(i&4) {
772             if(i&8) {
773                 test_table_type::const_accessor a;
774                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
775             } else {
776                 test_table_type::accessor a;
777                 b = table.find( a, MyKey::make(i) ) && table.erase( a );
778             }
779         } else
780             b = table.erase( MyKey::make(i) );
781         if( b ) ++EraseCount;
782         CHECK(table.count(MyKey::make(i)) == 0);
783     }
784 };
785 
786 using YourTable = oneapi::tbb::concurrent_hash_map<MyKey, MyData, YourHashCompare>;
787 static const int IE_SIZE = 2;
788 std::atomic<YourTable::size_type> InsertEraseCount[IE_SIZE];
789 
790 struct InsertErase  {
791     static void apply( YourTable& table, int i ) {
792         if ( i%3 ) {
793             int key = i%IE_SIZE;
794             if ( table.insert( std::make_pair(MyKey::make(key), MyData2()) ) )
795                 ++InsertEraseCount[key];
796         } else {
797             int key = i%IE_SIZE;
798             if( i&1 ) {
799                 YourTable::accessor res;
800                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
801                     --InsertEraseCount[key];
802             } else {
803                 YourTable::const_accessor res;
804                 if(table.find( res, MyKey::make(key) ) && table.erase( res ) )
805                     --InsertEraseCount[key];
806             }
807         }
808     }
809 };
810 
811 struct InnerInsert {
812     static void apply( YourTable& table, int i ) {
813         YourTable::accessor a1, a2;
814         if(i&1) utils::yield();
815         table.insert( a1, MyKey::make(1) );
816         utils::yield();
817         table.insert( a2, MyKey::make(1 + (1<<30)) ); // the same chain
818         table.erase( a2 ); // if erase by key it would lead to deadlock for single thread
819     }
820 };
821 
822 struct FakeExclusive {
823     utils::SpinBarrier& barrier;
824     YourTable& table;
825     FakeExclusive(utils::SpinBarrier& b, YourTable&t) : barrier(b), table(t) {}
826     void operator()( std::size_t i ) const {
827         if(i) {
828             YourTable::const_accessor real_ca;
829             // const accessor on non-const table acquired as reader (shared)
830             CHECK(table.find(real_ca,MyKey::make(1)));
831             barrier.wait(); // item can be erased
832             std::this_thread::sleep_for(std::chrono::milliseconds(10)); // let it enter the erase
833             real_ca->second.value_of(); // check the state while holding accessor
834         } else {
835             YourTable::accessor fake_ca;
836             const YourTable &const_table = table;
837             // non-const accessor on const table acquired as reader (shared)
838             CHECK(const_table.find(fake_ca,MyKey::make(1)));
839             barrier.wait(); // readers acquired
840             // can mistakenly remove the item while other readers still refers to it
841             table.erase( fake_ca );
842         }
843     }
844 };
845 
846 using AtomicByte = std::atomic<unsigned char>;
847 
848 template<typename RangeType>
849 struct ParallelTraverseBody {
850     const size_t n;
851     AtomicByte* const array;
852     ParallelTraverseBody( AtomicByte array_[], size_t n_ ) :
853         n(n_),
854         array(array_)
855     {}
856     void operator()( const RangeType& range ) const {
857         for( typename RangeType::iterator i = range.begin(); i!=range.end(); ++i ) {
858             int k = i->first.value_of();
859             CHECK((0<=k && size_t(k)<n));
860             ++array[k];
861         }
862     }
863 };
864 
865 void Check( AtomicByte array[], size_t n, size_t expected_size ) {
866     if( expected_size )
867         for( size_t k=0; k<n; ++k ) {
868             if( array[k] != int(UseKey(k)) ) {
869                 REPORT("array[%d]=%d != %d=UseKey(%d)\n",
870                        int(k), int(array[k]), int(UseKey(k)), int(k));
871                 CHECK(false);
872             }
873         }
874 }
875 
876 //! Test traversing the table with a parallel range
877 void ParallelTraverseTable( test_table_type& table, size_t n, size_t expected_size ) {
878     INFO("testing parallel traversal\n");
879     CHECK(table.size()==expected_size);
880     AtomicByte* array = new AtomicByte[n];
881 
882     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
883     test_table_type::range_type r = table.range(10);
884     oneapi::tbb::parallel_for( r, ParallelTraverseBody<test_table_type::range_type>( array, n ));
885     Check( array, n, expected_size );
886 
887     const test_table_type& const_table = table;
888     memset( static_cast<void*>(array), 0, n*sizeof(AtomicByte) );
889     test_table_type::const_range_type cr = const_table.range(10);
890     oneapi::tbb::parallel_for( cr, ParallelTraverseBody<test_table_type::const_range_type>( array, n ));
891     Check( array, n, expected_size );
892 
893     delete[] array;
894 }
895 
896 void TestInsertFindErase( std::size_t nthread ) {
897     int n=250000;
898 
899     // compute m = number of unique keys
900     int m = 0;
901     for( int i=0; i<n; ++i )
902         m += UseKey(i);
903     {
904         test_allocator_type alloc;
905         test_allocator_type::init_counters();
906         CHECK(MyDataCount==0);
907         test_table_type table(alloc);
908         TraverseTable(table,n,0);
909         ParallelTraverseTable(table,n,0);
910 
911         int expected_allocs = 0, expected_frees = 100;
912         for ( int i = 0; i < 2; ++i ) {
913             if ( i==0 )
914                 DoConcurrentOperations<InsertInitList, test_table_type>( table, n, "insert(std::initializer_list)", nthread );
915             else
916                 DoConcurrentOperations<Insert, test_table_type>( table, n, "insert", nthread );
917             CHECK(MyDataCount == m);
918             TraverseTable( table, n, m );
919             ParallelTraverseTable( table, n, m );
920             expected_allocs += m;
921 
922             DoConcurrentOperations<Find, test_table_type>( table, n, "find", nthread );
923             CHECK(MyDataCount == m);
924 
925             DoConcurrentOperations<FindConst, test_table_type>( table, n, "find(const)", nthread );
926             CHECK(MyDataCount == m);
927 
928             EraseCount = 0;
929             DoConcurrentOperations<Erase, test_table_type>( table, n, "erase", nthread );
930             CHECK(EraseCount == m);
931             CHECK(MyDataCount == 0);
932             TraverseTable( table, n, 0 );
933             expected_frees += m;
934 
935             table.clear();
936         }
937 
938         if( nthread > 1 ) {
939             YourTable ie_table;
940             for( int i=0; i<IE_SIZE; ++i )
941                 InsertEraseCount[i] = 0;
942             DoConcurrentOperations<InsertErase,YourTable>(ie_table,n/2,"insert_erase",nthread);
943             for( int i=0; i<IE_SIZE; ++i )
944                 CHECK(InsertEraseCount[i]==ie_table.count(MyKey::make(i)));
945 
946             DoConcurrentOperations<InnerInsert, YourTable>(ie_table,2000,"inner insert",nthread);
947             utils::SpinBarrier barrier(nthread);
948             INFO("testing erase on fake exclusive accessor\n");
949             utils::NativeParallelFor( nthread, FakeExclusive(barrier, ie_table));
950         }
951     }
952     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
953     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
954     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
955 }
956 
957 std::atomic<int> Counter;
958 
959 class AddToTable {
960     test_table_type& my_table;
961     const std::size_t my_nthread;
962     const int my_m;
963 public:
964     AddToTable( test_table_type& table, std::size_t nthread, int m ) : my_table(table), my_nthread(nthread), my_m(m) {}
965     void operator()( std::size_t ) const {
966         for( int i=0; i<my_m; ++i ) {
967             // Busy wait to synchronize threads
968             int j = 0;
969             while( Counter<i ) {
970                 if( ++j==1000000 ) {
971                     // If Counter<i after a million iterations, then we almost surely have
972                     // more logical threads than physical threads, and should yield in
973                     // order to let suspended logical threads make progress.
974                     j = 0;
975                     utils::yield();
976                 }
977             }
978             // Now all threads attempt to simultaneously insert a key.
979             int k;
980             {
981                 test_table_type::accessor a;
982                 MyKey key = MyKey::make(i);
983                 if( my_table.insert( a, key ) )
984                     a->second.set_value( 1 );
985                 else
986                     a->second.set_value( a->second.value_of()+1 );
987                 k = a->second.value_of();
988             }
989             if( std::size_t(k) == my_nthread )
990                 Counter=i+1;
991         }
992     }
993 };
994 
995 class RemoveFromTable {
996     test_table_type& my_table;
997     const int my_m;
998 public:
999     RemoveFromTable( test_table_type& table, int m ) : my_table(table), my_m(m) {}
1000     void operator()(std::size_t) const {
1001         for( int i=0; i<my_m; ++i ) {
1002             bool b;
1003             if(i&4) {
1004                 if(i&8) {
1005                     test_table_type::const_accessor a;
1006                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1007                 } else {
1008                     test_table_type::accessor a;
1009                     b = my_table.find( a, MyKey::make(i) ) && my_table.erase( a );
1010                 }
1011             } else
1012                 b = my_table.erase( MyKey::make(i) );
1013             if( b ) ++EraseCount;
1014         }
1015     }
1016 };
1017 
1018 void TestConcurrency( std::size_t nthread ) {
1019     INFO("testing multiple insertions/deletions of same key with " << nthread << " threads");
1020     test_allocator_type::init_counters();
1021     {
1022         CHECK( MyDataCount==0);
1023         test_table_type table;
1024         const int m = 1000;
1025         Counter = 0;
1026         oneapi::tbb::tick_count t0 = oneapi::tbb::tick_count::now();
1027         utils::NativeParallelFor( nthread, AddToTable(table,nthread,m) );
1028         REQUIRE_MESSAGE( MyDataCount==m, "memory leak detected" );
1029 
1030         EraseCount = 0;
1031         t0 = oneapi::tbb::tick_count::now();
1032         utils::NativeParallelFor( nthread, RemoveFromTable(table,m) );
1033         REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1034         REQUIRE_MESSAGE(EraseCount==m, "return value of erase() is broken");
1035 
1036     }
1037     REQUIRE( test_allocator_type::items_constructed == test_allocator_type::items_destroyed );
1038     REQUIRE( test_allocator_type::items_allocated == test_allocator_type::items_freed );
1039     REQUIRE( test_allocator_type::allocations == test_allocator_type::frees );
1040     REQUIRE_MESSAGE(MyDataCount==0, "memory leak detected");
1041 }
1042 
1043 template<typename Key>
1044 struct non_default_constructible_hash_compare : oneapi::tbb::detail::d1::tbb_hash_compare<Key> {
1045     non_default_constructible_hash_compare() {
1046         REQUIRE_MESSAGE(false, "Hash compare object must not default construct during the construction of hash_map with compare argument");
1047     }
1048 
1049     non_default_constructible_hash_compare(int) {}
1050 };
1051 
1052 void TestHashCompareConstructors() {
1053     using key_type = int;
1054     using map_type = oneapi::tbb::concurrent_hash_map<key_type, key_type, non_default_constructible_hash_compare<key_type>>;
1055 
1056     non_default_constructible_hash_compare<key_type> compare(0);
1057     map_type::allocator_type allocator;
1058 
1059     map_type map1(compare);
1060     map_type map2(compare, allocator);
1061 
1062     map_type map3(1, compare);
1063     map_type map4(1, compare, allocator);
1064 
1065     std::vector<map_type::value_type> reference_vector;
1066     map_type map5(reference_vector.begin(), reference_vector.end(), compare);
1067     map_type map6(reference_vector.begin(), reference_vector.end(), compare, allocator);
1068 
1069     map_type map7({}, compare);
1070     map_type map8({}, compare, allocator);
1071 }
1072 
1073 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1074 template <typename T>
1075 struct debug_hash_compare : public oneapi::tbb::detail::d1::tbb_hash_compare<T> {};
1076 
1077 template <template <typename...> typename TMap>
1078 void TestDeductionGuides() {
1079     using Key = int;
1080     using Value = std::string;
1081 
1082     using ComplexType = std::pair<Key, Value>;
1083     using ComplexTypeConst = std::pair<const Key, Value>;
1084 
1085     using DefaultCompare = oneapi::tbb::detail::d1::tbb_hash_compare<Key>;
1086     using Compare = debug_hash_compare<Key>;
1087     using DefaultAllocator = oneapi::tbb::tbb_allocator<ComplexTypeConst>;
1088     using Allocator = std::allocator<ComplexTypeConst>;
1089 
1090     std::vector<ComplexType> v;
1091     auto l = { ComplexTypeConst(1, "one"), ComplexTypeConst(2, "two") };
1092     Compare compare;
1093     Allocator allocator;
1094 
1095     // check TMap(InputIterator, InputIterator)
1096     TMap m1(v.begin(), v.end());
1097     static_assert(std::is_same<decltype(m1), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1098 
1099     // check TMap(InputIterator, InputIterator, HashCompare)
1100     TMap m2(v.begin(), v.end(), compare);
1101     static_assert(std::is_same<decltype(m2), TMap<Key, Value, Compare>>::value);
1102 
1103     // check TMap(InputIterator, InputIterator, HashCompare, Allocator)
1104     TMap m3(v.begin(), v.end(), compare, allocator);
1105     static_assert(std::is_same<decltype(m3), TMap<Key, Value, Compare, Allocator>>::value);
1106 
1107     // check TMap(InputIterator, InputIterator, Allocator)
1108     TMap m4(v.begin(), v.end(), allocator);
1109     static_assert(std::is_same<decltype(m4), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1110 
1111     // check TMap(std::initializer_list)
1112     TMap m5(l);
1113     static_assert(std::is_same<decltype(m5), TMap<Key, Value, DefaultCompare, DefaultAllocator>>::value);
1114 
1115     // check TMap(std::initializer_list, HashCompare)
1116     TMap m6(l, compare);
1117     static_assert(std::is_same<decltype(m6), TMap<Key, Value, Compare, DefaultAllocator>>::value);
1118 
1119     // check TMap(std::initializer_list, HashCompare, Allocator)
1120     TMap m7(l, compare, allocator);
1121     static_assert(std::is_same<decltype(m7), TMap<Key, Value, Compare, Allocator>>::value);
1122 
1123     // check TMap(std::initializer_list, Allocator)
1124     TMap m8(l, allocator);
1125     static_assert(std::is_same<decltype(m8), TMap<Key, Value, DefaultCompare, Allocator>>::value);
1126 
1127     // check TMap(TMap &)
1128     TMap m9(m1);
1129     static_assert(std::is_same<decltype(m9), decltype(m1)>::value);
1130 
1131     // check TMap(TMap &, Allocator)
1132     TMap m10(m4, allocator);
1133     static_assert(std::is_same<decltype(m10), decltype(m4)>::value);
1134 
1135     // check TMap(TMap &&)
1136     TMap m11(std::move(m1));
1137     static_assert(std::is_same<decltype(m11), decltype(m1)>::value);
1138 
1139     // check TMap(TMap &&, Allocator)
1140     TMap m12(std::move(m4), allocator);
1141     static_assert(std::is_same<decltype(m12), decltype(m4)>::value);
1142 }
1143 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1144 
1145 template <typename CHMapType>
1146 void test_comparisons_basic() {
1147     using comparisons_testing::testEqualityComparisons;
1148     CHMapType c1, c2;
1149     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1150 
1151     c1.emplace(1, 1);
1152     testEqualityComparisons</*ExpectEqual = */false>(c1, c2);
1153 
1154     c2.emplace(1, 1);
1155     testEqualityComparisons</*ExpectEqual = */true>(c1, c2);
1156 }
1157 
1158 template <typename TwoWayComparableContainerType>
1159 void test_two_way_comparable_chmap() {
1160     TwoWayComparableContainerType c1, c2;
1161     c1.emplace(1, 1);
1162     c2.emplace(1, 1);
1163     comparisons_testing::TwoWayComparable::reset();
1164     REQUIRE_MESSAGE(c1 == c2, "Incorrect operator == result");
1165     comparisons_testing::check_equality_comparison();
1166     REQUIRE_MESSAGE(!(c1 != c2), "Incorrect operator != result");
1167     comparisons_testing::check_equality_comparison();
1168 }
1169 
1170 void TestCHMapComparisons() {
1171     using integral_container = oneapi::tbb::concurrent_hash_map<int, int>;
1172     using two_way_comparable_container = oneapi::tbb::concurrent_hash_map<comparisons_testing::TwoWayComparable,
1173                                                                           comparisons_testing::TwoWayComparable>;
1174 
1175     test_comparisons_basic<integral_container>();
1176     test_comparisons_basic<two_way_comparable_container>();
1177     test_two_way_comparable_chmap<two_way_comparable_container>();
1178 }
1179 
1180 template <typename Iterator, typename CHMapType>
1181 void TestCHMapIteratorComparisonsBasic( CHMapType& chmap ) {
1182     REQUIRE_MESSAGE(!chmap.empty(), "Incorrect test setup");
1183     using namespace comparisons_testing;
1184     Iterator it1, it2;
1185     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1186     it1 = chmap.begin();
1187     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1188     it2 = chmap.begin();
1189     testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1190     it2 = chmap.end();
1191     testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1192 }
1193 
1194 void TestCHMapIteratorComparisons() {
1195     using chmap_type = oneapi::tbb::concurrent_hash_map<int, int>;
1196     using value_type = typename chmap_type::value_type;
1197     chmap_type chmap = {value_type{1, 1}, value_type{2, 2}, value_type{3, 3}};
1198     TestCHMapIteratorComparisonsBasic<typename chmap_type::iterator>(chmap);
1199     const chmap_type& cchmap = chmap;
1200     TestCHMapIteratorComparisonsBasic<typename chmap_type::const_iterator>(cchmap);
1201 }
1202 
1203 //! Test consruction with hash_compare
1204 //! \brief \ref interface \ref requirement
1205 TEST_CASE("testing consruction with hash_compare") {
1206     TestHashCompareConstructors();
1207 }
1208 
1209 //! Test concurrent_hash_map member types
1210 //! \brief \ref interface \ref requirement
1211 TEST_CASE("test types"){
1212     test_member_types<oneapi::tbb::concurrent_hash_map>();
1213 }
1214 
1215 //! Test swap and clear operations
1216 //! \brief \ref interface \ref requirement
1217 TEST_CASE("test copy operations") {
1218     TestCopy();
1219 }
1220 
1221 //! Test rehash operation
1222 //! \brief \ref interface \ref requirement
1223 TEST_CASE("test rehash operation") {
1224     TestRehash();
1225 }
1226 
1227 //! Test assignment operation
1228 //! \brief \ref interface \ref requirement
1229 TEST_CASE("test assignment operation") {
1230     TestAssignment();
1231 }
1232 
1233 //! Test iterators and ranges
1234 //! \brief \ref interface \ref requirement
1235 TEST_CASE("test iterators and ranges") {
1236     TestIteratorsAndRanges();
1237 }
1238 
1239 //! Test work with initializer_list
1240 //! \brief \ref interface \ref requirement
1241 TEST_CASE("test work with initializer_list") {
1242     TestInitList();
1243 }
1244 
1245 #if TBB_USE_EXCEPTIONS
1246 //! Test exception safety
1247 //! \brief \ref requirement
1248 TEST_CASE("test exception safety") {
1249     TestExceptions();
1250 }
1251 
1252 //! Test exceptions safety guarantees for move constructor
1253 //! \brief \ref requirement
1254 TEST_CASE("test move support with exceptions") {
1255     move_support_tests::test_ex_move_ctor_unequal_allocator_memory_failure<hash_map_traits>();
1256     move_support_tests::test_ex_move_ctor_unequal_allocator_element_ctor_failure<hash_map_traits>();
1257 }
1258 #endif
1259 
1260 //! Test move constructor
1261 //! \brief \ref interface \ref requirement
1262 TEST_CASE("testing move constructor"){
1263     move_support_tests::test_move_constructor<hash_map_traits>();
1264 }
1265 
1266 //! Test move assign operator
1267 //! \brief \ref interface \ref requirement
1268 TEST_CASE("testing move assign operator"){
1269     move_support_tests::test_move_assignment<hash_map_traits>();
1270 }
1271 
1272 //! Test insert and empace
1273 //! \brief \ref interface \ref requirement
1274 TEST_CASE("testing concurrent insert and emplace"){
1275     int n=250000;
1276     {
1277         DataStateTrackedTable table;
1278         DoConcurrentOperations<RvalueInsert, DataStateTrackedTable>( table, n, "rvalue ref insert", 1 );
1279     }
1280     {
1281         DataStateTrackedTable table;
1282         DoConcurrentOperations<Emplace, DataStateTrackedTable>( table, n, "emplace", 1 );
1283     }
1284 }
1285 
1286 //! Test allocator traits
1287 //! \brief \ref requirement
1288 TEST_CASE("testing allocator traits") {
1289     test_allocator_traits_support<hash_map_traits>();
1290 }
1291 
1292 //! Test concurrent operations
1293 //! \brief \ref requirement
1294 TEST_CASE("testing concurrency"){
1295     for (std::size_t p = 1; p <= 4; ++p) {
1296         oneapi::tbb::global_control limit(oneapi::tbb::global_control::max_allowed_parallelism, p);
1297         TestInsertFindErase(p);
1298         TestConcurrency(p);
1299     }
1300 }
1301 
1302 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1303 //! Test deduction guides
1304 //! \brief \ref interface
1305 TEST_CASE("testing deduction guides") {
1306     TestDeductionGuides<oneapi::tbb::concurrent_hash_map>();
1307 }
1308 #endif // __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1309 
1310 //! \brief \ref interface \ref requirement
1311 TEST_CASE("concurrent_hash_map comparisons") {
1312     TestCHMapComparisons();
1313 }
1314 
1315 //! \brief \ref interface \ref requirement
1316 TEST_CASE("concurrent_hash_map iterator comparisons") {
1317     TestCHMapIteratorComparisons();
1318 }
1319