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