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 #ifndef __TBB_test_common_container_move_support_H 18 #define __TBB_test_common_container_move_support_H 19 20 #include "config.h" 21 22 #include <vector> 23 #include <memory> 24 #include <type_traits> 25 #include <algorithm> 26 #include "custom_allocators.h" 27 #include "state_trackable.h" 28 29 namespace move_support_tests { 30 31 std::atomic<std::size_t> foo_count; 32 std::size_t max_foo_count = 0; 33 static constexpr intptr_t initial_bar = 42; 34 static constexpr std::size_t serial_dead_state = std::size_t(-1); 35 36 struct limit_foo_count_in_scope { 37 std::size_t previous_state; 38 bool active; 39 limit_foo_count_in_scope(std::size_t new_limit, bool an_active = true): previous_state(max_foo_count), active(an_active) { 40 if (active){ 41 max_foo_count = new_limit; 42 } 43 } 44 ~limit_foo_count_in_scope(){ 45 if (active) { 46 max_foo_count = previous_state; 47 } 48 } 49 }; 50 51 template<typename static_counter_allocator_type> 52 struct limit_allocated_items_in_scope { 53 std::size_t previous_state; 54 bool active; 55 limit_allocated_items_in_scope(std::size_t new_limit, bool an_active = true) : previous_state(static_counter_allocator_type::max_items), active(an_active) { 56 if (active){ 57 static_counter_allocator_type::set_limits(new_limit); 58 } 59 } 60 ~limit_allocated_items_in_scope(){ 61 if (active) { 62 static_counter_allocator_type::set_limits(previous_state); 63 } 64 } 65 }; 66 67 template<int line_n> 68 struct track_foo_count { 69 bool active; 70 std::size_t previous_state; 71 track_foo_count(): active(true), previous_state(foo_count) { } 72 ~track_foo_count(){ 73 if (active){ 74 this->verify_no_undestroyed_foo_left_and_dismiss(); 75 } 76 } 77 78 //TODO: ideally in most places this check should be replaced with "no foo created or destroyed" 79 //TODO: deactivation of the check seems like a hack 80 void verify_no_undestroyed_foo_left_and_dismiss() { 81 REQUIRE_MESSAGE( foo_count == previous_state, "Some instances of Foo were not destroyed ?" ); 82 active = false; 83 } 84 }; 85 86 template<typename static_counter_allocator_type> 87 struct track_allocator_memory { 88 using counters_type = typename static_counter_allocator_type::counters_type; 89 90 counters_type previous_state; 91 track_allocator_memory() { static_counter_allocator_type::init_counters(); } 92 ~track_allocator_memory(){verify_no_allocator_memory_leaks();} 93 94 void verify_no_allocator_memory_leaks() const{ 95 REQUIRE_MESSAGE( static_counter_allocator_type::items_allocated == static_counter_allocator_type::items_freed, "memory leak?" ); 96 REQUIRE_MESSAGE( static_counter_allocator_type::allocations == static_counter_allocator_type::frees, "memory leak?" ); 97 } 98 void save_allocator_counters(){ previous_state = static_counter_allocator_type::counters(); } 99 void verify_no_more_than_x_memory_items_allocated(std::size_t expected_number_of_items_to_allocate){ 100 counters_type now = static_counter_allocator_type::counters(); 101 REQUIRE_MESSAGE( (now.items_allocated - previous_state.items_allocated) <= expected_number_of_items_to_allocate, "More then excepted memory allocated ?" ); 102 } 103 }; 104 #if TBB_USE_EXCEPTIONS 105 struct FooException : std::bad_alloc { 106 virtual const char* what() const noexcept override { return "out of Foo limit"; } 107 virtual ~FooException() {} 108 }; 109 #endif 110 111 struct FooLimit { 112 FooLimit() { 113 if (max_foo_count && foo_count >= max_foo_count) { 114 TBB_TEST_THROW(FooException{}); 115 } 116 } 117 }; 118 119 // TODO: consider better naming 120 class Foo : FooLimit, public StateTrackable</*allow_zero_initialized = */true> { 121 protected: 122 using state_trackable_type = StateTrackable<true>; 123 intptr_t my_bar; 124 std::size_t my_serial{}; 125 std::size_t my_thread_id{}; 126 public: 127 128 bool is_valid_or_zero() const { 129 return is_valid() || (state == ZeroInitialized && !my_bar); 130 } 131 132 intptr_t& zero_bar() { 133 CHECK_FAST(is_valid_or_zero()); 134 return my_bar; 135 } 136 137 intptr_t zero_bar() const { 138 CHECK_FAST(is_valid_or_zero()); 139 return my_bar; 140 } 141 142 intptr_t& bar() { 143 CHECK_FAST(is_valid()); 144 return my_bar; 145 } 146 147 intptr_t bar() const { 148 CHECK_FAST(is_valid()); 149 return my_bar; 150 } 151 152 void set_serial( std::size_t s ) { 153 my_serial = s; 154 } 155 156 std::size_t get_serial() const { 157 return my_serial; 158 } 159 160 void set_thread_id( std::size_t t ) { 161 my_thread_id = t; 162 } 163 164 std::size_t get_thread_id() const { 165 return my_thread_id; 166 } 167 168 operator intptr_t() const { return bar(); } 169 170 Foo( intptr_t br ) : state_trackable_type(0) { 171 my_bar = br; 172 ++foo_count; 173 } 174 175 Foo() { 176 my_bar = initial_bar; 177 ++foo_count; 178 } 179 180 Foo( const Foo& foo ) : state_trackable_type(foo) { 181 my_bar = foo.my_bar; 182 ++foo_count; 183 my_serial = foo.my_serial; 184 my_thread_id = foo.my_thread_id; 185 } 186 187 Foo( Foo&& foo ) : state_trackable_type(std::move(foo)) { 188 my_bar = foo.my_bar; 189 my_serial = foo.my_serial; 190 my_thread_id = foo.my_thread_id; 191 ++foo_count; 192 } 193 194 ~Foo() { 195 my_bar = ~initial_bar; 196 my_serial = serial_dead_state; 197 my_thread_id = serial_dead_state; 198 if (state != ZeroInitialized) { 199 --foo_count; 200 } 201 } 202 203 Foo& operator=( const Foo& x ) { 204 state_trackable_type::operator=(x); 205 my_bar = x.my_bar; 206 my_serial = x.my_serial; 207 my_thread_id = x.my_thread_id; 208 return *this; 209 } 210 211 Foo& operator=( Foo&& x ) { 212 state_trackable_type::operator=(std::move(x)); 213 my_bar = x.my_bar; 214 my_serial = x.my_serial; 215 my_thread_id = x.my_thread_id; 216 x.my_serial = serial_dead_state; 217 x.my_thread_id = serial_dead_state; 218 x.my_bar = -1; 219 return *this; 220 } 221 222 friend bool operator==( const int& lhs, const Foo& rhs ) { 223 CHECK_FAST_MESSAGE(rhs.is_valid_or_zero(), "Comparing invalid objects"); 224 return lhs == rhs.my_bar; 225 } 226 227 friend bool operator==( const Foo& lhs, const int& rhs ) { 228 CHECK_FAST_MESSAGE(lhs.is_valid_or_zero(), "Comparing invalid objects"); 229 return lhs.my_bar == rhs; 230 } 231 232 friend bool operator==( const Foo& lhs, const Foo& rhs ) { 233 CHECK_FAST_MESSAGE(lhs.is_valid_or_zero(), "Comparing invalid objects"); 234 CHECK_FAST_MESSAGE(rhs.is_valid_or_zero(), "Comparing invalid objects"); 235 return lhs.my_bar == rhs.my_bar; 236 } 237 238 friend bool operator<( const Foo& lhs, const Foo& rhs ) { 239 CHECK_FAST_MESSAGE(lhs.is_valid_or_zero(), "Comparing invalid objects"); 240 CHECK_FAST_MESSAGE(rhs.is_valid_or_zero(), "Comparing invalid objects"); 241 return lhs.my_bar < rhs.my_bar; 242 } 243 244 bool is_const() const { return true; } 245 bool is_const() { return false; } 246 247 protected: 248 char reserve[1]; 249 }; // struct Foo 250 251 struct FooWithAssign : Foo { 252 FooWithAssign() = default; 253 FooWithAssign( intptr_t b ) : Foo(b) {} 254 FooWithAssign( const FooWithAssign& ) = default; 255 FooWithAssign( FooWithAssign&& ) = default; 256 257 FooWithAssign& operator=( const FooWithAssign& f ) { 258 return static_cast<FooWithAssign&>(Foo::operator=(f)); 259 } 260 261 FooWithAssign& operator=( FooWithAssign&& f ) { 262 return static_cast<FooWithAssign&>(Foo::operator=(std::move(f))); 263 } 264 }; // struct FooWithAssign 265 266 template <typename FooIteratorType> 267 class FooIteratorBase { 268 protected: 269 intptr_t x_bar; 270 private: 271 FooIteratorType& as_derived() { return *static_cast<FooIteratorType*>(this); } 272 public: 273 FooIteratorBase( intptr_t x ) : x_bar(x) {} 274 275 FooIteratorType& operator++() { 276 ++x_bar; 277 return as_derived(); 278 } 279 280 FooIteratorType operator++(int) { 281 FooIteratorType tmp(as_derived()); 282 ++x_bar; 283 return tmp; 284 } 285 286 friend bool operator==( const FooIteratorType& lhs, const FooIteratorType& rhs ) { 287 return lhs.x_bar == rhs.x_bar; 288 } 289 290 friend bool operator!=( const FooIteratorType& lhs, const FooIteratorType& rhs ) { 291 return !(lhs == rhs); 292 } 293 }; // class FooIteratorBase 294 295 class FooIterator : public FooIteratorBase<FooIterator> { 296 using base_type = FooIteratorBase<FooIterator>; 297 public: 298 using iterator_category = std::input_iterator_tag; 299 using value_type = FooWithAssign; 300 using difference_type = std::ptrdiff_t; 301 using pointer = value_type*; 302 using reference = value_type&; 303 304 using base_type::base_type; 305 306 value_type operator*() { 307 return value_type(x_bar); 308 } 309 }; // class FooIterator 310 311 class FooPairIterator : public FooIteratorBase<FooPairIterator> { 312 using base_type = FooIteratorBase<FooPairIterator>; 313 public: 314 using iterator_category = std::input_iterator_tag; 315 using value_type = std::pair<FooWithAssign, FooWithAssign>; 316 using difference_type = std::ptrdiff_t; 317 using pointer = value_type*; 318 using reference = value_type&; 319 320 using base_type::base_type; 321 322 value_type operator*() { 323 FooWithAssign foo; 324 foo.bar() = x_bar; 325 return std::make_pair(foo, foo); 326 } 327 }; // class FooPairIterator 328 329 struct MemoryLocations { 330 std::vector<const void*> locations; 331 332 template <typename ContainerType> 333 MemoryLocations( const ContainerType& source ) : locations(source.size()) { 334 for (auto it = source.begin(); it != source.end(); ++it) { 335 locations[std::distance(source.begin(), it)] = &*it; 336 } 337 } 338 339 template <typename ContainerType> 340 bool content_location_unchanged( const ContainerType& dst ) { 341 auto is_same_location = []( const typename ContainerType::value_type& v, const void* location ) { 342 return &v == location; 343 }; 344 345 return std::equal(dst.begin(), dst.end(), locations.begin(), is_same_location); 346 } 347 348 template <typename ContainerType> 349 bool content_location_changed( const ContainerType& dst ) { 350 auto is_not_same_location = []( const typename ContainerType::value_type& v, const void* location ) { 351 return &v != location; 352 }; 353 return std::equal(dst.begin(), dst.end(), locations.begin(), is_not_same_location); 354 } 355 }; // struct MemoryLocations 356 357 template <typename T, typename POCMA = std::false_type> 358 struct ArenaAllocatorFixture { 359 using allocator_type = ArenaAllocator<T, POCMA>; 360 using arena_data_type = typename allocator_type::arena_data_type; 361 362 std::vector<typename std::aligned_storage<sizeof(T)>::type> storage; 363 arena_data_type arena_data; 364 allocator_type allocator; 365 366 ArenaAllocatorFixture( std::size_t size_to_allocate ) 367 : storage(size_to_allocate), 368 arena_data(reinterpret_cast<T*>(&storage.front()), storage.size()), 369 allocator(arena_data) {} 370 371 ArenaAllocatorFixture( const ArenaAllocatorFixture& ) = delete; 372 }; // struct ArenaAllocatorFixture 373 374 template <typename T, typename POCMA = std::false_type> 375 struct TwoMemoryArenasFixture { 376 using arena_fixture_type = ArenaAllocatorFixture<T, POCMA>; 377 using allocator_type = typename arena_fixture_type::allocator_type; 378 379 arena_fixture_type source_arena_fixture; 380 arena_fixture_type dst_arena_fixture; 381 382 allocator_type& source_allocator; 383 allocator_type& dst_allocator; 384 385 TwoMemoryArenasFixture( std::size_t size_to_allocate ) 386 : source_arena_fixture(size_to_allocate), 387 dst_arena_fixture(size_to_allocate), 388 source_allocator(source_arena_fixture.allocator), 389 dst_allocator(dst_arena_fixture.allocator) 390 { 391 REQUIRE_MESSAGE(&(*source_arena_fixture.storage.begin()) != &(*dst_arena_fixture.storage.begin()), 392 "source and destination arena instances should use difference memory regions"); 393 REQUIRE_MESSAGE(source_allocator != dst_allocator, "arenas using difference memory regions should not compare equal"); 394 using Traits_POCMA = typename tbb::detail::allocator_traits<allocator_type>::propagate_on_container_move_assignment; 395 REQUIRE_MESSAGE(POCMA::value == Traits_POCMA::value, 396 "POCMA::value should be the same as in allocator_traits"); 397 398 allocator_type source_allocator_copy(source_allocator); 399 allocator_type dst_allocator_copy(dst_allocator); 400 allocator_type source_previous_state(source_allocator); 401 402 REQUIRE_MESSAGE(source_previous_state == source_allocator, 403 "Copy of the allocator should compare equal with it's source"); 404 dst_allocator_copy = std::move(source_allocator_copy); 405 REQUIRE_MESSAGE(dst_allocator_copy == source_previous_state, 406 "Move initialized allocator should compare equal with it's source before movement"); 407 } 408 409 TwoMemoryArenasFixture( const TwoMemoryArenasFixture& ) = delete; 410 411 void verify_allocator_was_moved( const allocator_type& result_allocator ) { 412 // TODO: add assert that move ctor/assignment was called 413 REQUIRE_MESSAGE(result_allocator == source_allocator, "allocator was not moved"); 414 REQUIRE_MESSAGE(result_allocator != dst_allocator, "allocator_was_not_moved"); 415 } 416 417 }; // struct TwoMemoryArenasFixture 418 419 template <typename ContainerTraits, typename Allocator> 420 struct MoveFixture { 421 using element_type = typename Allocator::value_type; 422 using container_value_type = typename ContainerTraits::template container_value_type<element_type>; 423 using allocator_type = typename tbb::detail::allocator_traits<Allocator>::template rebind_alloc<container_value_type>; 424 using container_type = typename ContainerTraits::template container_type<element_type, allocator_type>; 425 using init_iterator_type = typename ContainerTraits::init_iterator_type; 426 427 static constexpr std::size_t default_container_size = 100; 428 const std::size_t container_size; 429 430 typename std::aligned_storage<sizeof(container_type)>::type source_storage; 431 container_type& source; 432 433 MemoryLocations locations; 434 435 MoveFixture( std::size_t cont_size = default_container_size ) 436 : container_size(cont_size), 437 source(ContainerTraits::template construct_container<container_type>(source_storage, 438 init_iterator_type(0), init_iterator_type(cont_size))), 439 locations(source) 440 { 441 init(); 442 } 443 444 MoveFixture( const Allocator& a, std::size_t cont_size = default_container_size ) 445 : container_size(cont_size), 446 source(ContainerTraits::template construct_container<container_type>(source_storage, init_iterator_type(0), 447 init_iterator_type(cont_size), a)), 448 locations(source) 449 { 450 init(); 451 } 452 453 MoveFixture( const MoveFixture& ) = delete; 454 455 ~MoveFixture() { 456 reinterpret_cast<container_type*>(&source)->~container_type(); 457 } 458 459 void init() { 460 verify_size(source); 461 verify_content_equal_to_source(source); 462 verify_size(locations.locations); 463 } 464 465 bool content_location_unchanged( const container_type& dst ) { 466 return locations.content_location_unchanged(dst); 467 } 468 469 bool content_location_changed( const container_type& dst ) { 470 return locations.content_location_changed(dst); 471 } 472 473 template <typename ContainerType> 474 void verify_size( const ContainerType& dst ) { 475 REQUIRE(container_size == dst.size()); 476 } 477 478 void verify_content_equal_to_source( const container_type& dst ) { 479 REQUIRE(ContainerTraits::equal(dst, init_iterator_type(0), init_iterator_type(container_size))); 480 } 481 482 void verify_content_equal_to_source( const container_type& dst, std::size_t number_of_constructed_items ) { 483 REQUIRE(number_of_constructed_items <= dst.size()); 484 REQUIRE(std::equal(dst.begin(), dst.begin() + number_of_constructed_items, init_iterator_type(0))); 485 } 486 487 void verify_content_shallow_moved( const container_type& dst ) { 488 verify_size(dst); 489 REQUIRE_MESSAGE(content_location_unchanged(dst), "Container move ctor actually changed element locations, while should not"); 490 REQUIRE_MESSAGE(source.empty(), "Moved from container should not contain any elements"); 491 verify_content_equal_to_source(dst); 492 } 493 494 void verify_content_deep_moved( const container_type& dst ) { 495 verify_size(dst); 496 REQUIRE_MESSAGE(content_location_changed(dst), "Container did not changed element locations for unequal allocators"); 497 REQUIRE_MESSAGE(std::all_of(dst.begin(), dst.end(), is_state_predicate<Foo::MoveInitialized>()), 498 "Container did not move construct some elements"); 499 REQUIRE_MESSAGE(std::all_of(source.begin(), source.end(), is_state_predicate<Foo::MovedFrom>()), 500 "Container did not move all the elements"); 501 verify_content_equal_to_source(dst); 502 } 503 504 void verify_part_of_content_deep_moved(container_type const& dst, std::size_t number_of_constructed_items){ 505 REQUIRE_MESSAGE(content_location_changed(dst), "Vector actually did not changed element locations for unequal allocators, while should"); 506 REQUIRE_MESSAGE(std::all_of(dst.begin(), dst.begin() + number_of_constructed_items, is_state_predicate<Foo::MoveInitialized>{}), "Vector did not move construct some elements?"); 507 if (dst.size() != number_of_constructed_items) { 508 REQUIRE_MESSAGE(std::all_of(dst.begin() + number_of_constructed_items, dst.end(), is_state_predicate<Foo::ZeroInitialized>{}), "Failed to zero-initialize items left not constructed after the exception?" ); 509 } 510 verify_content_equal_to_source(dst, number_of_constructed_items); 511 512 REQUIRE_MESSAGE(std::all_of(source.begin(), source.begin() + number_of_constructed_items, is_state_predicate<Foo::MovedFrom>{}), "Vector did not move all the elements?"); 513 REQUIRE_MESSAGE(std::all_of(source.begin() + number_of_constructed_items, source.end(), is_not_state_predicate<Foo::MovedFrom>{}), "Vector changed elements in source after exception point?"); 514 } 515 }; // struct MoveFixture 516 517 template <typename StaticCountingAllocatorType> 518 struct TrackAllocatorMemory { 519 using counters_type = typename StaticCountingAllocatorType::counters_type; 520 521 counters_type previous_state; 522 523 TrackAllocatorMemory() { 524 StaticCountingAllocatorType::init_counters(); 525 } 526 527 TrackAllocatorMemory( const TrackAllocatorMemory& ) = delete; 528 529 ~TrackAllocatorMemory() { 530 verify_no_allocator_memory_leaks(); 531 } 532 533 void verify_no_allocator_memory_leaks() const { 534 REQUIRE_MESSAGE(StaticCountingAllocatorType::items_allocated == StaticCountingAllocatorType::items_freed, "Memory leak"); 535 REQUIRE_MESSAGE(StaticCountingAllocatorType::allocations == StaticCountingAllocatorType::frees, "Memory leak"); 536 REQUIRE_MESSAGE(StaticCountingAllocatorType::items_constructed == StaticCountingAllocatorType::items_destroyed, 537 "The number of constructed items is not equal to the number of destroyed items"); 538 } 539 540 void save_allocator_counters() { previous_state = StaticCountingAllocatorType::counters(); } 541 542 void verify_no_more_than_x_memory_items_allocated( std::size_t expected ) { 543 counters_type now = StaticCountingAllocatorType::counters(); 544 REQUIRE_MESSAGE((now.items_allocated - previous_state.items_allocated) <= expected, 545 "More then expected memory allocated"); 546 } 547 }; // struct TrackAllocatorMemory 548 549 struct TrackFooCount { 550 TrackFooCount() : active(true), previous_state(foo_count) {} 551 552 TrackFooCount( const TrackFooCount& ) = delete; 553 554 ~TrackFooCount() { 555 if (active) { 556 verify_no_undestroyed_foo_left_and_dismiss(); 557 } 558 } 559 560 void verify_no_undestroyed_foo_left_and_dismiss() { 561 REQUIRE_MESSAGE(foo_count == previous_state, "Some instances of Foo were not destroyed"); 562 active = false; 563 } 564 565 bool active; 566 std::size_t previous_state; 567 }; // struct TrackFooCount 568 569 template <typename ContainerTraits, typename POCMA = std::false_type, typename T = FooWithAssign> 570 struct DefaultStatefulFixtureHelper { 571 using allocator_fixture_type = TwoMemoryArenasFixture<T, POCMA>; 572 using allocator_type = StaticSharedCountingAllocator<typename allocator_fixture_type::allocator_type>; 573 574 using move_fixture_type = MoveFixture<ContainerTraits, allocator_type>; 575 576 using leaks_tracker_type = TrackAllocatorMemory<allocator_type>; 577 using foo_leaks_in_test_tracker_type = TrackFooCount; 578 579 struct DefaultStatefulFixture 580 : leaks_tracker_type, 581 allocator_fixture_type, 582 move_fixture_type, 583 foo_leaks_in_test_tracker_type 584 { 585 //TODO: calculate needed size for allocator_fixture_type more accurately 586 //allocate twice more storage to handle case when copy constructor called instead of move one 587 DefaultStatefulFixture() 588 : leaks_tracker_type(), 589 allocator_fixture_type(2 * 4 * move_fixture_type::default_container_size), 590 move_fixture_type(allocator_fixture_type::source_allocator), 591 foo_leaks_in_test_tracker_type() 592 { 593 leaks_tracker_type::save_allocator_counters(); 594 } 595 596 void verify_no_more_than_x_memory_items_allocated() { 597 auto n = ContainerTraits::expected_number_of_items_to_allocate_for_steal_move; 598 leaks_tracker_type::verify_no_more_than_x_memory_items_allocated(n); 599 } 600 601 using allocator_type = typename move_fixture_type::container_type::allocator_type; 602 }; // struct DefaultStatefulFixture 603 604 using type = DefaultStatefulFixture; 605 }; // struct DefaultStatefulFixtureHelper 606 607 template <typename StaticCountingAllocatorType> 608 struct LimitAllocatedItemsInScope { 609 LimitAllocatedItemsInScope( std::size_t limit, bool act = true ) 610 : previous_state(StaticCountingAllocatorType::max_items), active(act) 611 { 612 if (active) { 613 StaticCountingAllocatorType::set_limits(limit); 614 } 615 } 616 617 LimitAllocatedItemsInScope( const LimitAllocatedItemsInScope& ) = delete; 618 619 ~LimitAllocatedItemsInScope() { 620 if (active) { 621 StaticCountingAllocatorType::set_limits(previous_state); 622 } 623 } 624 625 std::size_t previous_state; 626 bool active; 627 }; // struct LimitAllocatedItemsInScope 628 629 struct LimitFooCountInScope { 630 LimitFooCountInScope( std::size_t limit, bool act = true ) 631 : previous_state(max_foo_count), active(act) 632 { 633 if (active) { 634 max_foo_count = limit; 635 } 636 } 637 638 LimitFooCountInScope( const LimitFooCountInScope& ) = delete; 639 640 ~LimitFooCountInScope() { 641 if (active) { 642 max_foo_count = previous_state; 643 } 644 } 645 646 std::size_t previous_state; 647 bool active; 648 }; // struct LimitFooCountInScope 649 650 template <typename ContainerTraits> 651 void test_move_ctor_single_argument() { 652 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits>::type; 653 using container_type = typename fixture_type::container_type; 654 655 fixture_type fixture; 656 657 container_type dst(std::move(fixture.source)); 658 659 fixture.verify_content_shallow_moved(dst); 660 fixture.verify_allocator_was_moved(dst.get_allocator()); 661 fixture.verify_no_more_than_x_memory_items_allocated(); 662 fixture.verify_no_undestroyed_foo_left_and_dismiss(); 663 } 664 665 template <typename ContainerTraits> 666 void test_move_ctor_with_equal_allocator() { 667 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits>::type; 668 using container_type = typename fixture_type::container_type; 669 670 fixture_type fixture; 671 672 container_type dst(std::move(fixture.source), fixture.source.get_allocator()); 673 674 fixture.verify_content_shallow_moved(dst); 675 fixture.verify_no_more_than_x_memory_items_allocated(); 676 fixture.verify_no_undestroyed_foo_left_and_dismiss(); 677 } 678 679 template <typename ContainerTraits> 680 void test_move_ctor_with_unequal_allocator() { 681 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits>::type; 682 using container_type = typename fixture_type::container_type; 683 684 fixture_type fixture; 685 686 typename container_type::allocator_type alloc(fixture.dst_allocator); 687 container_type dst(std::move(fixture.source), alloc); 688 689 fixture.verify_content_deep_moved(dst); 690 } 691 692 template <typename ContainerTraits> 693 void test_move_assignment_POCMA_true_stateful_allocator() { 694 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits, /*POCMA = */std::true_type>::type; 695 using container_type = typename fixture_type::container_type; 696 697 fixture_type fixture; 698 699 container_type dst(fixture.dst_allocator); 700 dst = std::move(fixture.source); 701 702 fixture.verify_content_shallow_moved(dst); 703 fixture.verify_allocator_was_moved(dst.get_allocator()); 704 fixture.verify_no_more_than_x_memory_items_allocated(); 705 fixture.verify_no_undestroyed_foo_left_and_dismiss(); 706 } 707 708 template <typename ContainerTraits> 709 void test_move_assignment_POCMA_true_stateless_allocator() { 710 // POCMA is true for std::allocator since C++14, is_always_equal is true since C++17 711 // Behavior can be unexpected, TODO: consider another allocator type 712 using allocator_type = std::allocator<FooWithAssign>; 713 using fixture_type = MoveFixture<ContainerTraits, allocator_type>; 714 using container_type = typename fixture_type::container_type; 715 716 fixture_type fixture; 717 718 REQUIRE_MESSAGE(fixture.source.get_allocator() == allocator_type(), "Incorrect test setup: allocator is stateful"); 719 720 container_type dst; 721 dst = std::move(fixture.source); 722 723 fixture.verify_content_shallow_moved(dst); 724 } 725 726 template <typename ContainerTraits> 727 void test_move_assignment_POCMA_false_equal_allocator() { 728 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits, /*POCMA = */std::false_type>::type; 729 using container_type = typename fixture_type::container_type; 730 731 fixture_type fixture; 732 container_type dst(fixture.source_allocator); 733 REQUIRE_MESSAGE(fixture.source.get_allocator() == dst.get_allocator(), "Incorrect test setup: allocators should be equal"); 734 735 fixture.save_allocator_counters(); 736 737 dst = std::move(fixture.source); 738 739 fixture.verify_content_shallow_moved(dst); 740 fixture.verify_no_more_than_x_memory_items_allocated(); 741 fixture.verify_no_undestroyed_foo_left_and_dismiss(); 742 } 743 744 template <typename ContainerTraits> 745 void test_move_assignment_POCMA_false_unequal_allocator() { 746 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits, /*POCMA = */std::false_type>::type; 747 using container_type = typename fixture_type::container_type; 748 749 fixture_type fixture; 750 751 container_type dst(fixture.dst_allocator); 752 dst = std::move(fixture.source); 753 754 fixture.verify_content_deep_moved(dst); 755 } 756 757 #define REQUIRE_THROW_EXCEPTION(expr, exception_type) \ 758 try { \ 759 expr; \ 760 REQUIRE_MESSAGE(false, "Exception should be thrown");\ 761 } catch (exception_type&) { \ 762 } catch (...) { \ 763 REQUIRE_MESSAGE(false, "Unexpected exception"); \ 764 } \ 765 766 #if TBB_USE_EXCEPTIONS 767 template <typename ContainerTraits> 768 void test_ex_move_ctor_unequal_allocator_memory_failure() { 769 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits>::type; 770 using container_type = typename fixture_type::container_type; 771 using allocator_type = typename container_type::allocator_type; 772 773 fixture_type fixture; 774 775 std::size_t limit = allocator_type::items_allocated + fixture.container_size / 4; 776 LimitAllocatedItemsInScope<allocator_type> alloc_limit(limit); 777 778 REQUIRE_THROW_EXCEPTION(container_type dst(std::move(fixture.source), fixture.dst_allocator), std::bad_alloc); 779 } 780 781 template <typename ContainerTraits> 782 void test_ex_move_ctor_unequal_allocator_element_ctor_failure() { 783 using fixture_type = typename DefaultStatefulFixtureHelper<ContainerTraits>::type; 784 using container_type = typename fixture_type::container_type; 785 786 fixture_type fixture; 787 788 std::size_t limit = foo_count + fixture.container_size / 4; 789 LimitFooCountInScope foo_limit(limit); 790 REQUIRE_THROW_EXCEPTION(container_type dst(std::move(fixture.source), fixture.dst_allocator), FooException); 791 } 792 793 template <typename ContainerTraits> 794 void test_ex_move_constructor() { 795 test_ex_move_ctor_unequal_allocator_memory_failure<ContainerTraits>(); 796 test_ex_move_ctor_unequal_allocator_element_ctor_failure<ContainerTraits>(); 797 // TODO: add test for move assignment exceptions 798 } 799 #endif 800 801 template <typename ContainerTraits> 802 void test_move_constructor() { 803 test_move_ctor_single_argument<ContainerTraits>(); 804 test_move_ctor_with_equal_allocator<ContainerTraits>(); 805 test_move_ctor_with_unequal_allocator<ContainerTraits>(); 806 } 807 808 template <typename ContainerTraits> 809 void test_move_assignment() { 810 test_move_assignment_POCMA_true_stateful_allocator<ContainerTraits>(); 811 test_move_assignment_POCMA_true_stateless_allocator<ContainerTraits>(); 812 test_move_assignment_POCMA_false_equal_allocator<ContainerTraits>(); 813 test_move_assignment_POCMA_false_unequal_allocator<ContainerTraits>(); 814 } 815 816 template<typename container_traits> 817 void test_constructor_with_move_iterators(){ 818 using fixture_type = typename move_support_tests::DefaultStatefulFixtureHelper<container_traits>::type; 819 using container_type = typename fixture_type::container_type; 820 821 fixture_type fixture; 822 823 container_type dst(std::make_move_iterator(fixture.source.begin()), std::make_move_iterator(fixture.source.end()), fixture.dst_allocator); 824 825 fixture.verify_content_deep_moved(dst); 826 } 827 828 template<typename container_traits> 829 void test_assign_with_move_iterators(){ 830 using fixture_type = typename move_support_tests::DefaultStatefulFixtureHelper<container_traits>::type; 831 using container_type = typename fixture_type::container_type; 832 833 fixture_type fixture; 834 835 container_type dst(fixture.dst_allocator); 836 dst.assign(std::make_move_iterator(fixture.source.begin()), std::make_move_iterator(fixture.source.end())); 837 838 fixture.verify_content_deep_moved(dst); 839 } 840 841 } // namespace move_support_tests 842 843 namespace std { 844 template <> 845 struct hash<move_support_tests::Foo> { 846 std::size_t operator()( const move_support_tests::Foo& f ) const { 847 return std::size_t(f.bar()); 848 } 849 }; 850 851 template <> 852 struct hash<move_support_tests::FooWithAssign> { 853 std::size_t operator()( const move_support_tests::FooWithAssign& f ) const { 854 return std::hash<move_support_tests::Foo>{}(f); 855 } 856 }; 857 } 858 859 #endif // __TBB_test_common_container_move_support_H 860