1 /*
2 Copyright (c) 2005-2023 Intel Corporation
3
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
15 */
16
17 #include <common/test.h>
18 #include <common/utils.h>
19 #include <common/utils_report.h>
20 #include <common/custom_allocators.h>
21 #include <common/container_move_support.h>
22 #include <common/test_comparisons.h>
23
24 #include "oneapi/tbb/concurrent_queue.h"
25 #include "oneapi/tbb/cache_aligned_allocator.h"
26 #include <type_traits>
27 #include <atomic>
28
29 //! \file conformance_concurrent_queue.cpp
30 //! \brief Test for [containers.concurrent_queue containers.concurrent_bounded_queue] specification
31
32 template <typename T>
33 using test_allocator = StaticSharedCountingAllocator<oneapi::tbb::cache_aligned_allocator<T>>;
34
35 static constexpr std::size_t MinThread = 1;
36 static constexpr std::size_t MaxThread = 4;
37
38 static constexpr std::size_t MAXTHREAD = 256;
39
40 static constexpr std::size_t M = 10000;
41 static std::atomic<long> PopKind[3];
42
43 static int Sum[MAXTHREAD];
44
45 template<typename CQ, typename ValueType, typename CounterType>
push(CQ & q,ValueType v,CounterType i)46 void push(CQ& q, ValueType v, CounterType i) {
47 switch (i % 3) {
48 case 0: q.push( v); break;
49 case 1: q.push( std::move(v)); break;
50 case 2: q.emplace( v); break;
51 default: CHECK(false); break;
52 }
53 }
54
55 template<typename T>
56 class ConcQWithCapacity : public oneapi::tbb::concurrent_queue<T, test_allocator<T>> {
57 using base_type = oneapi::tbb::concurrent_queue<T, test_allocator<T>>;
58 public:
ConcQWithCapacity()59 ConcQWithCapacity() : my_capacity( std::size_t(-1) / (sizeof(void*) + sizeof(T)) ) {}
size() const60 std::size_t size() const {
61 return this->unsafe_size();
62 }
63
capacity() const64 std::size_t capacity() const {
65 return my_capacity;
66 }
67
set_capacity(const std::size_t n)68 void set_capacity( const std::size_t n ) {
69 my_capacity = n;
70 }
71
try_push(const T & source)72 bool try_push( const T& source ) {
73 base_type::push( source);
74 return source.get_serial() < my_capacity;
75 }
76
try_pop(T & dest)77 bool try_pop( T& dest ) {
78 base_type::try_pop( dest);
79 return dest.get_serial() < my_capacity;
80 }
81
82 private:
83 std::size_t my_capacity;
84 };
85
86 template<typename CQ, typename T>
TestEmptyQueue()87 void TestEmptyQueue() {
88 const CQ queue;
89 CHECK(queue.size() == 0);
90 CHECK(queue.capacity()> 0);
91 CHECK(size_t(queue.capacity())>= std::size_t(-1)/(sizeof(void*)+sizeof(T)));
92 }
93
TestEmptiness()94 void TestEmptiness() {
95 TestEmptyQueue<ConcQWithCapacity<char>, char>();
96 TestEmptyQueue<ConcQWithCapacity<move_support_tests::Foo>, move_support_tests::Foo>();
97 TestEmptyQueue<oneapi::tbb::concurrent_bounded_queue<char, test_allocator<char>>, char>();
98 TestEmptyQueue<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo,
99 test_allocator<move_support_tests::Foo>>, move_support_tests::Foo>();
100 }
101
102 template<typename CQ, typename T>
TestFullQueue()103 void TestFullQueue() {
104 using allocator_type = decltype(std::declval<CQ>().get_allocator());
105
106 for (std::size_t n = 0; n < 100; ++n) {
107 allocator_type::init_counters();
108 {
109 CQ queue;
110 queue.set_capacity(n);
111 for (std::size_t i = 0; i <= n; ++i) {
112 T f;
113 f.set_serial(i);
114 bool result = queue.try_push( f);
115 CHECK((result == (i < n)));
116 }
117
118 for (std::size_t i = 0; i <= n; ++i) {
119 T f;
120 bool result = queue.try_pop(f);
121 CHECK((result == (i < n)));
122 CHECK((result == 0 || f.get_serial() == i));
123 }
124 }
125 CHECK(allocator_type::items_allocated == allocator_type::items_freed);
126 CHECK(allocator_type::allocations == allocator_type::frees);
127 }
128 }
129
TestFullness()130 void TestFullness() {
131 TestFullQueue<ConcQWithCapacity<move_support_tests::Foo>, move_support_tests::Foo>();
132 TestFullQueue<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>, move_support_tests::Foo>();
133 }
134
135 template<typename CQ>
TestClear()136 void TestClear() {
137 using allocator_type = decltype(std::declval<CQ>().get_allocator());
138 allocator_type::init_counters();
139 const std::size_t n = 5;
140
141 CQ queue;
142 const std::size_t q_capacity = 10;
143 queue.set_capacity(q_capacity);
144
145 for (std::size_t i = 0; i < n; ++i) {
146 move_support_tests::Foo f;
147 f.set_serial(i);
148 queue.push(f);
149 }
150
151 CHECK(queue.size() == n);
152
153 queue.clear();
154 CHECK(queue.size()==0);
155 for (std::size_t i = 0; i < n; ++i) {
156 move_support_tests::Foo f;
157 f.set_serial(i);
158 queue.push( f);
159 }
160
161 CHECK(queue.size() == n);
162 queue.clear();
163 CHECK(queue.size() == 0);
164
165 for (std::size_t i = 0; i < n; ++i) {
166 move_support_tests::Foo f;
167 f.set_serial(i);
168 queue.push(f);
169 }
170
171 CHECK(queue.size()==n);
172 }
173
TestClearWorks()174 void TestClearWorks() {
175 TestClear<ConcQWithCapacity<move_support_tests::Foo>>();
176 TestClear<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>>();
177 }
178
179 template<typename Iterator1, typename Iterator2>
TestIteratorAux(Iterator1 i,Iterator2 j,int size)180 void TestIteratorAux( Iterator1 i, Iterator2 j, int size ) {
181 Iterator1 old_i; // assigned at first iteration below
182 for (std::size_t k = 0; k < (std::size_t)size; ++k) {
183 CHECK_FAST(i != j);
184 CHECK_FAST(!(i == j));
185 // Test "->"
186 CHECK_FAST((k+1 == i->get_serial()));
187 if (k & 1) {
188 // Test post-increment
189 move_support_tests::Foo f = *old_i++;
190 CHECK_FAST((k + 1 == f.get_serial()));
191 // Test assignment
192 i = old_i;
193 } else {
194 // Test pre-increment
195 if (k < std::size_t(size - 1)) {
196 move_support_tests::Foo f = *++i;
197 CHECK_FAST((k + 2 == f.get_serial()));
198 } else ++i;
199 // Test assignment
200 old_i = i;
201 }
202 }
203 CHECK_FAST(!(i != j));
204 CHECK_FAST(i == j);
205 }
206
207 template<typename Iterator1, typename Iterator2>
TestIteratorAssignment(Iterator2 j)208 void TestIteratorAssignment( Iterator2 j ) {
209 Iterator1 i(j);
210 CHECK(i == j);
211 CHECK(!(i != j));
212
213 Iterator1 k;
214 k = j;
215 CHECK(k == j);
216 CHECK(!(k != j));
217 }
218
219 template<typename Iterator, typename T>
TestIteratorTraits()220 void TestIteratorTraits() {
221 static_assert( std::is_same<typename Iterator::iterator_category, std::forward_iterator_tag>::value, "wrong iterator category");
222
223 T x;
224
225 typename Iterator::reference xr = x;
226 typename Iterator::pointer xp = &x;
227 CHECK((&xr == xp));
228 }
229
230 // Test the iterators for concurrent_queue
231 template <typename CQ>
TestIterator()232 void TestIterator() {
233 CQ queue;
234 const CQ& const_queue = queue;
235 for (int j=0; j < 500; ++j) {
236 TestIteratorAux( queue.unsafe_begin() , queue.unsafe_end() , j);
237 TestIteratorAux( queue.unsafe_cbegin() , queue.unsafe_cend() , j);
238 TestIteratorAux( const_queue.unsafe_begin(), const_queue.unsafe_end(), j);
239 TestIteratorAux( const_queue.unsafe_begin(), queue.unsafe_end() , j);
240 TestIteratorAux( queue.unsafe_begin() , const_queue.unsafe_end(), j);
241 move_support_tests::Foo f;
242 f.set_serial(j+1);
243 queue.push(f);
244 }
245 TestIteratorAssignment<typename CQ::const_iterator>( const_queue.unsafe_begin());
246 TestIteratorAssignment<typename CQ::const_iterator>( queue.unsafe_begin());
247 TestIteratorAssignment<typename CQ::iterator>( queue.unsafe_begin());
248 TestIteratorTraits<typename CQ::const_iterator, const move_support_tests::Foo>();
249 TestIteratorTraits<typename CQ::iterator, move_support_tests::Foo>();
250 }
251
TestQueueIteratorWorks()252 void TestQueueIteratorWorks() {
253 TestIterator<oneapi::tbb::concurrent_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>>();
254 TestIterator<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>>();
255 }
256
257 // Define wrapper classes to test oneapi::tbb::concurrent_queue<T>
258 template<typename T, typename A = oneapi::tbb::cache_aligned_allocator<T>>
259 class ConcQWithSizeWrapper : public oneapi::tbb::concurrent_queue<T, A> {
260 public:
ConcQWithSizeWrapper()261 ConcQWithSizeWrapper() {}
ConcQWithSizeWrapper(const ConcQWithSizeWrapper & q)262 ConcQWithSizeWrapper( const ConcQWithSizeWrapper& q ) : oneapi::tbb::concurrent_queue<T, A>(q) {}
ConcQWithSizeWrapper(const ConcQWithSizeWrapper & q,const A & a)263 ConcQWithSizeWrapper( const ConcQWithSizeWrapper& q, const A& a ) : oneapi::tbb::concurrent_queue<T, A>(q, a) {}
ConcQWithSizeWrapper(const A & a)264 ConcQWithSizeWrapper( const A& a ) : oneapi::tbb::concurrent_queue<T, A>( a ) {}
265
ConcQWithSizeWrapper(ConcQWithSizeWrapper && q)266 ConcQWithSizeWrapper( ConcQWithSizeWrapper&& q ) : oneapi::tbb::concurrent_queue<T>(std::move(q)) {}
ConcQWithSizeWrapper(ConcQWithSizeWrapper && q,const A & a)267 ConcQWithSizeWrapper( ConcQWithSizeWrapper&& q, const A& a )
268 : oneapi::tbb::concurrent_queue<T, A>(std::move(q), a) { }
269
270 template<typename InputIterator>
ConcQWithSizeWrapper(InputIterator begin,InputIterator end,const A & a=A ())271 ConcQWithSizeWrapper( InputIterator begin, InputIterator end, const A& a = A() )
272 : oneapi::tbb::concurrent_queue<T, A>(begin, end, a) {}
size() const273 typename oneapi::tbb::concurrent_queue<T, A>::size_type size() const { return this->unsafe_size(); }
274 };
275
276 enum state_type {
277 LIVE = 0x1234,
278 DEAD = 0xDEAD
279 };
280
281 class Bar {
282 state_type state;
283 public:
284 static std::size_t construction_num, destruction_num;
285 std::ptrdiff_t my_id;
Bar()286 Bar() : state(LIVE), my_id(-1)
287 {}
288
Bar(std::size_t _i)289 Bar( std::size_t _i ) : state(LIVE), my_id(_i) { construction_num++; }
290
Bar(const Bar & a_bar)291 Bar( const Bar& a_bar ) : state(LIVE) {
292 CHECK_FAST(a_bar.state == LIVE);
293 my_id = a_bar.my_id;
294 construction_num++;
295 }
296
~Bar()297 ~Bar() {
298 CHECK_FAST(state == LIVE);
299 state = DEAD;
300 my_id = DEAD;
301 destruction_num++;
302 }
303
operator =(const Bar & a_bar)304 void operator=( const Bar& a_bar ) {
305 CHECK_FAST(a_bar.state == LIVE);
306 CHECK_FAST(state == LIVE);
307 my_id = a_bar.my_id;
308 }
309 friend bool operator==( const Bar& bar1, const Bar& bar2 ) ;
310 };
311
312 std::size_t Bar::construction_num = 0;
313 std::size_t Bar::destruction_num = 0;
314
operator ==(const Bar & bar1,const Bar & bar2)315 bool operator==( const Bar& bar1, const Bar& bar2 ) {
316 CHECK_FAST(bar1.state == LIVE);
317 CHECK_FAST(bar2.state == LIVE);
318 return bar1.my_id == bar2.my_id;
319 }
320
321 class BarIterator {
322 Bar* bar_ptr;
BarIterator(Bar * bp_)323 BarIterator(Bar* bp_) : bar_ptr(bp_) {}
324 public:
operator *() const325 Bar& operator*() const {
326 return *bar_ptr;
327 }
operator ++()328 BarIterator& operator++() {
329 ++bar_ptr;
330 return *this;
331 }
operator ++(int)332 Bar* operator++(int) {
333 Bar* result = &operator*();
334 operator++();
335 return result;
336 }
337 friend bool operator==(const BarIterator& bia, const BarIterator& bib) ;
338 friend bool operator!=(const BarIterator& bia, const BarIterator& bib) ;
339 template<typename CQ, typename T, typename TIter, typename CQ_EX, typename T_EX>
340 friend void TestConstructors ();
341 } ;
342
operator ==(const BarIterator & bia,const BarIterator & bib)343 bool operator==(const BarIterator& bia, const BarIterator& bib) {
344 return bia.bar_ptr==bib.bar_ptr;
345 }
346
operator !=(const BarIterator & bia,const BarIterator & bib)347 bool operator!=(const BarIterator& bia, const BarIterator& bib) {
348 return bia.bar_ptr!=bib.bar_ptr;
349 }
350
351
352 class Bar_exception : public std::bad_alloc {
353 public:
what() const354 virtual const char *what() const noexcept override { return "making the entry invalid"; }
~Bar_exception()355 virtual ~Bar_exception() noexcept {}
356 };
357
358 class BarEx {
359 static int count;
360 public:
361 state_type state;
362 typedef enum {
363 PREPARATION,
364 COPY_CONSTRUCT
365 } mode_type;
366 static mode_type mode;
367 std::ptrdiff_t my_id;
368 std::ptrdiff_t my_tilda_id;
369
370 static int button;
371
BarEx()372 BarEx() : state(LIVE), my_id(-1), my_tilda_id(-1)
373 {}
374
BarEx(std::size_t _i)375 BarEx(std::size_t _i) : state(LIVE), my_id(_i), my_tilda_id(my_id^(-1))
376 {}
377
BarEx(const BarEx & a_bar)378 BarEx( const BarEx& a_bar ) : state(LIVE) {
379 CHECK_FAST(a_bar.state == LIVE);
380 my_id = a_bar.my_id;
381 if (mode == PREPARATION)
382 if (!(++count % 100)) {
383 TBB_TEST_THROW(Bar_exception());
384 }
385 my_tilda_id = a_bar.my_tilda_id;
386 }
387
~BarEx()388 ~BarEx() {
389 CHECK_FAST(state == LIVE);
390 state = DEAD;
391 my_id = DEAD;
392 }
set_mode(mode_type m)393 static void set_mode( mode_type m ) { mode = m; }
394
operator =(const BarEx & a_bar)395 void operator=( const BarEx& a_bar ) {
396 CHECK_FAST(a_bar.state == LIVE);
397 CHECK_FAST(state == LIVE);
398 my_id = a_bar.my_id;
399 my_tilda_id = a_bar.my_tilda_id;
400 }
401
402 friend bool operator==(const BarEx& bar1, const BarEx& bar2 ) ;
403 };
404
405 int BarEx::count = 0;
406 BarEx::mode_type BarEx::mode = BarEx::PREPARATION;
407
operator ==(const BarEx & bar1,const BarEx & bar2)408 bool operator==(const BarEx& bar1, const BarEx& bar2) {
409 CHECK_FAST(bar1.state == LIVE);
410 CHECK_FAST(bar2.state == LIVE);
411 CHECK_FAST((bar1.my_id ^ bar1.my_tilda_id) == -1);
412 CHECK_FAST((bar2.my_id ^ bar2.my_tilda_id) == -1);
413 return bar1.my_id == bar2.my_id && bar1.my_tilda_id == bar2.my_tilda_id;
414 }
415
416 template<typename CQ, typename T, typename TIter, typename CQ_EX, typename T_EX>
TestConstructors()417 void TestConstructors () {
418 CQ src_queue;
419 typename CQ::const_iterator dqb;
420 typename CQ::const_iterator dqe;
421 typename CQ::const_iterator iter;
422 using size_type = typename CQ::size_type;
423
424 for (size_type size = 0; size < 1001; ++size) {
425 for (size_type i = 0; i < size; ++i)
426 src_queue.push(T(i + (i ^ size)));
427 typename CQ::const_iterator sqb( src_queue.unsafe_begin());
428 typename CQ::const_iterator sqe( src_queue.unsafe_end() );
429
430 CQ dst_queue(sqb, sqe);
431 CQ copy_with_alloc(src_queue, typename CQ::allocator_type());
432
433 CHECK_FAST_MESSAGE(src_queue.size() == dst_queue.size(), "different size");
434 CHECK_FAST_MESSAGE(src_queue.size() == copy_with_alloc.size(), "different size");
435
436 src_queue.clear();
437 }
438
439 T bar_array[1001];
440 for (size_type size=0; size < 1001; ++size) {
441 for (size_type i=0; i < size; ++i) {
442 bar_array[i] = T(i+(i^size));
443 }
444
445 const TIter sab(bar_array + 0);
446 const TIter sae(bar_array + size);
447
448 CQ dst_queue2(sab, sae);
449
450 CHECK_FAST(size == dst_queue2.size());
451 CHECK_FAST(sab == TIter(bar_array+0));
452 CHECK_FAST(sae == TIter(bar_array+size));
453
454 dqb = dst_queue2.unsafe_begin();
455 dqe = dst_queue2.unsafe_end();
456 auto res = std::mismatch(dqb, dqe, bar_array);
457 CHECK_FAST_MESSAGE(res.first == dqe, "unexpected element");
458 CHECK_FAST_MESSAGE(res.second == bar_array + size, "different size?");
459 }
460
461 src_queue.clear();
462
463 CQ dst_queue3(src_queue);
464 CHECK(src_queue.size() == dst_queue3.size());
465 CHECK(0 == dst_queue3.size());
466
467 int k = 0;
468 for (size_type i = 0; i < 1001; ++i) {
469 T tmp_bar;
470 src_queue.push(T(++k));
471 src_queue.push(T(++k));
472 src_queue.try_pop(tmp_bar);
473
474 CQ dst_queue4( src_queue);
475
476 CHECK_FAST(src_queue.size() == dst_queue4.size());
477
478 dqb = dst_queue4.unsafe_begin();
479 dqe = dst_queue4.unsafe_end();
480 iter = src_queue.unsafe_begin();
481 auto res = std::mismatch(dqb, dqe, iter);
482 CHECK_FAST_MESSAGE(res.first == dqe, "unexpected element");
483 CHECK_FAST_MESSAGE(res.second == src_queue.unsafe_end(), "different size?");
484 }
485
486 CQ dst_queue5(src_queue);
487
488 CHECK(src_queue.size() == dst_queue5.size());
489 dqb = dst_queue5.unsafe_begin();
490 dqe = dst_queue5.unsafe_end();
491 iter = src_queue.unsafe_begin();
492 REQUIRE_MESSAGE(std::equal(dqb, dqe, iter), "unexpected element");
493
494 for (size_type i=0; i<100; ++i) {
495 T tmp_bar;
496 src_queue.push(T(i + 1000));
497 src_queue.push(T(i + 1000));
498 src_queue.try_pop(tmp_bar);
499
500 dst_queue5.push(T(i + 1000));
501 dst_queue5.push(T(i + 1000));
502 dst_queue5.try_pop(tmp_bar);
503 }
504
505 CHECK(src_queue.size() == dst_queue5.size());
506 dqb = dst_queue5.unsafe_begin();
507 dqe = dst_queue5.unsafe_end();
508 iter = src_queue.unsafe_begin();
509 auto res = std::mismatch(dqb, dqe, iter);
510 REQUIRE_MESSAGE(res.first == dqe, "unexpected element");
511 REQUIRE_MESSAGE(res.second == src_queue.unsafe_end(), "different size?");
512
513 #if TBB_USE_EXCEPTIONS
514 k = 0;
515 typename CQ_EX::size_type n_elements = 0;
516 CQ_EX src_queue_ex;
517 for (size_type size = 0; size < 1001; ++size) {
518 T_EX tmp_bar_ex;
519 typename CQ_EX::size_type n_successful_pushes = 0;
520 T_EX::set_mode(T_EX::PREPARATION);
521 try {
522 src_queue_ex.push(T_EX(k + (k ^ size)));
523 ++n_successful_pushes;
524 } catch (...) {
525 }
526 ++k;
527 try {
528 src_queue_ex.push(T_EX(k + (k ^ size)));
529 ++n_successful_pushes;
530 } catch (...) {
531 }
532 ++k;
533 src_queue_ex.try_pop(tmp_bar_ex);
534 n_elements += (n_successful_pushes - 1);
535 CHECK_FAST(src_queue_ex.size() == n_elements);
536
537 T_EX::set_mode(T_EX::COPY_CONSTRUCT);
538 CQ_EX dst_queue_ex(src_queue_ex);
539
540 CHECK_FAST(src_queue_ex.size() == dst_queue_ex.size());
541
542 typename CQ_EX::const_iterator dqb_ex = dst_queue_ex.unsafe_begin();
543 typename CQ_EX::const_iterator dqe_ex = dst_queue_ex.unsafe_end();
544 typename CQ_EX::const_iterator iter_ex = src_queue_ex.unsafe_begin();
545
546 auto res2 = std::mismatch(dqb_ex, dqe_ex, iter_ex);
547 CHECK_FAST_MESSAGE(res2.first == dqe_ex, "unexpected element");
548 CHECK_FAST_MESSAGE(res2.second == src_queue_ex.unsafe_end(), "different size?");
549 }
550 #endif
551 src_queue.clear();
552
553 for (size_type size = 0; size < 1001; ++size) {
554 for (size_type i = 0; i < size; ++i) {
555 src_queue.push(T(i + (i ^ size)));
556 }
557 std::vector<const T*> locations(size);
558 typename CQ::const_iterator qit = src_queue.unsafe_begin();
559 for (size_type i = 0; i < size; ++i, ++qit) {
560 locations[i] = &(*qit);
561 }
562
563 size_type size_of_queue = src_queue.size();
564 CQ dst_queue(std::move(src_queue));
565
566 CHECK_FAST_MESSAGE((src_queue.empty() && src_queue.size() == 0), "not working move constructor?");
567 CHECK_FAST_MESSAGE((size == size_of_queue && size_of_queue == dst_queue.size()), "not working move constructor?");
568
569 CHECK_FAST_MESSAGE(
570 std::equal(locations.begin(), locations.end(), dst_queue.unsafe_begin(), [](const T* t1, const T& r2) { return t1 == &r2; }),
571 "there was data movement during move constructor"
572 );
573
574 for (size_type i = 0; i < size; ++i) {
575 T test(i + (i ^ size));
576 T popped;
577 bool pop_result = dst_queue.try_pop( popped);
578
579 CHECK_FAST(pop_result);
580 CHECK_FAST(test == popped);
581 }
582 }
583 }
584
TestQueueConstructors()585 void TestQueueConstructors() {
586 TestConstructors<ConcQWithSizeWrapper<Bar>, Bar, BarIterator, ConcQWithSizeWrapper<BarEx>, BarEx>();
587 TestConstructors<oneapi::tbb::concurrent_bounded_queue<Bar>, Bar, BarIterator, oneapi::tbb::concurrent_bounded_queue<BarEx>, BarEx>();
588 }
589
590 template<typename T>
591 struct TestNegativeQueueBody {
592 oneapi::tbb::concurrent_bounded_queue<T>& queue;
593 const std::size_t nthread;
TestNegativeQueueBodyTestNegativeQueueBody594 TestNegativeQueueBody( oneapi::tbb::concurrent_bounded_queue<T>& q, std::size_t n ) : queue(q), nthread(n) {}
operator ()TestNegativeQueueBody595 void operator()( std::size_t k ) const {
596 if (k == 0) {
597 int number_of_pops = int(nthread) - 1;
598 // Wait for all pops to pend.
599 while (int(queue.size())> -number_of_pops) {
600 utils::yield();
601 }
602
603 for (int i = 0; ; ++i) {
604 CHECK(queue.size() == std::size_t(i - number_of_pops));
605 CHECK((queue.empty() == (queue.size() <= 0)));
606 if (i == number_of_pops) break;
607 // Satisfy another pop
608 queue.push(T());
609 }
610 } else {
611 // Pop item from queue
612 T item;
613 queue.pop(item);
614 }
615 }
616 };
617
618 //! Test a queue with a negative size.
619 template<typename T>
TestNegativeQueue(std::size_t nthread)620 void TestNegativeQueue( std::size_t nthread ) {
621 oneapi::tbb::concurrent_bounded_queue<T> queue;
622 utils::NativeParallelFor( nthread, TestNegativeQueueBody<T>(queue,nthread));
623 }
624
625 template<typename T>
626 class ConcQPushPopWrapper : public oneapi::tbb::concurrent_queue<T, test_allocator<T>> {
627 public:
ConcQPushPopWrapper()628 ConcQPushPopWrapper() : my_capacity(std::size_t(-1) / (sizeof(void*) + sizeof(T)))
629 {}
630
size() const631 std::size_t size() const { return this->unsafe_size(); }
set_capacity(const ptrdiff_t n)632 void set_capacity( const ptrdiff_t n ) { my_capacity = n; }
try_push(const T & source)633 bool try_push( const T& source ) { return this->push( source); }
try_pop(T & dest)634 bool try_pop( T& dest ) { return this->oneapi::tbb::concurrent_queue<T, test_allocator<T>>::try_pop(dest); }
635 std::size_t my_capacity;
636 };
637
638 template<typename CQ, typename T>
639 struct Body {
640 CQ* queue;
641 const std::size_t nthread;
BodyBody642 Body( std::size_t nthread_ ) : nthread(nthread_) {}
operator ()Body643 void operator()( std::size_t thread_id ) const {
644 long pop_kind[3] = {0, 0, 0};
645 std::size_t serial[MAXTHREAD + 1];
646 memset(serial, 0, nthread * sizeof(std::size_t));
647 CHECK(thread_id < nthread);
648
649 long sum = 0;
650 for (std::size_t j = 0; j < M; ++j) {
651 T f;
652 f.set_thread_id(move_support_tests::serial_dead_state);
653 f.set_serial(move_support_tests::serial_dead_state);
654 bool prepopped = false;
655 if (j & 1) {
656 prepopped = queue->try_pop(f);
657 ++pop_kind[prepopped];
658 }
659 T g;
660 g.set_thread_id(thread_id);
661 g.set_serial(j + 1);
662 push(*queue, g, j);
663 if (!prepopped) {
664 while(!(queue)->try_pop(f)) utils::yield();
665 ++pop_kind[2];
666 }
667 CHECK_FAST(f.get_thread_id() <= nthread);
668 CHECK_FAST_MESSAGE((f.get_thread_id() == nthread || serial[f.get_thread_id()] < f.get_serial()), "partial order violation");
669 serial[f.get_thread_id()] = f.get_serial();
670 sum += int(f.get_serial() - 1);
671 }
672 Sum[thread_id] = sum;
673 for (std::size_t k = 0; k < 3; ++k)
674 PopKind[k] += pop_kind[k];
675 }
676 };
677
678 template<typename CQ, typename T>
TestPushPop(typename CQ::size_type prefill,std::ptrdiff_t capacity,std::size_t nthread)679 void TestPushPop(typename CQ::size_type prefill, std::ptrdiff_t capacity, std::size_t nthread ) {
680 using allocator_type = decltype(std::declval<CQ>().get_allocator());
681 CHECK(nthread> 0);
682 std::ptrdiff_t signed_prefill = std::ptrdiff_t(prefill);
683
684 if (signed_prefill + 1>= capacity) {
685 return;
686 }
687
688 bool success = false;
689 for (std::size_t k=0; k < 3; ++k) {
690 PopKind[k] = 0;
691 }
692
693 for (std::size_t trial = 0; !success; ++trial) {
694 allocator_type::init_counters();
695 Body<CQ,T> body(nthread);
696 CQ queue;
697 queue.set_capacity(capacity);
698 body.queue = &queue;
699 for (typename CQ::size_type i = 0; i < prefill; ++i) {
700 T f;
701 f.set_thread_id(nthread);
702 f.set_serial(1 + i);
703 push(queue, f, i);
704 CHECK_FAST(queue.size() == i + 1);
705 CHECK_FAST(!queue.empty());
706 }
707
708 utils::NativeParallelFor( nthread, body);
709
710 int sum = 0;
711 for (std::size_t k = 0; k < nthread; ++k) {
712 sum += Sum[k];
713 }
714
715 int expected = int( nthread * ((M - 1) * M / 2) + ((prefill - 1) * prefill) / 2);
716 for (int i = int(prefill); --i>=0;) {
717 CHECK_FAST(!queue.empty());
718 T f;
719 bool result = queue.try_pop(f);
720 CHECK_FAST(result);
721 CHECK_FAST(int(queue.size()) == i);
722 sum += int(f.get_serial()) - 1;
723 }
724 REQUIRE_MESSAGE(queue.empty(), "The queue should be empty");
725 REQUIRE_MESSAGE(queue.size() == 0, "The queue should have zero size");
726 if (sum != expected) {
727 REPORT("sum=%d expected=%d\n",sum,expected);
728 }
729
730 success = true;
731 if (nthread> 1 && prefill == 0) {
732 // Check that pop_if_present got sufficient exercise
733 for (std::size_t k = 0; k < 2; ++k) {
734 const int min_requirement = 100;
735 const int max_trial = 20;
736
737 if (PopKind[k] < min_requirement) {
738 if (trial>= max_trial) {
739 REPORT("Warning: %d threads had only %ld pop_if_present operations %s after %d trials (expected at least %d). "
740 "This problem may merely be unlucky scheduling. "
741 "Investigate only if it happens repeatedly.\n",
742 nthread, long(PopKind[k]), k==0?"failed":"succeeded", max_trial, min_requirement);
743 } else {
744 success = false;
745 }
746 }
747 }
748 }
749 }
750 }
751
TestConcurrentPushPop()752 void TestConcurrentPushPop() {
753 for (std::size_t nthread = MinThread; nthread <= MaxThread; ++nthread) {
754 INFO(" Testing with "<< nthread << " thread(s)");
755 TestNegativeQueue<move_support_tests::Foo>(nthread);
756 for (std::size_t prefill=0; prefill < 64; prefill += (1 + prefill / 3)) {
757 TestPushPop<ConcQPushPopWrapper<move_support_tests::Foo>, move_support_tests::Foo>(prefill, std::ptrdiff_t(-1), nthread);
758 TestPushPop<ConcQPushPopWrapper<move_support_tests::Foo>, move_support_tests::Foo>(prefill, std::ptrdiff_t(1), nthread);
759 TestPushPop<ConcQPushPopWrapper<move_support_tests::Foo>, move_support_tests::Foo>(prefill, std::ptrdiff_t(2), nthread);
760 TestPushPop<ConcQPushPopWrapper<move_support_tests::Foo>, move_support_tests::Foo>(prefill, std::ptrdiff_t(10), nthread);
761 TestPushPop<ConcQPushPopWrapper<move_support_tests::Foo>, move_support_tests::Foo>(prefill, std::ptrdiff_t(100), nthread);
762 }
763 for (std::size_t prefill = 0; prefill < 64; prefill += (1 + prefill / 3) ) {
764 TestPushPop<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>,
765 move_support_tests::Foo>(prefill, std::ptrdiff_t(-1), nthread);
766 TestPushPop<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>,
767 move_support_tests::Foo>(prefill, std::ptrdiff_t(1), nthread);
768 TestPushPop<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>,
769 move_support_tests::Foo>(prefill, std::ptrdiff_t(2), nthread);
770 TestPushPop<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>,
771 move_support_tests::Foo>(prefill, std::ptrdiff_t(10), nthread);
772 TestPushPop<oneapi::tbb::concurrent_bounded_queue<move_support_tests::Foo, test_allocator<move_support_tests::Foo>>,
773 move_support_tests::Foo>(prefill, std::ptrdiff_t(100), nthread);
774 }
775 }
776 }
777
778 class Foo_exception : public std::bad_alloc {
779 public:
what() const780 virtual const char *what() const throw() override { return "out of Foo limit"; }
~Foo_exception()781 virtual ~Foo_exception() throw() {}
782 };
783
784 #if TBB_USE_EXCEPTIONS
785 static std::atomic<long> FooExConstructed;
786 static std::atomic<long> FooExDestroyed;
787 static std::atomic<long> serial_source;
788 static long MaxFooCount = 0;
789 static const long Threshold = 400;
790
791 class FooEx {
792 state_type state;
793 public:
794 int serial;
FooEx()795 FooEx() : state(LIVE) {
796 ++FooExConstructed;
797 serial = serial_source++;
798 }
799
FooEx(const FooEx & item)800 FooEx( const FooEx& item ) : state(LIVE) {
801 CHECK(item.state == LIVE);
802 ++FooExConstructed;
803 if (MaxFooCount && (FooExConstructed - FooExDestroyed) >= MaxFooCount) { // in push()
804 throw Foo_exception();
805 }
806 serial = item.serial;
807 }
808
~FooEx()809 ~FooEx() {
810 CHECK(state==LIVE);
811 ++FooExDestroyed;
812 state=DEAD;
813 serial=DEAD;
814 }
815
operator =(FooEx & item)816 void operator=( FooEx& item ) {
817 CHECK(item.state==LIVE);
818 CHECK(state==LIVE);
819 serial = item.serial;
820 if( MaxFooCount==2*Threshold && (FooExConstructed-FooExDestroyed) <= MaxFooCount/4 ) // in pop()
821 throw Foo_exception();
822 }
823
operator =(FooEx && item)824 void operator=( FooEx&& item ) {
825 operator=( item );
826 item.serial = 0;
827 }
828
829 };
830
831 template <template <typename, typename> class CQ, typename A1, typename A2, typename T>
TestExceptionBody()832 void TestExceptionBody() {
833 enum methods {
834 m_push = 0,
835 m_pop
836 };
837
838 const int N = 1000; // # of bytes
839
840 MaxFooCount = 5;
841
842 try {
843 int n_pushed=0, n_popped=0;
844 for(int t = 0; t <= 1; t++)// exception type -- 0 : from allocator(), 1 : from Foo's constructor
845 {
846 CQ<T,A1> queue_test;
847 for( int m=m_push; m<=m_pop; m++ ) {
848 // concurrent_queue internally rebinds the allocator to the one for 'char'
849 A2::init_counters();
850
851 if(t) MaxFooCount = MaxFooCount + 400;
852 else A2::set_limits(N/2);
853
854 try {
855 switch(m) {
856 case m_push:
857 for( int k=0; k<N; k++ ) {
858 push( queue_test, T(), k);
859 n_pushed++;
860 }
861 break;
862 case m_pop:
863 n_popped=0;
864 for( int k=0; k<n_pushed; k++ ) {
865 T elt;
866 queue_test.try_pop( elt);
867 n_popped++;
868 }
869 n_pushed = 0;
870 A2::set_limits();
871 break;
872 }
873 if( !t && m==m_push ) REQUIRE_MESSAGE(false, "should throw an exception");
874 } catch ( Foo_exception & ) {
875 long tc = MaxFooCount;
876 MaxFooCount = 0; // disable exception
877 switch(m) {
878 case m_push:
879 REQUIRE_MESSAGE(ptrdiff_t(queue_test.size())==n_pushed, "incorrect queue size");
880 for( int k=0; k<(int)tc; k++ ) {
881 push( queue_test, T(), k);
882 n_pushed++;
883 }
884 break;
885 case m_pop:
886 n_pushed -= (n_popped+1); // including one that threw the exception
887 REQUIRE_MESSAGE(n_pushed>=0, "n_pushed cannot be less than 0");
888 for( int k=0; k<1000; k++ ) {
889 push( queue_test, T(), k);
890 n_pushed++;
891 }
892 REQUIRE_MESSAGE(!queue_test.empty(), "queue must not be empty");
893 REQUIRE_MESSAGE(ptrdiff_t(queue_test.size())==n_pushed, "queue size must be equal to n pushed");
894 for( int k=0; k<n_pushed; k++ ) {
895 T elt;
896 queue_test.try_pop( elt);
897 }
898 REQUIRE_MESSAGE(queue_test.empty(), "queue must be empty");
899 REQUIRE_MESSAGE(queue_test.size()==0, "queue must be empty");
900 break;
901 }
902 MaxFooCount = tc;
903 } catch ( std::bad_alloc & ) {
904 A2::set_limits(); // disable exception from allocator
905 std::size_t size = queue_test.size();
906 switch(m) {
907 case m_push:
908 REQUIRE_MESSAGE(size>0, "incorrect queue size");
909 break;
910 case m_pop:
911 if( !t ) REQUIRE_MESSAGE(false, "should not throw an exception");
912 break;
913 }
914 }
915 INFO("for t= " << t << "and m= " << m << " exception test passed");
916 }
917 }
918 } catch(...) {
919 REQUIRE_MESSAGE(false, "unexpected exception");
920 }
921 }
922
TestExceptions()923 void TestExceptions() {
924 using allocator_t = StaticSharedCountingAllocator<oneapi::tbb::cache_aligned_allocator<std::size_t>>;
925 using allocator_char_t = StaticSharedCountingAllocator<oneapi::tbb::cache_aligned_allocator<char>>;
926 TestExceptionBody<ConcQWithSizeWrapper, allocator_t, allocator_char_t, FooEx>();
927 TestExceptionBody<oneapi::tbb::concurrent_bounded_queue, allocator_t, allocator_char_t, FooEx>();
928
929 }
930
931 std::atomic<std::size_t> num_pushed;
932 std::atomic<std::size_t> num_popped;
933 std::atomic<std::size_t> failed_pushes;
934 std::atomic<std::size_t> failed_pops;
935
936 class SimplePushBody {
937 oneapi::tbb::concurrent_bounded_queue<int>* q;
938 std::size_t max;
939 public:
SimplePushBody(oneapi::tbb::concurrent_bounded_queue<int> * _q,std::size_t hi_thr)940 SimplePushBody(oneapi::tbb::concurrent_bounded_queue<int>* _q, std::size_t hi_thr) : q(_q), max(hi_thr) {}
941
operator ()(std::size_t thread_id) const942 void operator()(std::size_t thread_id) const {
943 if (thread_id == max) {
944 while ( q->size() < std::ptrdiff_t(max) ) {
945 utils::yield();
946 }
947 q->abort();
948 return;
949 }
950 try {
951 q->push(42);
952 ++num_pushed;
953 } catch (...) {
954 ++failed_pushes;
955 }
956 }
957 };
958
959 class SimplePopBody {
960 oneapi::tbb::concurrent_bounded_queue<int>* q;
961 std::ptrdiff_t max;
962 std::ptrdiff_t prefill;
963 public:
SimplePopBody(oneapi::tbb::concurrent_bounded_queue<int> * _q,std::size_t hi_thr,std::size_t nitems)964 SimplePopBody(oneapi::tbb::concurrent_bounded_queue<int>* _q, std::size_t hi_thr, std::size_t nitems)
965 : q(_q), max(hi_thr), prefill(nitems) {}
966
operator ()(std::size_t thread_id) const967 void operator()(std::size_t thread_id) const {
968 int e;
969 if (thread_id == std::size_t(max)) {
970 while (q->size()> prefill - max) {
971 utils::yield();
972 }
973
974 q->abort();
975 return;
976 }
977 try {
978 q->pop(e);
979 ++num_popped;
980 } catch ( ... ) {
981 ++failed_pops;
982 }
983 }
984 };
985
TestAbort()986 void TestAbort() {
987 for (std::size_t nthreads = MinThread; nthreads <= MaxThread; ++nthreads) {
988 oneapi::tbb::concurrent_bounded_queue<int> iq1;
989 iq1.set_capacity(0);
990 for (std::size_t i = 0; i < 10; ++i) {
991 num_pushed.store(0, std::memory_order_relaxed);
992 num_popped.store(0, std::memory_order_relaxed);
993 failed_pushes.store(0, std::memory_order_relaxed);
994 failed_pops.store(0, std::memory_order_relaxed);
995 SimplePushBody my_push_body1(&iq1, nthreads);
996 utils::NativeParallelFor(nthreads + 1, my_push_body1);
997 REQUIRE_MESSAGE(num_pushed == 0, "no elements should have been pushed to zero-sized queue");
998 REQUIRE_MESSAGE(failed_pushes == nthreads, "All threads should have failed to push an element to zero-sized queue");
999 // Do not test popping each time in order to test queue destruction with no previous pops
1000 if (nthreads < (MaxThread + MinThread) / 2) {
1001 int e;
1002 bool queue_empty = !iq1.try_pop(e);
1003 REQUIRE_MESSAGE(queue_empty, "no elements should have been popped from zero-sized queue");
1004 }
1005 }
1006
1007 oneapi::tbb::concurrent_bounded_queue<int> iq2;
1008 iq2.set_capacity(2);
1009 for (std::size_t i=0; i < 10; ++i) {
1010 num_pushed.store(0, std::memory_order_relaxed);
1011 num_popped.store(0, std::memory_order_relaxed);
1012 failed_pushes.store(0, std::memory_order_relaxed);
1013 failed_pops.store(0, std::memory_order_relaxed);
1014 SimplePushBody my_push_body2(&iq2, nthreads);
1015 utils::NativeParallelFor(nthreads + 1, my_push_body2);
1016 REQUIRE_MESSAGE(num_pushed <= 2, "at most 2 elements should have been pushed to queue of size 2");
1017 if (nthreads>= 2)
1018 REQUIRE_MESSAGE(failed_pushes == nthreads - 2, "nthreads-2 threads should have failed to push an element to queue of size 2");
1019 int e;
1020 while (iq2.try_pop(e)) ;
1021 }
1022
1023 oneapi::tbb::concurrent_bounded_queue<int> iq3;
1024 iq3.set_capacity(2);
1025 for (std::size_t i = 0; i < 10; ++i) {
1026 num_pushed.store(0, std::memory_order_relaxed);
1027 num_popped.store(0, std::memory_order_relaxed);
1028 failed_pushes.store(0, std::memory_order_relaxed);
1029 failed_pops.store(0, std::memory_order_relaxed);
1030 iq3.push(42);
1031 iq3.push(42);
1032 SimplePopBody my_pop_body(&iq3, nthreads, 2);
1033 utils::NativeParallelFor( nthreads+1, my_pop_body );
1034 REQUIRE_MESSAGE(num_popped <= 2, "at most 2 elements should have been popped from queue of size 2");
1035 if (nthreads>= 2)
1036 REQUIRE_MESSAGE(failed_pops == nthreads - 2, "nthreads-2 threads should have failed to pop an element from queue of size 2");
1037 else {
1038 int e;
1039 iq3.pop(e);
1040 }
1041 }
1042
1043 oneapi::tbb::concurrent_bounded_queue<int> iq4;
1044 std::size_t cap = nthreads / 2;
1045 if (!cap) cap = 1;
1046 iq4.set_capacity(cap);
1047 for (int i=0; i<10; ++i) {
1048 num_pushed.store(0, std::memory_order_relaxed);
1049 num_popped.store(0, std::memory_order_relaxed);
1050 failed_pushes.store(0, std::memory_order_relaxed);
1051 failed_pops.store(0, std::memory_order_relaxed);
1052 SimplePushBody my_push_body2(&iq4, nthreads);
1053 utils::NativeParallelFor(nthreads + 1, my_push_body2);
1054 REQUIRE_MESSAGE(num_pushed <= cap, "at most cap elements should have been pushed to queue of size cap");
1055 if (nthreads>= cap)
1056 REQUIRE_MESSAGE(failed_pushes == nthreads-cap, "nthreads-cap threads should have failed to push an element to queue of size cap");
1057 SimplePopBody my_pop_body(&iq4, nthreads, num_pushed);
1058 utils::NativeParallelFor( nthreads+1, my_pop_body );
1059 REQUIRE_MESSAGE((int)num_popped <= cap, "at most cap elements should have been popped from queue of size cap");
1060 if (nthreads>= cap)
1061 REQUIRE_MESSAGE(failed_pops == nthreads-cap, "nthreads-cap threads should have failed to pop an element from queue of size cap");
1062 else {
1063 int e;
1064 while (iq4.try_pop(e)) ;
1065 }
1066 }
1067 }
1068 }
1069 #endif
1070
1071 template <template <typename...> class ContainerType>
test_member_types()1072 void test_member_types() {
1073 using container_type = ContainerType<int>;
1074 static_assert(std::is_same<typename container_type::allocator_type, oneapi::tbb::cache_aligned_allocator<int>>::value,
1075 "Incorrect default template allocator");
1076
1077 static_assert(std::is_same<typename container_type::value_type, int>::value,
1078 "Incorrect container value_type member type");
1079
1080 static_assert(std::is_signed<typename container_type::difference_type>::value,
1081 "Incorrect container difference_type member type");
1082
1083 using value_type = typename container_type::value_type;
1084 static_assert(std::is_same<typename container_type::reference, value_type&>::value,
1085 "Incorrect container reference member type");
1086 static_assert(std::is_same<typename container_type::const_reference, const value_type&>::value,
1087 "Incorrect container const_reference member type");
1088 using allocator_type = typename container_type::allocator_type;
1089 static_assert(std::is_same<typename container_type::pointer, typename std::allocator_traits<allocator_type>::pointer>::value,
1090 "Incorrect container pointer member type");
1091 static_assert(std::is_same<typename container_type::const_pointer, typename std::allocator_traits<allocator_type>::const_pointer>::value,
1092 "Incorrect container const_pointer member type");
1093
1094 static_assert(utils::is_forward_iterator<typename container_type::iterator>::value,
1095 "Incorrect container iterator member type");
1096 static_assert(!std::is_const<typename container_type::iterator::value_type>::value,
1097 "Incorrect container iterator member type");
1098 static_assert(utils::is_forward_iterator<typename container_type::const_iterator>::value,
1099 "Incorrect container const_iterator member type");
1100 static_assert(std::is_const<typename container_type::const_iterator::value_type>::value,
1101 "Incorrect container iterator member type");
1102 }
1103
1104 enum push_t { push_op, try_push_op };
1105
1106 template<push_t push_op>
1107 struct pusher {
1108 template<typename CQ, typename VType>
pushpusher1109 static bool push( CQ& queue, VType&& val ) {
1110 queue.push( std::forward<VType>( val ) );
1111 return true;
1112 }
1113 };
1114
1115 template<>
1116 struct pusher< try_push_op> {
1117 template<typename CQ, typename VType>
pushpusher1118 static bool push( CQ& queue, VType&& val ) {
1119 return queue.try_push( std::forward<VType>( val ) );
1120 }
1121 };
1122
1123 enum pop_t { pop_op, try_pop_op };
1124
1125 template<pop_t pop_op>
1126 struct popper {
1127 template<typename CQ, typename VType>
poppopper1128 static bool pop( CQ& queue, VType&& val ) {
1129 if( queue.empty() ) return false;
1130 queue.pop( std::forward<VType>( val ) );
1131 return true;
1132 }
1133 };
1134
1135 template<>
1136 struct popper<try_pop_op> {
1137 template<typename CQ, typename VType>
poppopper1138 static bool pop( CQ& queue, VType&& val ) {
1139 return queue.try_pop( std::forward<VType>( val ) );
1140 }
1141 };
1142
1143 struct MoveOperationTracker {
1144 static std::size_t copy_constructor_called_times;
1145 static std::size_t move_constructor_called_times;
1146 static std::size_t copy_assignment_called_times;
1147 static std::size_t move_assignment_called_times;
1148
MoveOperationTrackerMoveOperationTracker1149 MoveOperationTracker() {}
MoveOperationTrackerMoveOperationTracker1150 MoveOperationTracker(const MoveOperationTracker&) {
1151 ++copy_constructor_called_times;
1152 }
MoveOperationTrackerMoveOperationTracker1153 MoveOperationTracker(MoveOperationTracker&&) {
1154 ++move_constructor_called_times;
1155 }
operator =MoveOperationTracker1156 MoveOperationTracker& operator=(MoveOperationTracker const&) {
1157 ++copy_assignment_called_times;
1158 return *this;
1159 }
operator =MoveOperationTracker1160 MoveOperationTracker& operator=(MoveOperationTracker&&) {
1161 ++move_assignment_called_times;
1162 return *this;
1163 }
1164 };
1165
1166 size_t MoveOperationTracker::copy_constructor_called_times = 0;
1167 size_t MoveOperationTracker::move_constructor_called_times = 0;
1168 size_t MoveOperationTracker::copy_assignment_called_times = 0;
1169 size_t MoveOperationTracker::move_assignment_called_times = 0;
1170
1171 template <class CQ, push_t push_op, pop_t pop_op>
TestMoveSupport()1172 void TestMoveSupport() {
1173 std::size_t &mcct = MoveOperationTracker::move_constructor_called_times;
1174 std::size_t &ccct = MoveOperationTracker::copy_constructor_called_times;
1175 std::size_t &cact = MoveOperationTracker::copy_assignment_called_times;
1176 std::size_t &mact = MoveOperationTracker::move_assignment_called_times;
1177 mcct = ccct = cact = mact = 0;
1178
1179 CQ q;
1180
1181 REQUIRE_MESSAGE(mcct == 0, "Value must be zero-initialized");
1182 REQUIRE_MESSAGE(ccct == 0, "Value must be zero-initialized");
1183 CHECK(pusher<push_op>::push( q, MoveOperationTracker() ));
1184 REQUIRE_MESSAGE(mcct == 1, "Not working push(T&&) or try_push(T&&)?");
1185 REQUIRE_MESSAGE(ccct == 0, "Copying of arg occurred during push(T&&) or try_push(T&&)");
1186
1187 MoveOperationTracker ob;
1188 CHECK(pusher<push_op>::push( q, std::move(ob) ));
1189 REQUIRE_MESSAGE(mcct == 2, "Not working push(T&&) or try_push(T&&)?");
1190 REQUIRE_MESSAGE(ccct == 0, "Copying of arg occurred during push(T&&) or try_push(T&&)");
1191
1192 REQUIRE_MESSAGE(cact == 0, "Copy assignment called during push(T&&) or try_push(T&&)");
1193 REQUIRE_MESSAGE(mact == 0, "Move assignment called during push(T&&) or try_push(T&&)");
1194
1195 bool result = popper<pop_op>::pop( q, ob );
1196 CHECK(result);
1197 REQUIRE_MESSAGE(cact == 0, "Copy assignment called during try_pop(T&&)");
1198 REQUIRE_MESSAGE(mact == 1, "Move assignment was not called during try_pop(T&&)");
1199 }
1200
TestMoveSupportInPushPop()1201 void TestMoveSupportInPushPop() {
1202 TestMoveSupport<oneapi::tbb::concurrent_queue<MoveOperationTracker>, push_op, try_pop_op>();
1203 TestMoveSupport<oneapi::tbb::concurrent_bounded_queue<MoveOperationTracker>, push_op, pop_op>();
1204 TestMoveSupport<oneapi::tbb::concurrent_bounded_queue<MoveOperationTracker>, try_push_op, try_pop_op>();
1205 }
1206
1207 template<class T>
1208 class allocator: public oneapi::tbb::cache_aligned_allocator<T> {
1209 public:
1210 state_type state = LIVE;
1211 std::size_t m_unique_id;
1212
allocator()1213 allocator() : m_unique_id( 0 ) {}
allocator(size_t unique_id)1214 allocator(size_t unique_id) { m_unique_id = unique_id; }
1215
~allocator()1216 ~allocator() {
1217 REQUIRE_MESSAGE(state == LIVE, "Destroyed allocator has been used.");
1218 state = DEAD;
1219 }
1220
1221 template<typename U>
allocator(const allocator<U> & a)1222 allocator(const allocator<U>& a) noexcept {
1223 REQUIRE_MESSAGE(a.state == LIVE, "Destroyed allocator has been used.");
1224 m_unique_id = a.m_unique_id;
1225 }
1226
1227 template<typename U>
1228 struct rebind { typedef allocator<U> other; };
1229
operator ==(const allocator & lhs,const allocator & rhs)1230 friend bool operator==(const allocator& lhs, const allocator& rhs) {
1231 REQUIRE_MESSAGE(lhs.state == LIVE, "Destroyed allocator has been used.");
1232 REQUIRE_MESSAGE(rhs.state == LIVE, "Destroyed allocator has been used.");
1233 return lhs.m_unique_id == rhs.m_unique_id;
1234 }
1235 };
1236
1237 template <typename Queue>
AssertEquality(Queue & q,const std::vector<typename Queue::value_type> & vec)1238 void AssertEquality(Queue &q, const std::vector<typename Queue::value_type> &vec) {
1239 CHECK(q.size() == typename Queue::size_type(vec.size()));
1240 CHECK(std::equal(q.unsafe_begin(), q.unsafe_end(), vec.begin()));
1241 }
1242
1243 template <typename Queue>
AssertEmptiness(Queue & q)1244 void AssertEmptiness(Queue &q) {
1245 CHECK(q.empty());
1246 CHECK(!q.size());
1247 typename Queue::value_type elem;
1248 CHECK(!q.try_pop(elem));
1249 }
1250
1251 template <push_t push_op, typename Queue>
FillTest(Queue & q,const std::vector<typename Queue::value_type> & vec)1252 void FillTest(Queue &q, const std::vector<typename Queue::value_type> &vec) {
1253 for (typename std::vector<typename Queue::value_type>::const_iterator it = vec.begin(); it != vec.end(); ++it)
1254 CHECK(pusher<push_op>::push(q, *it));
1255 AssertEquality(q, vec);
1256 }
1257
1258 template <pop_t pop_op, typename Queue>
EmptyTest(Queue & q,const std::vector<typename Queue::value_type> & vec)1259 void EmptyTest(Queue &q, const std::vector<typename Queue::value_type> &vec) {
1260 typedef typename Queue::value_type value_type;
1261
1262 value_type elem;
1263 typename std::vector<value_type>::const_iterator it = vec.begin();
1264 while (popper<pop_op>::pop(q, elem)) {
1265 CHECK(elem == *it);
1266 ++it;
1267 }
1268 CHECK(it == vec.end());
1269 AssertEmptiness(q);
1270 }
1271
1272 template <typename T, typename A>
bounded_queue_specific_test(oneapi::tbb::concurrent_queue<T,A> &,const std::vector<T> &)1273 void bounded_queue_specific_test(oneapi::tbb::concurrent_queue<T, A> &, const std::vector<T> &) { /* do nothing */ }
1274
1275 template <typename T, typename A>
bounded_queue_specific_test(oneapi::tbb::concurrent_bounded_queue<T,A> & q,const std::vector<T> & vec)1276 void bounded_queue_specific_test(oneapi::tbb::concurrent_bounded_queue<T, A> &q, const std::vector<T> &vec) {
1277 typedef typename oneapi::tbb::concurrent_bounded_queue<T, A>::size_type size_type;
1278
1279 FillTest<try_push_op>(q, vec);
1280 oneapi::tbb::concurrent_bounded_queue<T, A> q2 = q;
1281 EmptyTest<pop_op>(q, vec);
1282
1283 // capacity
1284 q2.set_capacity(size_type(vec.size()));
1285 CHECK(q2.capacity() == size_type(vec.size()));
1286 CHECK(q2.size() == size_type(vec.size()));
1287 CHECK(!q2.try_push(vec[0]));
1288 q.abort();
1289 }
1290
1291 // Checks operability of the queue the data was moved from
1292 template<typename T, typename CQ>
TestQueueOperabilityAfterDataMove(CQ & queue)1293 void TestQueueOperabilityAfterDataMove( CQ& queue ) {
1294 const std::size_t size = 10;
1295 std::vector<T> v(size);
1296 for( std::size_t i = 0; i < size; ++i ) v[i] = T( i * i + i );
1297
1298 FillTest<push_op>(queue, v);
1299 EmptyTest<try_pop_op>(queue, v);
1300 bounded_queue_specific_test(queue, v);
1301 }
1302
1303 template<class CQ, class T>
TestMoveConstructors()1304 void TestMoveConstructors() {
1305 T::construction_num = T::destruction_num = 0;
1306 CQ src_queue( allocator<T>(0) );
1307 const std::size_t size = 10;
1308 for( std::size_t i = 0; i < size; ++i )
1309 src_queue.push( T(i + (i ^ size)) );
1310 CHECK(T::construction_num == 2 * size);
1311 CHECK(T::destruction_num == size);
1312
1313 const T* locations[size];
1314 typename CQ::const_iterator qit = src_queue.unsafe_begin();
1315 for( std::size_t i = 0; i < size; ++i, ++qit )
1316 locations[i] = &(*qit);
1317
1318 // Ensuring allocation operation takes place during move when allocators are different
1319 T::construction_num = T::destruction_num = 0;
1320 CQ dst_queue( std::move(src_queue), allocator<T>(1) );
1321 CHECK(T::construction_num == size);
1322 CHECK(T::destruction_num == size);
1323
1324 TestQueueOperabilityAfterDataMove<T>( src_queue );
1325
1326 qit = dst_queue.unsafe_begin();
1327 for( std::size_t i = 0; i < size; ++i, ++qit ) {
1328 REQUIRE_MESSAGE(locations[i] != &(*qit), "an item should have been copied but was not" );
1329 locations[i] = &(*qit);
1330 }
1331
1332 T::construction_num = T::destruction_num = 0;
1333 // Ensuring there is no allocation operation during move with equal allocators
1334 CQ dst_queue2( std::move(dst_queue), allocator<T>(1) );
1335 CHECK(T::construction_num == 0);
1336 CHECK(T::destruction_num == 0);
1337
1338 TestQueueOperabilityAfterDataMove<T>( dst_queue );
1339
1340 qit = dst_queue2.unsafe_begin();
1341 for( std::size_t i = 0; i < size; ++i, ++qit ) {
1342 REQUIRE_MESSAGE(locations[i] == &(*qit), "an item should have been moved but was not" );
1343 }
1344
1345 for( std::size_t i = 0; i < size; ++i) {
1346 T test(i + (i ^ size));
1347 T popped;
1348 bool pop_result = dst_queue2.try_pop( popped );
1349 CHECK(pop_result);
1350 CHECK(test == popped);
1351 }
1352 CHECK(dst_queue2.empty());
1353 CHECK(dst_queue2.size() == 0);
1354 }
1355
TestMoveConstruction()1356 void TestMoveConstruction() {
1357 TestMoveConstructors<ConcQWithSizeWrapper<Bar, allocator<Bar>>, Bar>();
1358 TestMoveConstructors<oneapi::tbb::concurrent_bounded_queue<Bar, allocator<Bar>>, Bar>();
1359 }
1360
1361 class NonTrivialConstructorType {
1362 public:
NonTrivialConstructorType(int a=0)1363 NonTrivialConstructorType( int a = 0 ) : m_a( a ), m_str( "" ) {}
NonTrivialConstructorType(const std::string & str)1364 NonTrivialConstructorType( const std::string& str ) : m_a( 0 ), m_str( str ) {}
NonTrivialConstructorType(int a,const std::string & str)1365 NonTrivialConstructorType( int a, const std::string& str ) : m_a( a ), m_str( str ) {}
get_a() const1366 int get_a() const { return m_a; }
get_str() const1367 std::string get_str() const { return m_str; }
1368 private:
1369 int m_a;
1370 std::string m_str;
1371 };
1372
1373 enum emplace_t { emplace_op, try_emplace_op };
1374
1375 template<emplace_t emplace_op>
1376 struct emplacer {
1377 template<typename CQ, typename... Args>
emplaceemplacer1378 static void emplace( CQ& queue, Args&&... val ) { queue.emplace( std::forward<Args>( val )... ); }
1379 };
1380
1381 template<>
1382 struct emplacer <try_emplace_op> {
1383 template<typename CQ, typename... Args>
emplaceemplacer1384 static void emplace( CQ& queue, Args&&... val ) {
1385 bool result = queue.try_emplace( std::forward<Args>( val )... );
1386 REQUIRE_MESSAGE(result, "try_emplace error\n");
1387 }
1388 };
1389
1390 template<typename CQ, emplace_t emplace_op>
TestEmplaceInQueue()1391 void TestEmplaceInQueue() {
1392 CQ cq;
1393 std::string test_str = "I'm being emplaced!";
1394 {
1395 emplacer<emplace_op>::emplace( cq, 5 );
1396 CHECK(cq.size() == 1);
1397 NonTrivialConstructorType popped( -1 );
1398 bool result = cq.try_pop( popped );
1399 CHECK(result);
1400 CHECK(popped.get_a() == 5);
1401 CHECK(popped.get_str() == std::string( "" ));
1402 }
1403
1404 CHECK(cq.empty());
1405
1406 {
1407 NonTrivialConstructorType popped( -1 );
1408 emplacer<emplace_op>::emplace( cq, std::string(test_str) );
1409 bool result = cq.try_pop( popped );
1410 CHECK(result);
1411 CHECK(popped.get_a() == 0);
1412 CHECK(popped.get_str() == test_str);
1413 }
1414
1415 CHECK(cq.empty());
1416
1417 {
1418 NonTrivialConstructorType popped( -1, "" );
1419 emplacer<emplace_op>::emplace( cq, 5, std::string(test_str) );
1420 bool result = cq.try_pop( popped );
1421 CHECK(result);
1422 CHECK(popped.get_a() == 5);
1423 CHECK(popped.get_str() == test_str);
1424 }
1425 }
TestEmplace()1426 void TestEmplace() {
1427 TestEmplaceInQueue<ConcQWithSizeWrapper<NonTrivialConstructorType>, emplace_op>();
1428 TestEmplaceInQueue<oneapi::tbb::concurrent_bounded_queue<NonTrivialConstructorType>, emplace_op>();
1429 TestEmplaceInQueue<oneapi::tbb::concurrent_bounded_queue<NonTrivialConstructorType>, try_emplace_op>();
1430 }
1431
1432 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1433 template <template <typename...> typename TQueue>
TestDeductionGuides()1434 void TestDeductionGuides() {
1435 using ComplexType = const std::string*;
1436 std::vector<ComplexType> v;
1437
1438 // check TQueue(InputIterator, InputIterator)
1439 TQueue q1(v.begin(), v.end());
1440 static_assert(std::is_same<decltype(q1), TQueue<ComplexType>>::value);
1441
1442 // check TQueue(InputIterator, InputIterator, Allocator)
1443 TQueue q2(v.begin(), v.end(), std::allocator<ComplexType>());
1444 static_assert(std::is_same<decltype(q2), TQueue<ComplexType, std::allocator<ComplexType>>>::value);
1445
1446 // check TQueue(TQueue &)
1447 TQueue q3(q1);
1448 static_assert(std::is_same<decltype(q3), decltype(q1)>::value);
1449
1450 // check TQueue(TQueue &, Allocator)
1451 TQueue q4(q2, std::allocator<ComplexType>());
1452 static_assert(std::is_same<decltype(q4), decltype(q2)>::value);
1453
1454 // check TQueue(TQueue &&)
1455 TQueue q5(std::move(q1));
1456 static_assert(std::is_same<decltype(q5), decltype(q1)>::value);
1457
1458 // check TQueue(TQueue &&, Allocator)
1459 TQueue q6(std::move(q4), std::allocator<ComplexType>());
1460 static_assert(std::is_same<decltype(q6), decltype(q4)>::value);
1461 }
1462 #endif
1463
1464 template <typename Iterator, typename QueueType>
TestQueueIteratorComparisonsBasic(QueueType & q)1465 void TestQueueIteratorComparisonsBasic( QueueType& q ) {
1466 REQUIRE_MESSAGE(!q.empty(), "Incorrect test setup");
1467 using namespace comparisons_testing;
1468 Iterator it1, it2;
1469 testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1470 it1 = q.unsafe_begin();
1471 testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1472 it2 = q.unsafe_begin();
1473 testEqualityComparisons</*ExpectEqual = */true>(it1, it2);
1474 it2 = q.unsafe_end();
1475 testEqualityComparisons</*ExpectEqual = */false>(it1, it2);
1476 }
1477
1478 template <typename QueueType>
TestQueueIteratorComparisons()1479 void TestQueueIteratorComparisons() {
1480 QueueType q;
1481 q.emplace(1);
1482 q.emplace(2);
1483 q.emplace(3);
1484 TestQueueIteratorComparisonsBasic<typename QueueType::iterator>(q);
1485 const QueueType& cq = q;
1486 TestQueueIteratorComparisonsBasic<typename QueueType::const_iterator>(cq);
1487 }
1488
1489 //! Test constructors
1490 //! \brief \ref interface \ref requirement
1491 TEST_CASE("testing constructors") {
1492 TestQueueConstructors();
1493 }
1494
1495 //! Test work with empty queue
1496 //! \brief \ref interface \ref requirement
1497 TEST_CASE("testing work with empty queue") {
1498 TestEmptiness();
1499 }
1500
1501 //! Test set capacity operation
1502 //! \brief \ref interface \ref requirement
1503 TEST_CASE("testing set capacity operation") {
1504 TestFullness();
1505 }
1506
1507 //! Test clean operation
1508 //! \brief \ref interface \ref requirement
1509 TEST_CASE("testing clean operation") {
1510 TestClearWorks();
1511 }
1512
1513 //! Test move constructors
1514 //! \brief \ref interface \ref requirement
1515 TEST_CASE("testing move constructor") {
1516 TestMoveConstruction();
1517 }
1518
1519 //! Test move support in push and pop
1520 //! \brief \ref requirement
1521 TEST_CASE("testing move support in push and pop") {
1522 TestMoveSupportInPushPop();
1523 }
1524
1525 //! Test emplace operation
1526 //! \brief \ref interface \ref requirement
1527 TEST_CASE("testing emplace") {
1528 TestEmplace();
1529 }
1530
1531 //! Test concurrent_queues member types
1532 //! \brief \ref interface \ref requirement
1533 TEST_CASE("testing concurrent_queues member types"){
1534 test_member_types<oneapi::tbb::concurrent_queue>();
1535 test_member_types<oneapi::tbb::concurrent_bounded_queue>();
1536
1537 // Test size_type
1538 static_assert(std::is_unsigned<typename oneapi::tbb::concurrent_queue<int>::size_type>::value,
1539 "Incorrect oneapi::tbb::concurrent_queue::size_type member type");
1540 static_assert(std::is_signed<typename oneapi::tbb::concurrent_bounded_queue<int>::size_type>::value,
1541 "Incorrect oneapi::tbb::concurrent_bounded_queue::size_type member type");
1542 }
1543
1544 //! Test iterators
1545 //! \brief \ref interface \ref requirement
1546 TEST_CASE("testing iterators") {
1547 TestQueueIteratorWorks();
1548 }
1549
1550 //! Test concurrent operations support
1551 //! \brief \ref requirement
1552 TEST_CASE("testing concurrent operations support") {
1553 TestConcurrentPushPop();
1554 }
1555
1556 #if TBB_USE_EXCEPTIONS
1557 //! Test exception safety
1558 //! \brief \ref requirement
1559 TEST_CASE("testing exception safety") {
1560 TestExceptions();
1561 }
1562
1563 //! Test abort operation
1564 //! \brief \ref interface \ref requirement
1565 TEST_CASE("testing abort operation") {
1566 TestAbort();
1567 }
1568 #endif
1569
1570 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
1571 //! Test deduction guides
1572 //! \brief \ref interface
1573 TEST_CASE("testing deduction guides") {
1574 TestDeductionGuides<oneapi::tbb::concurrent_queue>();
1575 TestDeductionGuides<oneapi::tbb::concurrent_bounded_queue>();
1576 }
1577 #endif
1578
1579 //! \brief \ref interface \ref requirement
1580 TEST_CASE("concurrent_queue iterator comparisons") {
1581 TestQueueIteratorComparisons<oneapi::tbb::concurrent_queue<int>>();
1582 }
1583
1584 //! \brief \ref interface \ref requirement
1585 TEST_CASE("concurrent_bounded_queue iterator comparisons") {
1586 TestQueueIteratorComparisons<oneapi::tbb::concurrent_bounded_queue<int>>();
1587 }
1588
1589 class MinimalisticObject {
1590 public:
1591 struct flag {};
1592
1593 MinimalisticObject() = delete;
MinimalisticObject(flag)1594 MinimalisticObject(flag) : underlying_obj(default_obj) {}
1595
1596 MinimalisticObject(const MinimalisticObject&) = delete;
1597 MinimalisticObject& operator=(const MinimalisticObject&) = delete;
1598
get_obj() const1599 std::size_t get_obj() const { return underlying_obj; }
get_default_obj() const1600 std::size_t get_default_obj() const { return default_obj; }
1601
1602 protected:
1603 static constexpr std::size_t default_obj = 42;
1604 std::size_t underlying_obj;
1605 friend struct MoveAssignableMinimalisticObject;
1606 };
1607
1608 struct MoveAssignableMinimalisticObject : MinimalisticObject {
1609 public:
1610 using MinimalisticObject::MinimalisticObject;
1611
operator =MoveAssignableMinimalisticObject1612 MoveAssignableMinimalisticObject& operator=(MoveAssignableMinimalisticObject&& other) {
1613 if (this != &other) {
1614 underlying_obj = other.underlying_obj;
1615 other.underlying_obj = 0;
1616 }
1617 return *this;
1618 }
1619 };
1620
1621 template <typename Container>
test_basics(Container & container,std::size_t desired_size)1622 void test_basics(Container& container, std::size_t desired_size) {
1623 CHECK(!container.empty());
1624
1625 std::size_t counter = 0;
1626 for (auto it = container.unsafe_begin(); it != container.unsafe_end(); ++it) {
1627 CHECK(it->get_obj() == it->get_default_obj());
1628 ++counter;
1629 }
1630 CHECK(counter == desired_size);
1631
1632 container.clear();
1633 CHECK(container.empty());
1634 }
1635
1636 template <template <class...> class Container>
test_with_minimalistic_objects()1637 void test_with_minimalistic_objects() {
1638 // Test with MinimalisticObject and no pop operations
1639 const std::size_t elements_count = 100;
1640 {
1641 Container<MinimalisticObject> default_container;
1642
1643 for (std::size_t i = 0; i < elements_count; ++i) {
1644 default_container.emplace(MinimalisticObject::flag{});
1645 }
1646 test_basics(default_container, elements_count);
1647 }
1648 // Test with MoveAssignableMinimalisticObject with pop operation
1649 {
1650 Container<MoveAssignableMinimalisticObject> default_container;
1651
1652 for (std::size_t i = 0; i < elements_count; ++i) {
1653 default_container.emplace(MinimalisticObject::flag{});
1654 }
1655 test_basics(default_container, elements_count);
1656
1657 // Refill again
1658 for (std::size_t i = 0; i < elements_count; ++i) {
1659 default_container.emplace(MinimalisticObject::flag{});
1660 }
1661
1662 MoveAssignableMinimalisticObject result(MinimalisticObject::flag{});
1663
1664 std::size_t element_counter = 0;
1665 while (!default_container.empty()) {
1666 CHECK(default_container.try_pop(result));
1667 ++element_counter;
1668 }
1669
1670 CHECK(element_counter == elements_count);
1671 CHECK(default_container.empty());
1672 }
1673 }
1674
1675 //! \brief \ref requirement
1676 TEST_CASE("Test with minimalistic object type") {
1677 test_with_minimalistic_objects<oneapi::tbb::concurrent_queue>();
1678 test_with_minimalistic_objects<oneapi::tbb::concurrent_bounded_queue>();
1679 }
1680
1681 //TODO: Once support for std::allocator_traits::propagate_on_container_* is implemented,
1682 // most of the 4 test cases below can be replaced with move_support_tests::test_*.
1683
1684 template<typename CQ>
test_queue_helper()1685 void test_queue_helper() {
1686 int size = 5;
1687 typename CQ::value_type vec_1(size, 0), vec_2(size, 0), vec_3(size, 0), vec_4(size, 0);
1688 srand(static_cast<unsigned>(time(0)));
1689 generate(vec_1.begin(), vec_1.end(), rand);
1690 generate(vec_2.begin(), vec_2.end(), rand);
1691 generate(vec_3.begin(), vec_3.end(), rand);
1692 generate(vec_4.begin(), vec_4.end(), rand);
1693
1694 CQ q1, q2, q3;
1695 q3 = {vec_4, vec_2, vec_3};
1696 CQ q4({vec_1, vec_2, vec_3});
1697
1698 q1 = q3;
1699 q2 = std::move(q3);
1700 CHECK(q3.empty());
1701
1702 CHECK(q1 != q4);
1703 q1.swap(q4);
1704 CHECK(q2 == q4);
1705
1706 swap(q2, q3);
1707 CHECK(q2.empty());
1708 CHECK(q3 == q4);
1709 }
1710
1711 //! Test assignment (copy/move/initializer_list) and swapping
1712 //! \brief \ref interface \ref requirement
1713 TEST_CASE("testing assignment and swapping") {
1714 test_queue_helper<tbb::concurrent_queue<std::vector<int>>>();
1715 test_queue_helper<tbb::concurrent_bounded_queue<std::vector<int>>>();
1716 }
1717
1718 template <typename QueueType>
TestMoveQueue()1719 void TestMoveQueue() {
1720 using allocator_type = typename QueueType::allocator_type;
1721
1722 QueueType q1, q2;
1723 move_support_tests::Foo obj;
1724 size_t n1(15), n2(7);
1725
1726 allocator_type::init_counters();
1727 for(size_t i =0; i < n1; i++)
1728 q1.push(obj);
1729 size_t q1_items_constructed = allocator_type::items_constructed;
1730 size_t q1_items_allocated = allocator_type::items_allocated;
1731
1732 allocator_type::init_counters();
1733 for(size_t i =0; i < n2; i++)
1734 q2.push(obj);
1735 size_t q2_items_allocated = allocator_type::items_allocated;
1736
1737 allocator_type::init_counters();
1738 q1 = std::move(q2);
1739
1740 CHECK(q1_items_allocated == allocator_type::items_freed);
1741 CHECK(q1_items_constructed == allocator_type::items_destroyed);
1742 CHECK(q2_items_allocated >= allocator_type::items_allocated);
1743 }
1744
1745 //! move assignment test for equal counting allocator
1746 //! \brief \ref interface \ref requirement
1747 TEST_CASE("testing move assignment with equal counting allocators") {
1748 using allocator_type = StaticSharedCountingAllocator<std::allocator<move_support_tests::Foo>>;
1749 TestMoveQueue<tbb::concurrent_queue<move_support_tests::Foo, allocator_type>>();
1750 TestMoveQueue<tbb::concurrent_bounded_queue<move_support_tests::Foo, allocator_type>>();
1751 }
1752
1753 template<class T>
1754 struct stateful_allocator {
1755 typedef T value_type;
1756 stateful_allocator() = default;
1757 int state = 0;
1758 template<class U>
stateful_allocatorstateful_allocator1759 constexpr stateful_allocator(const stateful_allocator<U>& src) noexcept : state(src.state) {}
1760
allocatestateful_allocator1761 T* allocate(std::size_t n) {
1762 return static_cast<T*>(::operator new(n * sizeof(T)));
1763 }
1764
deallocatestateful_allocator1765 void deallocate(T* p, std::size_t) noexcept {
1766 ::operator delete(p);
1767 }
1768 };
1769
1770 template<class T, class U>
operator ==(const stateful_allocator<T> & lhs,const stateful_allocator<U> & rhs)1771 bool operator==(const stateful_allocator<T>& lhs, const stateful_allocator<U>& rhs) { return lhs.state == rhs.state; }
1772
1773 template<class T, class U>
operator !=(const stateful_allocator<T> & lhs,const stateful_allocator<U> & rhs)1774 bool operator!=(const stateful_allocator<T>& lhs, const stateful_allocator<U>& rhs) { return lhs.state != rhs.state; }
1775
1776 template <typename QueueType>
TestMoveQueueUnequal()1777 void TestMoveQueueUnequal() {
1778 using allocator_type = typename QueueType::allocator_type;
1779 allocator_type alloc1, alloc2;
1780 alloc1.state = 0;
1781 alloc2.state = 1;
1782
1783 QueueType q1(alloc1), q2(alloc2);
1784 move_support_tests::Foo obj;
1785 size_t n1(15), n2(7);
1786
1787 allocator_type::init_counters();
1788 for(size_t i =0; i < n1; i++)
1789 q1.push(obj);
1790
1791 allocator_type::init_counters();
1792 for(size_t i =0; i < n2; i++)
1793 q2.push(obj);
1794 size_t q2_items_allocated = allocator_type::items_allocated;
1795
1796 allocator_type::init_counters();
1797 q1 = std::move(q2);
1798
1799 REQUIRE_MESSAGE(allocator_type::items_allocated == q2_items_allocated, "More than expected memory allocated?");
1800 REQUIRE_MESSAGE(std::all_of(q1.unsafe_begin(), q1.unsafe_end(), is_state_predicate<move_support_tests::Foo::MoveInitialized>()),
1801 "Container did not move construct some elements");
1802 REQUIRE_MESSAGE(std::all_of(q2.unsafe_begin(), q2.unsafe_end(), is_state_predicate<move_support_tests::Foo::MovedFrom>()),
1803 "Container did not move all the elements");
1804 }
1805
1806 //! move assignment test for unequal counting allocator
1807 //! \brief \ref interface \ref requirement
1808 TEST_CASE("testing move assignment with unequal counting allocators") {
1809 using allocator_type = StaticSharedCountingAllocator<stateful_allocator<move_support_tests::Foo>>;
1810 TestMoveQueueUnequal<tbb::concurrent_queue<move_support_tests::Foo, allocator_type>>();
1811 TestMoveQueueUnequal<tbb::concurrent_bounded_queue<move_support_tests::Foo, allocator_type>>();
1812 }
1813
1814 template<typename Container>
test_check_move_allocator(Container & src,Container & dst,Container & cpy)1815 void test_check_move_allocator(Container& src, Container& dst, Container& cpy) {
1816 REQUIRE_MESSAGE(src.empty(), "Source didn't clear");
1817 REQUIRE_MESSAGE(std::equal(dst.unsafe_begin(), dst.unsafe_end(), cpy.unsafe_begin()), "Elements are not equal");
1818 }
1819
test_move_assignment_test_equal()1820 void test_move_assignment_test_equal() {
1821 int n = 5;
1822 std::vector<int> vect1(n, 10), vect2(n,20), vect3(n, 30);
1823
1824 tbb::concurrent_queue<std::vector<int>> src({vect1, vect2, vect3});
1825 tbb::concurrent_queue<std::vector<int>> dst(src.get_allocator());
1826 tbb::concurrent_queue<std::vector<int>> cpy(src.get_allocator());
1827 REQUIRE_MESSAGE(src.get_allocator() == dst.get_allocator(), "Incorrect test setup: allocators should be equal");
1828 cpy = src;
1829 dst = std::move(src);
1830
1831 tbb::concurrent_bounded_queue<std::vector<int>> src_bnd({vect1, vect2, vect3});
1832 tbb::concurrent_bounded_queue<std::vector<int>> dst_bnd(src_bnd.get_allocator());
1833 tbb::concurrent_bounded_queue<std::vector<int>> cpy_bnd(src_bnd.get_allocator());
1834 REQUIRE_MESSAGE(src_bnd.get_allocator() == dst_bnd.get_allocator(), "Incorrect test setup: allocators should be equal");
1835 cpy_bnd = src_bnd;
1836 dst_bnd = std::move(src_bnd);
1837
1838 test_check_move_allocator<tbb::concurrent_queue<std::vector<int>>>(src, dst, cpy);
1839 REQUIRE_MESSAGE(cpy.unsafe_size() == dst.unsafe_size(), "Queues are not equal");
1840
1841 test_check_move_allocator<tbb::concurrent_bounded_queue<std::vector<int>>>(src_bnd, dst_bnd, cpy_bnd);
1842 REQUIRE_MESSAGE(cpy_bnd.size() == dst_bnd.size(), "Queues are not equal");
1843 }
1844
test_move_assignment_test_unequal()1845 void test_move_assignment_test_unequal() {
1846 stateful_allocator<int> src_alloc;
1847 src_alloc.state = 0;
1848 std::vector<int, stateful_allocator<int>> v(8, 0, src_alloc);
1849 tbb::concurrent_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> src(src_alloc);
1850
1851 v.push_back(42);
1852 v.push_back(82);
1853 src.push(v);
1854 src.push(v);
1855
1856 stateful_allocator<int> dst_alloc;
1857 dst_alloc.state = 1;
1858 tbb::concurrent_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> dst(dst_alloc);
1859 tbb::concurrent_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> cpy(src_alloc);
1860 REQUIRE_MESSAGE(src.get_allocator() != dst.get_allocator(), "Incorrect test setup: allocators should be unequal");
1861 cpy = src;
1862 dst = std::move(src);
1863
1864 tbb::concurrent_bounded_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> src_bnd(src_alloc);
1865 tbb::concurrent_bounded_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> dst_bnd(dst_alloc);
1866 tbb::concurrent_bounded_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>> cpy_bnd(src_alloc);
1867 REQUIRE_MESSAGE(src_bnd.get_allocator() != dst_bnd.get_allocator(), "Incorrect test setup: allocators should be unequal");
1868 src_bnd.push(v);
1869 src_bnd.push(v);
1870 cpy_bnd = src_bnd;
1871 dst_bnd = std::move(src_bnd);
1872
1873 test_check_move_allocator<tbb::concurrent_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>>>(src, dst, cpy);
1874 REQUIRE_MESSAGE(dst.unsafe_size() == cpy.unsafe_size(), "Queues are not equal");
1875
1876 test_check_move_allocator<tbb::concurrent_bounded_queue<std::vector<int, stateful_allocator<int>>, stateful_allocator<int>>>(src_bnd, dst_bnd, cpy_bnd);
1877 REQUIRE_MESSAGE(dst_bnd.size() == cpy_bnd.size(), "Queues are not equal");
1878 }
1879
1880 //! move assignment test for equal and unequal allocator
1881 //! \brief \ref interface \ref requirement
1882 TEST_CASE("testing move assignment with equal and unequal allocators") {
1883 test_move_assignment_test_equal();
1884 test_move_assignment_test_unequal();
1885 }
1886