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