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;
previous_statelimit_foo_count_in_scope39 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 }
~limit_foo_count_in_scopelimit_foo_count_in_scope44 ~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;
previous_statelimit_allocated_items_in_scope55 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 }
~limit_allocated_items_in_scopelimit_allocated_items_in_scope60 ~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;
track_foo_counttrack_foo_count71 track_foo_count(): active(true), previous_state(foo_count) { }
~track_foo_counttrack_foo_count72 ~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
verify_no_undestroyed_foo_left_and_dismisstrack_foo_count80 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;
track_allocator_memorytrack_allocator_memory91 track_allocator_memory() { static_counter_allocator_type::init_counters(); }
~track_allocator_memorytrack_allocator_memory92 ~track_allocator_memory(){verify_no_allocator_memory_leaks();}
93
verify_no_allocator_memory_leakstrack_allocator_memory94 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 }
save_allocator_counterstrack_allocator_memory98 void save_allocator_counters(){ previous_state = static_counter_allocator_type::counters(); }
verify_no_more_than_x_memory_items_allocatedtrack_allocator_memory99 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 {
whatFooException106 virtual const char* what() const noexcept override { return "out of Foo limit"; }
~FooExceptionFooException107 virtual ~FooException() {}
108 };
109 #endif
110
111 struct FooLimit {
FooLimitFooLimit112 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
is_valid_or_zero()128 bool is_valid_or_zero() const {
129 return is_valid() || (state == ZeroInitialized && !my_bar);
130 }
131
zero_bar()132 intptr_t& zero_bar() {
133 CHECK_FAST(is_valid_or_zero());
134 return my_bar;
135 }
136
zero_bar()137 intptr_t zero_bar() const {
138 CHECK_FAST(is_valid_or_zero());
139 return my_bar;
140 }
141
bar()142 intptr_t& bar() {
143 CHECK_FAST(is_valid());
144 return my_bar;
145 }
146
bar()147 intptr_t bar() const {
148 CHECK_FAST(is_valid());
149 return my_bar;
150 }
151
set_serial(std::size_t s)152 void set_serial( std::size_t s ) {
153 my_serial = s;
154 }
155
get_serial()156 std::size_t get_serial() const {
157 return my_serial;
158 }
159
set_thread_id(std::size_t t)160 void set_thread_id( std::size_t t ) {
161 my_thread_id = t;
162 }
163
get_thread_id()164 std::size_t get_thread_id() const {
165 return my_thread_id;
166 }
167
intptr_t()168 operator intptr_t() const { return bar(); }
169
Foo(intptr_t br)170 Foo( intptr_t br ) : state_trackable_type(0) {
171 my_bar = br;
172 ++foo_count;
173 }
174
Foo()175 Foo() {
176 my_bar = initial_bar;
177 ++foo_count;
178 }
179
Foo(const Foo & foo)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
Foo(Foo && foo)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
~Foo()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
is_const()244 bool is_const() const { return true; }
is_const()245 bool is_const() { return false; }
246
247 protected:
248 char reserve[1];
249 }; // struct Foo
250
251 struct FooWithAssign : Foo {
252 FooWithAssign() = default;
FooWithAssignFooWithAssign253 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:
as_derived()271 FooIteratorType& as_derived() { return *static_cast<FooIteratorType*>(this); }
272 public:
FooIteratorBase(intptr_t x)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>
MemoryLocationsMemoryLocations333 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>
content_location_unchangedMemoryLocations340 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>
content_location_changedMemoryLocations349 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
ArenaAllocatorFixtureArenaAllocatorFixture366 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
TwoMemoryArenasFixtureTwoMemoryArenasFixture385 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
verify_allocator_was_movedTwoMemoryArenasFixture411 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 )
container_sizeMoveFixture436 : 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 )
container_sizeMoveFixture445 : 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
~MoveFixtureMoveFixture455 ~MoveFixture() {
456 reinterpret_cast<container_type*>(&source)->~container_type();
457 }
458
initMoveFixture459 void init() {
460 verify_size(source);
461 verify_content_equal_to_source(source);
462 verify_size(locations.locations);
463 }
464
content_location_unchangedMoveFixture465 bool content_location_unchanged( const container_type& dst ) {
466 return locations.content_location_unchanged(dst);
467 }
468
content_location_changedMoveFixture469 bool content_location_changed( const container_type& dst ) {
470 return locations.content_location_changed(dst);
471 }
472
473 template <typename ContainerType>
verify_sizeMoveFixture474 void verify_size( const ContainerType& dst ) {
475 REQUIRE(container_size == dst.size());
476 }
477
verify_content_equal_to_sourceMoveFixture478 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
verify_content_equal_to_sourceMoveFixture482 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
verify_content_shallow_movedMoveFixture487 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
verify_content_deep_movedMoveFixture494 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
verify_part_of_content_deep_movedMoveFixture504 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
TrackAllocatorMemoryTrackAllocatorMemory523 TrackAllocatorMemory() {
524 StaticCountingAllocatorType::init_counters();
525 }
526
527 TrackAllocatorMemory( const TrackAllocatorMemory& ) = delete;
528
~TrackAllocatorMemoryTrackAllocatorMemory529 ~TrackAllocatorMemory() {
530 verify_no_allocator_memory_leaks();
531 }
532
verify_no_allocator_memory_leaksTrackAllocatorMemory533 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
save_allocator_countersTrackAllocatorMemory540 void save_allocator_counters() { previous_state = StaticCountingAllocatorType::counters(); }
541
verify_no_more_than_x_memory_items_allocatedTrackAllocatorMemory542 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 {
TrackFooCountTrackFooCount550 TrackFooCount() : active(true), previous_state(foo_count) {}
551
552 TrackFooCount( const TrackFooCount& ) = delete;
553
~TrackFooCountTrackFooCount554 ~TrackFooCount() {
555 if (active) {
556 verify_no_undestroyed_foo_left_and_dismiss();
557 }
558 }
559
verify_no_undestroyed_foo_left_and_dismissTrackFooCount560 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
DefaultStatefulFixtureDefaultStatefulFixtureHelper::DefaultStatefulFixture587 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
verify_no_more_than_x_memory_items_allocatedDefaultStatefulFixtureHelper::DefaultStatefulFixture596 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 )
previous_stateLimitAllocatedItemsInScope610 : 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
~LimitAllocatedItemsInScopeLimitAllocatedItemsInScope619 ~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 )
previous_stateLimitFooCountInScope631 : 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
~LimitFooCountInScopeLimitFooCountInScope640 ~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>
test_move_ctor_single_argument()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>
test_move_ctor_with_equal_allocator()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>
test_move_ctor_with_unequal_allocator()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>
test_move_assignment_POCMA_true_stateful_allocator()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>
test_move_assignment_POCMA_true_stateless_allocator()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>
test_move_assignment_POCMA_false_equal_allocator()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>
test_move_assignment_POCMA_false_unequal_allocator()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>
test_ex_move_ctor_unequal_allocator_memory_failure()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>
test_ex_move_ctor_unequal_allocator_element_ctor_failure()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>
test_ex_move_constructor()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>
test_move_constructor()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>
test_move_assignment()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>
test_constructor_with_move_iterators()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>
test_assign_with_move_iterators()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