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