1 | /* |
2 | Copyright (c) 2005-2019 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_container_move_support_H |
18 | #define __TBB_test_container_move_support_H |
19 | |
20 | #include "harness.h" |
21 | #include "harness_assert.h" |
22 | #include "harness_allocator.h" |
23 | #include "harness_state_trackable.h" |
24 | |
25 | #include "tbb/atomic.h" |
26 | #include "tbb/aligned_space.h" |
27 | #include "tbb/internal/_allocator_traits.h" |
28 | |
29 | #include <stdexcept> |
30 | #include <string> |
31 | #include <functional> |
32 | |
33 | tbb::atomic<size_t> FooCount; |
34 | size_t MaxFooCount = 0; |
35 | |
36 | //! Exception for concurrent_container |
37 | class Foo_exception : public std::bad_alloc { |
38 | public: |
39 | virtual const char *what() const throw() __TBB_override { return "out of Foo limit" ; } |
40 | virtual ~Foo_exception() throw() {} |
41 | }; |
42 | |
43 | struct FooLimit { |
44 | FooLimit(){ |
45 | if(MaxFooCount && FooCount >= MaxFooCount) |
46 | __TBB_THROW( Foo_exception() ); |
47 | } |
48 | }; |
49 | |
50 | static const intptr_t initial_value_of_bar = 42; |
51 | |
52 | |
53 | struct Foo : FooLimit, Harness::StateTrackable<true>{ |
54 | typedef Harness::StateTrackable<true> StateTrackable; |
55 | intptr_t my_bar; |
56 | public: |
57 | bool is_valid_or_zero() const{ |
58 | return is_valid()||(state==ZeroInitialized && !my_bar); |
59 | } |
60 | intptr_t& zero_bar(){ |
61 | ASSERT( is_valid_or_zero(), NULL ); |
62 | return my_bar; |
63 | } |
64 | intptr_t zero_bar() const{ |
65 | ASSERT( is_valid_or_zero(), NULL ); |
66 | return my_bar; |
67 | } |
68 | intptr_t& bar(){ |
69 | ASSERT( is_valid(), NULL ); |
70 | return my_bar; |
71 | } |
72 | intptr_t bar() const{ |
73 | ASSERT( is_valid(), NULL ); |
74 | return my_bar; |
75 | } |
76 | operator intptr_t() const{ |
77 | return this->bar(); |
78 | } |
79 | Foo( intptr_t barr ): StateTrackable(0){ |
80 | my_bar = barr; |
81 | FooCount++; |
82 | } |
83 | Foo(){ |
84 | my_bar = initial_value_of_bar; |
85 | FooCount++; |
86 | } |
87 | Foo( const Foo& foo ): FooLimit(), StateTrackable(foo){ |
88 | my_bar = foo.my_bar; |
89 | FooCount++; |
90 | } |
91 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
92 | Foo( Foo&& foo ): FooLimit(), StateTrackable(std::move(foo)){ |
93 | my_bar = foo.my_bar; |
94 | //TODO: consider not using constant here, instead something like ~my_bar |
95 | foo.my_bar = -1; |
96 | FooCount++; |
97 | } |
98 | #endif |
99 | ~Foo(){ |
100 | my_bar = ~initial_value_of_bar; |
101 | if(state != ZeroInitialized) --FooCount; |
102 | } |
103 | friend bool operator==(const int &lhs, const Foo &rhs) { |
104 | ASSERT( rhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
105 | return lhs == rhs.my_bar; |
106 | } |
107 | friend bool operator==(const Foo &lhs, const int &rhs) { |
108 | ASSERT( lhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
109 | return lhs.my_bar == rhs; |
110 | } |
111 | friend bool operator==(const Foo &lhs, const Foo &rhs) { |
112 | ASSERT( lhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
113 | ASSERT( rhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
114 | return lhs.my_bar == rhs.my_bar; |
115 | } |
116 | friend bool operator<(const Foo &lhs, const Foo &rhs) { |
117 | ASSERT( lhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
118 | ASSERT( rhs.is_valid_or_zero(), "comparing invalid objects ?" ); |
119 | return lhs.my_bar < rhs.my_bar; |
120 | } |
121 | bool is_const() const {return true;} |
122 | bool is_const() {return false;} |
123 | protected: |
124 | char reserve[1]; |
125 | Foo& operator=( const Foo& x ) { |
126 | StateTrackable::operator=(x); |
127 | my_bar = x.my_bar; |
128 | return *this; |
129 | } |
130 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
131 | Foo& operator=( Foo&& x ) { |
132 | ASSERT( x.is_valid_or_zero(), "bad source for assignment" ); |
133 | ASSERT( is_valid_or_zero(), NULL ); |
134 | StateTrackable::operator=(std::move(x)); |
135 | my_bar = x.my_bar; |
136 | x.my_bar = -1; |
137 | return *this; |
138 | } |
139 | #endif |
140 | }; |
141 | |
142 | struct FooWithAssign: public Foo { |
143 | FooWithAssign() : Foo(){} |
144 | FooWithAssign(intptr_t barr) : Foo(barr){} |
145 | FooWithAssign(FooWithAssign const& f) : Foo(f) {} |
146 | FooWithAssign& operator=(FooWithAssign const& f) { return static_cast<FooWithAssign&>(Foo::operator=(f)); } |
147 | |
148 | |
149 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
150 | FooWithAssign(FooWithAssign && f) : Foo(std::move(f)) {} |
151 | FooWithAssign& operator=(FooWithAssign && f) { return static_cast<FooWithAssign&>(Foo::operator=(std::move(f))); } |
152 | #endif |
153 | }; |
154 | |
155 | template<typename FooIteratorType> |
156 | class FooIteratorBase { |
157 | protected: |
158 | intptr_t x_bar; |
159 | private: |
160 | FooIteratorType& as_derived(){ return *static_cast<FooIteratorType*>(this);} |
161 | public: |
162 | FooIteratorBase(intptr_t x) { |
163 | x_bar = x; |
164 | } |
165 | FooIteratorType &operator++() { |
166 | x_bar++; return as_derived(); |
167 | } |
168 | FooIteratorType operator++(int) { |
169 | FooIteratorType tmp(as_derived()); x_bar++; return tmp; |
170 | } |
171 | friend bool operator==(const FooIteratorType & lhs, const FooIteratorType & rhs){ return lhs.x_bar == rhs.x_bar; } |
172 | friend bool operator!=(const FooIteratorType & lhs, const FooIteratorType & rhs){ return !(lhs == rhs); } |
173 | }; |
174 | |
175 | class FooIterator: public std::iterator<std::input_iterator_tag,FooWithAssign>, public FooIteratorBase<FooIterator> { |
176 | public: |
177 | FooIterator(intptr_t x): FooIteratorBase<FooIterator>(x) {} |
178 | |
179 | FooWithAssign operator*() { |
180 | return FooWithAssign(x_bar); |
181 | } |
182 | }; |
183 | |
184 | class FooPairIterator: public std::iterator<std::input_iterator_tag, std::pair<FooWithAssign,FooWithAssign> >, public FooIteratorBase<FooPairIterator> { |
185 | public: |
186 | FooPairIterator(intptr_t x): FooIteratorBase<FooPairIterator>(x) {} |
187 | |
188 | std::pair<FooWithAssign,FooWithAssign> operator*() { |
189 | FooWithAssign foo; foo.bar() = x_bar; |
190 | |
191 | return std::make_pair(foo, foo); |
192 | } |
193 | }; |
194 | |
195 | namespace FooTests{ |
196 | template<typename Foo_type> |
197 | void (){ |
198 | Foo_type src; |
199 | ASSERT(src.state == Foo::DefaultInitialized, "incorrect state for default constructed Foo (derived) ?" ); |
200 | } |
201 | |
202 | template<typename Foo_type> |
203 | void (){ |
204 | Foo_type src(1); |
205 | ASSERT(src.state == Foo::DirectInitialized, "incorrect state for direct constructed Foo (derived) ?" ); |
206 | } |
207 | |
208 | template<typename Foo_type> |
209 | void (){ |
210 | Foo_type src; |
211 | Foo_type dst(src); |
212 | ASSERT(dst.state == Foo::CopyInitialized, "incorrect state for Copy constructed Foo ?" ); |
213 | } |
214 | |
215 | template<typename Foo_type> |
216 | void (){ |
217 | Foo_type src; |
218 | Foo_type dst; |
219 | dst = (src); |
220 | |
221 | ASSERT(dst.state == Foo::Assigned, "incorrect state for Assigned Foo ?" ); |
222 | } |
223 | |
224 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
225 | template<typename Foo_type> |
226 | void (){ |
227 | Foo_type src; |
228 | Foo_type dst(std::move(src)); |
229 | ASSERT(dst.state == Foo::MoveInitialized, "incorrect state for Move constructed Foo ?" ); |
230 | ASSERT(src.state == Foo::MovedFrom, "incorrect state for Move from Foo ?" ); |
231 | } |
232 | |
233 | template<typename Foo_type> |
234 | void (){ |
235 | Foo_type src; |
236 | Foo_type dst; |
237 | dst = std::move(src); |
238 | |
239 | ASSERT(dst.state == Foo::MoveAssigned, "incorrect state for Move Assigned Foo ?" ); |
240 | ASSERT(src.state == Foo::MovedFrom, "incorrect state for Moved from Foo ?" ); |
241 | } |
242 | #if TBB_USE_EXCEPTIONS |
243 | void TestMoveConstructorException(); |
244 | #endif //TBB_USE_EXCEPTIONS |
245 | #endif //__TBB_CPP11_RVALUE_REF_PRESENT |
246 | } |
247 | |
248 | void TestFoo(){ |
249 | using namespace FooTests; |
250 | TestDefaultConstructor<Foo>(); |
251 | TestDefaultConstructor<FooWithAssign>(); |
252 | TestDirectConstructor<Foo>(); |
253 | TestDirectConstructor<FooWithAssign>(); |
254 | TestCopyConstructor<Foo>(); |
255 | TestCopyConstructor<FooWithAssign>(); |
256 | TestAssignOperator<FooWithAssign>(); |
257 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
258 | TestMoveConstructor<Foo>(); |
259 | TestMoveConstructor<FooWithAssign>(); |
260 | TestMoveAssignOperator<FooWithAssign>(); |
261 | #if TBB_USE_EXCEPTIONS && !__TBB_CPP11_EXCEPTION_IN_STATIC_TEST_BROKEN |
262 | TestMoveConstructorException(); |
263 | #endif //TBB_USE_EXCEPTIONS |
264 | #endif //__TBB_CPP11_RVALUE_REF_PRESENT |
265 | } |
266 | |
267 | //TODO: replace _IN_TEST with separately defined macro IN_TEST(msg,test_name) |
268 | #define ASSERT_IN_TEST(p,message,test_name) ASSERT(p, (std::string(test_name) + ": " + message).c_str()); |
269 | //TODO: move to harness_assert |
270 | #define ASSERT_THROWS_IN_TEST(expression, exception_type, message, test_name) \ |
271 | try{ \ |
272 | expression; \ |
273 | ASSERT_IN_TEST(false, "should throw an exception", test_name); \ |
274 | }catch(exception_type &){ \ |
275 | }catch(...){ASSERT_IN_TEST(false, "unexpected exception", test_name);} \ |
276 | |
277 | #define ASSERT_THROWS(expression, exception_type, message) ASSERT_THROWS_IN_TEST(expression, exception_type, message, "") |
278 | |
279 | template<Harness::StateTrackableBase::StateValue desired_state, bool allow_zero_initialized_state> |
280 | bool is_state(Harness::StateTrackable<allow_zero_initialized_state> const& f){ return f.state == desired_state;} |
281 | |
282 | template<Harness::StateTrackableBase::StateValue desired_state> |
283 | struct is_not_state_f { |
284 | template <bool allow_zero_initialized_state> |
285 | bool operator()(Harness::StateTrackable<allow_zero_initialized_state> const& f){ return !is_state<desired_state>(f);} |
286 | }; |
287 | |
288 | template<Harness::StateTrackableBase::StateValue desired_state> |
289 | struct is_state_f { |
290 | template <bool allow_zero_initialized_state> |
291 | bool operator()(Harness::StateTrackable<allow_zero_initialized_state> const& f){ return is_state<desired_state>(f); } |
292 | //TODO: cu_map defines key as a const thus by default it is not moved, instead it is copied. Investigate how std::unordered_map behaves |
293 | template<typename T1, typename T2> |
294 | bool operator()(std::pair<T1, T2> const& p){ return /*is_state<desired_state>(p.first) && */is_state<desired_state>(p.second); } |
295 | }; |
296 | |
297 | template<typename iterator, typename unary_predicate> |
298 | bool all_of(iterator begin, iterator const& end, unary_predicate p){ |
299 | for (; begin != end; ++begin){ |
300 | if ( !p(*begin)) return false; |
301 | } |
302 | return true; |
303 | } |
304 | |
305 | template<typename container, typename unary_predicate> |
306 | bool all_of(container const& c, unary_predicate p){ |
307 | return ::all_of( c.begin(), c.end(), p ); |
308 | } |
309 | |
310 | void TestAllOf(){ |
311 | Foo foos[] = {Foo(), Foo(), Foo()}; |
312 | ASSERT(::all_of(foos, Harness::end(foos), is_state_f<Foo::DefaultInitialized>()), "all_of returned false while true expected" ); |
313 | ASSERT(! ::all_of(foos, Harness::end(foos), is_state_f<Foo::CopyInitialized>()), "all_of returned true while false expected " ); |
314 | } |
315 | |
316 | template<typename static_counter_allocator_type> |
317 | struct track_allocator_memory: NoCopy{ |
318 | typedef typename static_counter_allocator_type::counters_t counters_t; |
319 | |
320 | counters_t previous_state; |
321 | const char* const test_name; |
322 | track_allocator_memory(const char* a_test_name): test_name(a_test_name) { static_counter_allocator_type::init_counters(); } |
323 | ~track_allocator_memory(){verify_no_allocator_memory_leaks();} |
324 | |
325 | void verify_no_allocator_memory_leaks() const{ |
326 | ASSERT_IN_TEST( static_counter_allocator_type::items_allocated == static_counter_allocator_type::items_freed, "memory leak?" , test_name ); |
327 | ASSERT_IN_TEST( static_counter_allocator_type::allocations == static_counter_allocator_type::frees, "memory leak?" , test_name ); |
328 | } |
329 | void save_allocator_counters(){ previous_state = static_counter_allocator_type::counters(); } |
330 | void verify_no_more_than_x_memory_items_allocated(size_t expected_number_of_items_to_allocate){ |
331 | counters_t now = static_counter_allocator_type::counters(); |
332 | ASSERT_IN_TEST( (now.items_allocated - previous_state.items_allocated) <= expected_number_of_items_to_allocate, "More then excepted memory allocated ?" , test_name ); |
333 | } |
334 | }; |
335 | |
336 | #include <vector> |
337 | template<int line_n> |
338 | struct track_foo_count: NoCopy{ |
339 | bool active; |
340 | size_t previous_state; |
341 | const char* const test_name; |
342 | track_foo_count(const char* a_test_name): active(true), previous_state(FooCount), test_name(a_test_name) { } |
343 | ~track_foo_count(){ |
344 | if (active){ |
345 | this->verify_no_undestroyed_foo_left_and_dismiss(); |
346 | } |
347 | } |
348 | |
349 | //TODO: ideally in most places this check should be replaced with "no foo created or destroyed" |
350 | //TODO: deactivation of the check seems like a hack |
351 | void verify_no_undestroyed_foo_left_and_dismiss() { |
352 | ASSERT_IN_TEST( FooCount == previous_state, "Some instances of Foo were not destroyed ?" , test_name ); |
353 | active = false; |
354 | } |
355 | }; |
356 | |
357 | //TODO: inactive mode in these limiters is a temporary workaround for usage in exception type loop of TestException |
358 | |
359 | struct limit_foo_count_in_scope: NoCopy{ |
360 | size_t previous_state; |
361 | bool active; |
362 | limit_foo_count_in_scope(size_t new_limit, bool an_active = true): previous_state(MaxFooCount), active(an_active) { |
363 | if (active){ |
364 | MaxFooCount = new_limit; |
365 | } |
366 | } |
367 | ~limit_foo_count_in_scope(){ |
368 | if (active) { |
369 | MaxFooCount = previous_state; |
370 | } |
371 | } |
372 | }; |
373 | |
374 | template<typename static_counter_allocator_type> |
375 | struct limit_allocated_items_in_scope: NoCopy{ |
376 | size_t previous_state; |
377 | bool active; |
378 | limit_allocated_items_in_scope(size_t new_limit, bool an_active = true) : previous_state(static_counter_allocator_type::max_items), active(an_active) { |
379 | if (active){ |
380 | static_counter_allocator_type::set_limits(new_limit); |
381 | } |
382 | } |
383 | ~limit_allocated_items_in_scope(){ |
384 | if (active) { |
385 | static_counter_allocator_type::set_limits(previous_state); |
386 | } |
387 | } |
388 | }; |
389 | |
390 | struct default_container_traits{ |
391 | template <typename container_type, typename iterator_type> |
392 | static container_type& construct_container(tbb::aligned_space<container_type> & storage, iterator_type begin, iterator_type end){ |
393 | new (storage.begin()) container_type(begin, end); |
394 | return *storage.begin(); |
395 | } |
396 | |
397 | template <typename container_type, typename iterator_type, typename allocator_type> |
398 | static container_type& construct_container(tbb::aligned_space<container_type> & storage, iterator_type begin, iterator_type end, allocator_type const& a){ |
399 | new (storage.begin()) container_type(begin, end, a); |
400 | return *storage.begin(); |
401 | } |
402 | }; |
403 | |
404 | struct memory_locations { |
405 | std::vector<const void*> locations; |
406 | |
407 | template <typename container_type> |
408 | memory_locations(container_type const& source) : locations(source.size()){ |
409 | for (typename container_type::const_iterator it = source.begin(); it != source.end(); ++it){locations[std::distance(source.begin(), it)] = & *it;} |
410 | } |
411 | |
412 | template <typename container_t> |
413 | bool content_location_unchanged(container_t const& dst){ |
414 | struct is_same_location{ |
415 | static bool compare(typename container_t::value_type const& v, const void* location){ return &v == location;} |
416 | }; |
417 | |
418 | return std::equal(dst.begin(), dst.end(), locations.begin(), &is_same_location::compare); |
419 | } |
420 | |
421 | template <typename container_t> |
422 | bool content_location_changed(container_t const& dst){ |
423 | struct is_not_same_location{ |
424 | static bool compare(typename container_t::value_type const& v, const void* location){ return &v != location;} |
425 | }; |
426 | |
427 | return std::equal(dst.begin(), dst.end(), locations.begin(), &is_not_same_location::compare); |
428 | } |
429 | |
430 | }; |
431 | |
432 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
433 | #include <algorithm> |
434 | void TestMemoryLocaionsHelper(){ |
435 | const size_t test_sequence_len = 15; |
436 | std::vector<char> source(test_sequence_len, 0); |
437 | std::generate_n(source.begin(), source.size(), Harness::FastRandomBody<char>(1)); |
438 | |
439 | memory_locations source_memory_locations((source)); |
440 | |
441 | std::vector<char> copy((source)); |
442 | ASSERT(source_memory_locations.content_location_changed(copy), "" ); |
443 | |
444 | std::vector<char> alias(std::move(source)); |
445 | ASSERT(source_memory_locations.content_location_unchanged(alias), "" ); |
446 | } |
447 | namespace FooTests{ |
448 | #if TBB_USE_EXCEPTIONS |
449 | void (){ |
450 | Foo src; |
451 | const Foo::StateValue source_state_before = src.state; |
452 | ASSERT_THROWS_IN_TEST( |
453 | { |
454 | limit_foo_count_in_scope foo_limit(FooCount); |
455 | Foo f1(std::move(src)); |
456 | }, |
457 | std::bad_alloc, "" , "TestLimitInstancesNumber" |
458 | ); |
459 | ASSERT(source_state_before == src.state, "state of source changed while should not?" ); |
460 | } |
461 | #endif //TBB_USE_EXCEPTIONS |
462 | } |
463 | |
464 | template<typename container_traits, typename allocator_t> |
465 | struct move_fixture : NoCopy{ |
466 | typedef typename allocator_t::value_type element_type; |
467 | typedef typename container_traits:: template apply<element_type, allocator_t>::type container_t; |
468 | typedef typename container_traits::init_iterator_type init_iterator_type; |
469 | enum {default_container_size = 100}; |
470 | const size_t container_size; |
471 | tbb::aligned_space<container_t> source_storage; |
472 | container_t & source; |
473 | //check that location of _all_ elements of container under test is changed/unchanged |
474 | memory_locations locations; |
475 | |
476 | ~move_fixture(){ |
477 | source_storage.begin()->~container_t(); |
478 | } |
479 | |
480 | const char* const test_name; |
481 | move_fixture(const char* a_test_name, size_t a_container_size = default_container_size ) |
482 | : container_size(a_container_size) |
483 | , source(container_traits::construct_container(source_storage, init_iterator_type(0), init_iterator_type(container_size))) |
484 | , locations(source) |
485 | , test_name(a_test_name) |
486 | { |
487 | init("move_fixture::move_fixture()" ); |
488 | } |
489 | |
490 | move_fixture(const char* a_test_name, allocator_t const& a, size_t a_container_size = default_container_size) |
491 | : container_size(a_container_size) |
492 | , source(container_traits::construct_container(source_storage, init_iterator_type(0), init_iterator_type(container_size), a)) |
493 | , locations(source) |
494 | , test_name(a_test_name) |
495 | { |
496 | init("move_fixture::move_fixture(allocator_t const& a)" ); |
497 | } |
498 | |
499 | void init(const std::string& ctor_name){ |
500 | verify_size(source, ctor_name.c_str()); |
501 | verify_content_equal_to_source(source, "did not properly initialized source? Or can not check container for equality with expected ?: " + ctor_name); |
502 | verify_size(locations.locations, "move_fixture:init " ); |
503 | } |
504 | |
505 | bool content_location_unchanged(container_t const& dst){ |
506 | return locations.content_location_unchanged(dst); |
507 | } |
508 | |
509 | bool content_location_changed(container_t const& dst){ |
510 | return locations.content_location_changed(dst); |
511 | } |
512 | |
513 | template<typename container_type> |
514 | void verify_size(container_type const& dst, const char* a_test_name){ |
515 | ASSERT_IN_TEST(container_size == dst.size(), "Did not construct all the elements or allocate enough memory?, while should ?" , a_test_name); |
516 | } |
517 | |
518 | void verify_content_equal_to_source(container_t const& dst, const std::string& msg){ |
519 | ASSERT_IN_TEST( container_traits::equal(dst, init_iterator_type(0), init_iterator_type(container_size)), msg.c_str(), test_name); |
520 | } |
521 | |
522 | void verify_content_equal_to_source(container_t const& dst){ |
523 | verify_content_equal_to_source(dst, "content changed during move/copy ?" ); |
524 | } |
525 | |
526 | void verify_content_equal_to_source(container_t const& dst, size_t number_of_constructed_items){ |
527 | ASSERT_IN_TEST(number_of_constructed_items <= dst.size(), "incorrect test expectation/input parameters?" , test_name); |
528 | ASSERT_IN_TEST(std::equal(dst.begin(), dst.begin() + number_of_constructed_items, init_iterator_type(0)), "content changed during move/copy ?" , test_name); |
529 | } |
530 | |
531 | //TODO: better name ? e.g. "content_was_stolen" |
532 | void verify_content_shallow_moved(container_t const& dst){ |
533 | verify_size(dst, test_name); |
534 | ASSERT_IN_TEST(content_location_unchanged(dst), "container move constructor actually changed element locations, while should not" , test_name); |
535 | ASSERT_IN_TEST(source.empty(), "Moved from container instance should not contain any elements" , test_name); |
536 | verify_content_equal_to_source(dst); |
537 | } |
538 | |
539 | //TODO: better name ? e.g. "element move" |
540 | void verify_content_deep_moved(container_t const& dst){ |
541 | verify_size(dst, test_name); |
542 | ASSERT_IN_TEST(content_location_changed(dst), "container actually did not changed element locations for unequal allocators, while should" , test_name); |
543 | ASSERT_IN_TEST(all_of(dst, is_state_f<Foo::MoveInitialized>()), "container did not move construct some elements?" , test_name); |
544 | ASSERT_IN_TEST(all_of(source, is_state_f<Foo::MovedFrom>()), "container did not move all the elements?" , test_name); |
545 | verify_content_equal_to_source(dst); |
546 | } |
547 | |
548 | void verify_part_of_content_deep_moved(container_t const& dst, size_t number_of_constructed_items){ |
549 | ASSERT_IN_TEST(content_location_changed(dst), "Vector actually did not changed element locations for unequal allocators, while should" , test_name); |
550 | ASSERT_IN_TEST(::all_of(dst.begin(), dst.begin() + number_of_constructed_items, is_state_f<Foo::MoveInitialized>()), "Vector did not move construct some elements?" , test_name); |
551 | if (dst.size() != number_of_constructed_items) { |
552 | ASSERT_IN_TEST(::all_of(dst.begin() + number_of_constructed_items, dst.end(), is_state_f<Foo::ZeroInitialized>()), "Failed to zero-initialize items left not constructed after the exception?" , test_name ); |
553 | } |
554 | verify_content_equal_to_source(dst, number_of_constructed_items); |
555 | |
556 | ASSERT_IN_TEST(::all_of(source.begin(), source.begin() + number_of_constructed_items, is_state_f<Foo::MovedFrom>()), "Vector did not move all the elements?" , test_name); |
557 | ASSERT_IN_TEST(::all_of(source.begin() + number_of_constructed_items, source.end(), is_not_state_f<Foo::MovedFrom>()), "Vector changed elements in source after exception point?" , test_name); |
558 | } |
559 | }; |
560 | |
561 | |
562 | template <typename T, typename pocma = Harness::false_type> |
563 | struct arena_allocator_fixture : NoCopy{ |
564 | typedef arena<T, pocma> allocator_t; |
565 | typedef typename allocator_t::arena_data_t arena_data_t; |
566 | |
567 | std::vector<tbb::aligned_space<T, 1> > storage; |
568 | arena_data_t arena_data; |
569 | allocator_t allocator; |
570 | |
571 | arena_allocator_fixture(size_t size_to_allocate) |
572 | : storage(size_to_allocate) |
573 | , arena_data((*storage.begin()).begin(), storage.size()) |
574 | , allocator(arena_data) |
575 | {} |
576 | }; |
577 | |
578 | //TODO: add ability to inject debug_allocator into stateful_allocator_fixture::allocator_t |
579 | template <typename T, typename pocma = Harness::false_type> |
580 | struct two_memory_arenas_fixture : NoCopy{ |
581 | typedef arena_allocator_fixture<T, pocma> arena_fixture_t; |
582 | typedef typename arena_fixture_t::allocator_t allocator_t; |
583 | |
584 | arena_fixture_t source_arena_fixture; |
585 | arena_fixture_t dst_arena_fixture; |
586 | |
587 | allocator_t& source_allocator; |
588 | allocator_t& dst_allocator; |
589 | |
590 | const char* test_name; |
591 | |
592 | two_memory_arenas_fixture(size_t size_to_allocate, const char* a_test_name) |
593 | : source_arena_fixture(size_to_allocate) |
594 | , dst_arena_fixture(size_to_allocate) |
595 | , source_allocator(source_arena_fixture.allocator) |
596 | , dst_allocator(dst_arena_fixture.allocator) |
597 | , test_name(a_test_name) |
598 | { |
599 | ASSERT_IN_TEST(&*source_arena_fixture.storage.begin() != &*dst_arena_fixture.storage.begin(), "source and destination arena instances should use different memory regions" , test_name); |
600 | ASSERT_IN_TEST(source_allocator != dst_allocator, "arenas using different memory regions should not compare equal" , test_name); |
601 | ASSERT_IN_TEST(pocma::value == tbb::internal::allocator_traits<allocator_t>::propagate_on_container_move_assignment::value, "This test require proper allocator_traits support" , test_name); |
602 | |
603 | //Some ISO C++11 allocator requirements enforcement: |
604 | allocator_t source_allocator_copy(source_allocator), dst(dst_allocator); |
605 | allocator_t source_previous_state(source_allocator); |
606 | ASSERT_IN_TEST(source_previous_state == source_allocator, "Copy of allocator should compare equal to it's source" , test_name); |
607 | dst = std::move(source_allocator_copy); |
608 | ASSERT_IN_TEST(dst == source_previous_state, "Move initialized instance of allocator should compare equal to it's source state before movement" , test_name); |
609 | } |
610 | |
611 | void verify_allocator_was_moved(const allocator_t& result_allocator){ |
612 | //TODO: add assert that allocator move constructor/assignment operator was called |
613 | ASSERT_IN_TEST(result_allocator == source_allocator, "allocator was not moved ?" , test_name); |
614 | ASSERT_IN_TEST(result_allocator != dst_allocator, "allocator was not moved ?" , test_name); |
615 | } |
616 | |
617 | // template <typename any_allocator_t> |
618 | // void verify_allocator_was_moved(const any_allocator_t& ){} |
619 | }; |
620 | |
621 | template <typename pocma = Harness::false_type> |
622 | struct std_stateful_allocator : NoCopy { |
623 | typedef stateful_allocator<FooWithAssign, pocma> allocator_t; |
624 | |
625 | allocator_t source_allocator; |
626 | allocator_t dst_allocator; |
627 | |
628 | const char* test_name; |
629 | |
630 | std_stateful_allocator(size_t , const char* a_test_name) |
631 | : test_name(a_test_name) |
632 | {} |
633 | |
634 | template <typename any_allocator_t> |
635 | void verify_allocator_was_moved(const any_allocator_t& ){} |
636 | |
637 | }; |
638 | |
639 | template<typename container_traits, typename pocma = Harness::false_type, typename T = FooWithAssign> |
640 | struct default_stateful_fixture_make_helper{ |
641 | // typedef std_stateful_allocator<pocma> allocator_fixture_t; |
642 | typedef two_memory_arenas_fixture<T, pocma> allocator_fixture_t; |
643 | typedef static_shared_counting_allocator<Harness::int_to_type<__LINE__>, typename allocator_fixture_t::allocator_t, std::size_t> allocator_t; |
644 | |
645 | typedef move_fixture<container_traits, allocator_t> move_fixture_t; |
646 | typedef track_allocator_memory<allocator_t> no_leaks_t; |
647 | typedef track_foo_count<__LINE__> no_foo_leaks_in_fixture_t; |
648 | typedef track_foo_count<__LINE__> no_foo_leaks_in_test_t; |
649 | |
650 | struct default_stateful_fixture : no_leaks_t, private no_foo_leaks_in_fixture_t, allocator_fixture_t, move_fixture_t, no_foo_leaks_in_test_t { |
651 | |
652 | default_stateful_fixture(const char* a_test_name) |
653 | : no_leaks_t(a_test_name) |
654 | , no_foo_leaks_in_fixture_t(a_test_name) |
655 | //TODO: calculate needed size more accurately |
656 | //allocate twice more storage to handle case when copy constructor called instead of move one |
657 | , allocator_fixture_t(2*4 * move_fixture_t::default_container_size, a_test_name) |
658 | , move_fixture_t(a_test_name, allocator_fixture_t::source_allocator) |
659 | , no_foo_leaks_in_test_t(a_test_name) |
660 | { |
661 | no_leaks_t::save_allocator_counters(); |
662 | } |
663 | |
664 | void verify_no_more_than_x_memory_items_allocated(){ |
665 | no_leaks_t::verify_no_more_than_x_memory_items_allocated(container_traits::expected_number_of_items_to_allocate_for_steal_move); |
666 | } |
667 | using no_foo_leaks_in_test_t::verify_no_undestroyed_foo_left_and_dismiss; |
668 | typedef typename move_fixture_t::container_t::allocator_type allocator_t; |
669 | }; |
670 | |
671 | typedef default_stateful_fixture type; |
672 | }; |
673 | |
674 | template<typename container_traits> |
675 | void TestMoveConstructorSingleArgument(){ |
676 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
677 | typedef typename fixture_t::container_t container_t; |
678 | |
679 | fixture_t fixture("TestMoveConstructorSingleArgument" ); |
680 | |
681 | container_t dst(std::move(fixture.source)); |
682 | |
683 | fixture.verify_content_shallow_moved(dst); |
684 | fixture.verify_allocator_was_moved(dst.get_allocator()); |
685 | fixture.verify_no_more_than_x_memory_items_allocated(); |
686 | fixture.verify_no_undestroyed_foo_left_and_dismiss(); |
687 | } |
688 | |
689 | template<typename container_traits> |
690 | void TestMoveConstructorWithEqualAllocator(){ |
691 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
692 | typedef typename fixture_t::container_t container_t; |
693 | |
694 | fixture_t fixture("TestMoveConstructorWithEqualAllocator" ); |
695 | |
696 | container_t dst(std::move(fixture.source), fixture.source.get_allocator()); |
697 | |
698 | fixture.verify_content_shallow_moved(dst); |
699 | fixture.verify_no_more_than_x_memory_items_allocated(); |
700 | fixture.verify_no_undestroyed_foo_left_and_dismiss(); |
701 | } |
702 | |
703 | template<typename container_traits> |
704 | void TestMoveConstructorWithUnEqualAllocator(){ |
705 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
706 | typedef typename fixture_t::container_t container_t; |
707 | |
708 | fixture_t fixture("TestMoveConstructorWithUnEqualAllocator" ); |
709 | |
710 | container_t dst(std::move(fixture.source), fixture.dst_allocator); |
711 | |
712 | fixture.verify_content_deep_moved(dst); |
713 | } |
714 | |
715 | template<typename container_traits> |
716 | void TestMoveConstructor(){ |
717 | TestMoveConstructorSingleArgument<container_traits>(); |
718 | TestMoveConstructorWithEqualAllocator<container_traits>(); |
719 | TestMoveConstructorWithUnEqualAllocator<container_traits>(); |
720 | } |
721 | |
722 | template<typename container_traits> |
723 | void TestMoveAssignOperatorPOCMAStateful(){ |
724 | typedef typename default_stateful_fixture_make_helper<container_traits, Harness::true_type>::type fixture_t; |
725 | typedef typename fixture_t::container_t container_t; |
726 | |
727 | fixture_t fixture("TestMoveAssignOperatorPOCMAStateful" ); |
728 | |
729 | container_t dst(fixture.dst_allocator); |
730 | |
731 | fixture.save_allocator_counters(); |
732 | |
733 | dst = std::move(fixture.source); |
734 | |
735 | fixture.verify_content_shallow_moved(dst); |
736 | fixture.verify_allocator_was_moved(dst.get_allocator()); |
737 | fixture.verify_no_more_than_x_memory_items_allocated(); |
738 | fixture.verify_no_undestroyed_foo_left_and_dismiss(); |
739 | } |
740 | |
741 | template<typename container_traits> |
742 | void TestMoveAssignOperatorPOCMANonStateful(){ |
743 | typedef std::allocator<FooWithAssign> allocator_t; |
744 | |
745 | typedef move_fixture<container_traits, allocator_t> fixture_t; |
746 | typedef typename fixture_t::container_t container_t; |
747 | |
748 | fixture_t fixture("TestMoveAssignOperatorPOCMANonStateful" ); |
749 | |
750 | ASSERT(fixture.source.get_allocator() == allocator_t(), "Incorrect test setup: allocator is stateful while should not?" ); |
751 | |
752 | container_t dst; |
753 | dst = std::move(fixture.source); |
754 | |
755 | fixture.verify_content_shallow_moved(dst); |
756 | //TODO: add an assert that allocator was "moved" when POCMA is set |
757 | } |
758 | |
759 | template<typename container_traits> |
760 | void TestMoveAssignOperatorNotPOCMAWithUnEqualAllocator(){ |
761 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
762 | typedef typename fixture_t::container_t container_t; |
763 | |
764 | fixture_t fixture("TestMoveAssignOperatorNotPOCMAWithUnEqualAllocator" ); |
765 | |
766 | container_t dst(fixture.dst_allocator); |
767 | dst = std::move(fixture.source); |
768 | |
769 | fixture.verify_content_deep_moved(dst); |
770 | } |
771 | |
772 | template<typename container_traits> |
773 | void TestMoveAssignOperatorNotPOCMAWithEqualAllocator(){ |
774 | typedef typename default_stateful_fixture_make_helper<container_traits, Harness::false_type>::type fixture_t; |
775 | typedef typename fixture_t::container_t container_t; |
776 | fixture_t fixture("TestMoveAssignOperatorNotPOCMAWithEqualAllocator" ); |
777 | |
778 | container_t dst(fixture.source_allocator); |
779 | ASSERT(fixture.source.get_allocator() == dst.get_allocator(), "Incorrect test setup: allocators are not equal while should be?" ); |
780 | |
781 | fixture.save_allocator_counters(); |
782 | |
783 | dst = std::move(fixture.source); |
784 | |
785 | fixture.verify_content_shallow_moved(dst); |
786 | fixture.verify_no_more_than_x_memory_items_allocated(); |
787 | fixture.verify_no_undestroyed_foo_left_and_dismiss(); |
788 | } |
789 | |
790 | template<typename container_traits> |
791 | void TestMoveAssignOperator(){ |
792 | #if __TBB_ALLOCATOR_TRAITS_PRESENT |
793 | TestMoveAssignOperatorPOCMANonStateful<container_traits>(); |
794 | TestMoveAssignOperatorPOCMAStateful<container_traits>(); |
795 | #endif |
796 | TestMoveAssignOperatorNotPOCMAWithUnEqualAllocator<container_traits>(); |
797 | TestMoveAssignOperatorNotPOCMAWithEqualAllocator<container_traits>(); |
798 | } |
799 | |
800 | template<typename container_traits> |
801 | void TestConstructorWithMoveIterators(){ |
802 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
803 | typedef typename fixture_t::container_t container_t; |
804 | |
805 | fixture_t fixture("TestConstructorWithMoveIterators" ); |
806 | |
807 | container_t dst(std::make_move_iterator(fixture.source.begin()), std::make_move_iterator(fixture.source.end()), fixture.dst_allocator); |
808 | |
809 | fixture.verify_content_deep_moved(dst); |
810 | } |
811 | |
812 | template<typename container_traits> |
813 | void TestAssignWithMoveIterators(){ |
814 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
815 | typedef typename fixture_t::container_t container_t; |
816 | |
817 | fixture_t fixture("TestAssignWithMoveIterators" ); |
818 | |
819 | container_t dst(fixture.dst_allocator); |
820 | dst.assign(std::make_move_iterator(fixture.source.begin()), std::make_move_iterator(fixture.source.end())); |
821 | |
822 | fixture.verify_content_deep_moved(dst); |
823 | } |
824 | |
825 | #if TBB_USE_EXCEPTIONS |
826 | template<typename container_traits> |
827 | void TestExceptionSafetyGuaranteesMoveConstructorWithUnEqualAllocatorMemoryFailure(){ |
828 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
829 | typedef typename fixture_t::container_t container_t; |
830 | typedef typename container_t::allocator_type allocator_t; |
831 | const char* test_name = "TestExceptionSafetyGuaranteesMoveConstructorWithUnEqualAllocatorMemoryFailure" ; |
832 | fixture_t fixture(test_name); |
833 | |
834 | limit_allocated_items_in_scope<allocator_t> allocator_limit(allocator_t::items_allocated + fixture.container_size/4); |
835 | ASSERT_THROWS_IN_TEST(container_t dst(std::move(fixture.source), fixture.dst_allocator), std::bad_alloc, "" , test_name); |
836 | } |
837 | |
838 | //TODO: add tests that verify that stealing move constructors/assign operators does not throw exceptions |
839 | template<typename container_traits> |
840 | void TestExceptionSafetyGuaranteesMoveConstructorWithUnEqualAllocatorExceptionInElementCtor(){ |
841 | typedef typename default_stateful_fixture_make_helper<container_traits>::type fixture_t; |
842 | typedef typename fixture_t::container_t container_t; |
843 | |
844 | const char* test_name = "TestExceptionSafetyGuaranteesMoveConstructorWithUnEqualAllocatorExceptionInElementCtor" ; |
845 | fixture_t fixture(test_name); |
846 | |
847 | limit_foo_count_in_scope foo_limit(FooCount + fixture.container_size/4); |
848 | ASSERT_THROWS_IN_TEST(container_t dst(std::move(fixture.source), fixture.dst_allocator), std::bad_alloc, "" , test_name); |
849 | } |
850 | #endif /* TBB_USE_EXCEPTIONS */ |
851 | #endif//__TBB_CPP11_RVALUE_REF_PRESENT |
852 | |
853 | namespace helper_stuff_tests { |
854 | void inline TestArena(){ |
855 | typedef int arena_element; |
856 | |
857 | arena_element arena_storage[10] = {0}; |
858 | typedef arena<arena_element> arena_t; |
859 | |
860 | arena_t::arena_data_t arena_data(arena_storage,Harness::array_length(arena_storage)); |
861 | arena_t a(arena_data); |
862 | |
863 | ASSERT(a.allocate(1) == arena_storage, "" ); |
864 | ASSERT(a.allocate(2) == &arena_storage[1], "" ); |
865 | ASSERT(a.allocate(2) == &arena_storage[2+1], "" ); |
866 | } |
867 | |
868 | template<typename static_counting_allocator_type> |
869 | void inline TestStaticCountingAllocatorRebound(){ |
870 | static_counting_allocator_type::set_limits(1); |
871 | typedef typename static_counting_allocator_type:: template rebind<std::pair<int,int> >::other rebound_type; |
872 | ASSERT(rebound_type::max_items == static_counting_allocator_type::max_items, "rebound allocator should use the same limits" ); |
873 | static_counting_allocator_type::set_limits(0); |
874 | } |
875 | |
876 | void inline TestStatefulAllocator(){ |
877 | stateful_allocator<int> a1,a2; |
878 | stateful_allocator<int> copy_of_a1(a1); |
879 | ASSERT(a1 != a2,"non_equal_allocator are designed to simulate stateful allocators" ); |
880 | ASSERT(copy_of_a1 == a1,"" ); |
881 | } |
882 | } |
883 | struct TestHelperStuff{ |
884 | TestHelperStuff(){ |
885 | using namespace helper_stuff_tests; |
886 | TestFoo(); |
887 | TestAllOf(); |
888 | TestArena(); |
889 | TestStaticCountingAllocatorRebound<static_shared_counting_allocator<int, arena<int> > >(); |
890 | TestStatefulAllocator(); |
891 | #if __TBB_CPP11_RVALUE_REF_PRESENT |
892 | TestMemoryLocaionsHelper(); |
893 | #endif //__TBB_CPP11_RVALUE_REF_PRESENT |
894 | } |
895 | }; |
896 | static TestHelperStuff TestHelperStuff_s; |
897 | #endif /* __TBB_test_container_move_support_H */ |
898 | |