xref: /oneTBB/test/tbb/test_indexer_node.cpp (revision d86ed7fb)
1 /*
2     Copyright (c) 2005-2020 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 #define MAX_TUPLE_TEST_SIZE 10
18 #include "common/config.h"
19 
20 #include "tbb/flow_graph.h"
21 
22 #include "common/test.h"
23 #include "common/utils.h"
24 #include "common/utils_assert.h"
25 #include "common/test_follows_and_precedes_api.h"
26 
27 
28 //! \file test_indexer_node.cpp
29 //! \brief Test for [flow_graph.indexer_node] specification
30 
31 
32 #if defined(_MSC_VER) && _MSC_VER < 1600
33     #pragma warning (disable : 4503) //disabling the "decorated name length exceeded" warning for VS2008 and earlier
34 #endif
35 
36 const int Count = 150;
37 const int MaxPorts = 10;
38 const int MaxNInputs = 5; // max # of input_nodes to register for each indexer_node input in parallel test
39 bool outputCheck[MaxPorts][Count];  // for checking output
40 
41 void
42 check_outputCheck( int nUsed, int maxCnt) {
43     for(int i=0; i < nUsed; ++i) {
44         for( int j = 0; j < maxCnt; ++j) {
45             CHECK_MESSAGE(outputCheck[i][j], "");
46         }
47     }
48 }
49 
50 void
51 reset_outputCheck( int nUsed, int maxCnt) {
52     for(int i=0; i < nUsed; ++i) {
53         for( int j = 0; j < maxCnt; ++j) {
54             outputCheck[i][j] = false;
55         }
56     }
57 }
58 
59 class test_class {
60     public:
61         test_class() { my_val = 0; }
62         test_class(int i) { my_val = i; }
63         operator int() { return my_val; }
64     private:
65         int my_val;
66 };
67 
68 template<typename T>
69 class name_of {
70 public:
71     static const char* name() { return  "Unknown"; }
72 };
73 template<>
74 class name_of<int> {
75 public:
76     static const char* name() { return  "int"; }
77 };
78 template<>
79 class name_of<float> {
80 public:
81     static const char* name() { return  "float"; }
82 };
83 template<>
84 class name_of<double> {
85 public:
86     static const char* name() { return  "double"; }
87 };
88 template<>
89 class name_of<long> {
90 public:
91     static const char* name() { return  "long"; }
92 };
93 template<>
94 class name_of<short> {
95 public:
96     static const char* name() { return  "short"; }
97 };
98 template<>
99 class name_of<test_class> {
100 public:
101     static const char* name() { return  "test_class"; }
102 };
103 
104 // TT must be arithmetic, and shouldn't wrap around for reasonable sizes of Count (which is now 150, and maxPorts is 10,
105 // so the max number generated right now is 1500 or so.)  Input will generate a series of TT with value
106 // (init_val + (i-1)*addend) * my_mult, where i is the i-th invocation of the body.  We are attaching addend
107 // input nodes to a indexer_port, and each will generate part of the numerical series the port is expecting
108 // to receive.  If there is only one input node, the series order will be maintained; if more than one,
109 // this is not guaranteed.
110 // The manual specifies bodies can be assigned, so we can't hide the operator=.
111 template<typename TT>
112 class my_input_body {
113     TT my_mult;
114     int my_count;
115     int addend;
116 public:
117     my_input_body(TT multiplier, int init_val, int addto) : my_mult(multiplier), my_count(init_val), addend(addto) { }
118     TT operator()( tbb::flow_control& fc) {
119         int lc = my_count;
120         TT ret = my_mult * (TT)my_count;
121         my_count += addend;
122         if ( lc < Count){
123             return ret;
124         }else{
125             fc.stop();
126             return TT();
127         }
128     }
129 };
130 
131 // allocator for indexer_node.
132 
133 template<typename IType>
134 class makeIndexer {
135 public:
136     static IType *create() {
137         IType *temp = new IType();
138         return temp;
139     }
140     static void destroy(IType *p) { delete p; }
141 };
142 
143 template<int ELEM, typename INT>
144 struct getval_helper {
145 
146     typedef typename INT::output_type OT;
147     typedef typename std::tuple_element<ELEM-1, typename INT::tuple_types>::type stored_type;
148 
149     static int get_integer_val(OT const &o) {
150         stored_type res = tbb::flow::cast_to<stored_type>(o);
151         return (int)res;
152     }
153 };
154 
155 // holder for input_node pointers for eventual deletion
156 
157 static void* all_input_nodes[MaxPorts][MaxNInputs];
158 
159 template<int ELEM, typename INT>
160 class input_node_helper {
161 public:
162     typedef INT indexer_node_type;
163     typedef typename indexer_node_type::output_type TT;
164     typedef typename std::tuple_element<ELEM-1,typename INT::tuple_types>::type IT;
165     typedef typename tbb::flow::input_node<IT> my_input_node_type;
166     static void print_remark() {
167         input_node_helper<ELEM-1,INT>::print_remark();
168         INFO(", " << name_of<IT>::name());
169     }
170     static void add_input_nodes(indexer_node_type &my_indexer, tbb::flow::graph &g, int nInputs) {
171         for(int i=0; i < nInputs; ++i) {
172             my_input_node_type *new_node = new my_input_node_type(g, my_input_body<IT>((IT)(ELEM+1), i, nInputs));
173             tbb::flow::make_edge(*new_node, tbb::flow::input_port<ELEM-1>(my_indexer));
174 
175             all_input_nodes[ELEM-1][i] = (void *)new_node;
176             new_node->activate();
177         }
178 
179         // add the next input_node
180         input_node_helper<ELEM-1, INT>::add_input_nodes(my_indexer, g, nInputs);
181     }
182     static void check_value(TT &v) {
183         if(v.tag() == ELEM-1) {
184             int ival = getval_helper<ELEM,INT>::get_integer_val(v);
185             CHECK_MESSAGE(!(ival%(ELEM+1)), "");
186             ival /= (ELEM+1);
187             CHECK_MESSAGE(!outputCheck[ELEM-1][ival], "");
188             outputCheck[ELEM-1][ival] = true;
189         }
190         else {
191             input_node_helper<ELEM-1,INT>::check_value(v);
192         }
193     }
194 
195     static void remove_input_nodes(indexer_node_type& my_indexer, int nInputs) {
196         for(int i=0; i< nInputs; ++i) {
197             my_input_node_type *dp = reinterpret_cast<my_input_node_type *>(all_input_nodes[ELEM-1][i]);
198             tbb::flow::remove_edge(*dp, tbb::flow::input_port<ELEM-1>(my_indexer));
199             delete dp;
200         }
201         input_node_helper<ELEM-1, INT>::remove_input_nodes(my_indexer, nInputs);
202     }
203 };
204 
205 template<typename INT>
206 class input_node_helper<1, INT> {
207     typedef INT indexer_node_type;
208     typedef typename indexer_node_type::output_type TT;
209 
210     typedef typename std::tuple_element<0, typename INT::tuple_types>::type IT;
211     typedef typename tbb::flow::input_node<IT> my_input_node_type;
212 public:
213     static void print_remark() {
214         INFO("Parallel test of indexer_node< " << name_of<IT>::name());
215     }
216     static void add_input_nodes(indexer_node_type &my_indexer, tbb::flow::graph &g, int nInputs) {
217         for(int i=0; i < nInputs; ++i) {
218             my_input_node_type *new_node = new my_input_node_type(g, my_input_body<IT>((IT)2, i, nInputs));
219             tbb::flow::make_edge(*new_node, tbb::flow::input_port<0>(my_indexer));
220             all_input_nodes[0][i] = (void *)new_node;
221             new_node->activate();
222         }
223     }
224     static void check_value(TT &v) {
225         int ival = getval_helper<1,INT>::get_integer_val(v);
226         CHECK_MESSAGE(!(ival%2), "");
227         ival /= 2;
228         CHECK_MESSAGE(!outputCheck[0][ival], "");
229         outputCheck[0][ival] = true;
230     }
231     static void remove_input_nodes(indexer_node_type& my_indexer, int nInputs) {
232         for(int i=0; i < nInputs; ++i) {
233             my_input_node_type *dp = reinterpret_cast<my_input_node_type *>(all_input_nodes[0][i]);
234             tbb::flow::remove_edge(*dp, tbb::flow::input_port<0>(my_indexer));
235             delete dp;
236         }
237     }
238 };
239 
240 template<typename IType>
241 class parallel_test {
242 public:
243     typedef typename IType::output_type TType;
244     typedef typename IType::tuple_types union_types;
245     static const int SIZE = std::tuple_size<union_types>::value;
246     static void test() {
247         TType v;
248         input_node_helper<SIZE,IType>::print_remark();
249         INFO(" >\n");
250         for(int i=0; i < MaxPorts; ++i) {
251             for(int j=0; j < MaxNInputs; ++j) {
252                 all_input_nodes[i][j] = NULL;
253             }
254         }
255         for(int nInputs = 1; nInputs <= MaxNInputs; ++nInputs) {
256             tbb::flow::graph g;
257             IType* my_indexer_ptr = new IType(g); //makeIndexer<IType>::create();
258             IType my_indexer = *my_indexer_ptr;
259             tbb::flow::queue_node<TType> outq1(g);
260             tbb::flow::queue_node<TType> outq2(g);
261 
262             tbb::flow::make_edge(my_indexer, outq1);
263             tbb::flow::make_edge(my_indexer, outq2);
264 
265             input_node_helper<SIZE, IType>::add_input_nodes(my_indexer, g, nInputs);
266 
267             g.wait_for_all();
268             makeIndexer<IType>::destroy(my_indexer_ptr);
269 
270             reset_outputCheck(SIZE, Count);
271             for(int i=0; i < Count*SIZE; ++i) {
272                 CHECK_MESSAGE(outq1.try_get(v), "");
273                 input_node_helper<SIZE, IType>::check_value(v);
274             }
275 
276             check_outputCheck(SIZE, Count);
277             reset_outputCheck(SIZE, Count);
278 
279             for(int i=0; i < Count*SIZE; i++) {
280                 CHECK_MESSAGE(outq2.try_get(v), "");;
281                 input_node_helper<SIZE, IType>::check_value(v);
282             }
283             check_outputCheck(SIZE, Count);
284 
285             CHECK_MESSAGE(!outq1.try_get(v), "");
286             CHECK_MESSAGE(!outq2.try_get(v), "");
287 
288             input_node_helper<SIZE, IType>::remove_input_nodes(my_indexer, nInputs);
289             tbb::flow::remove_edge(my_indexer, outq1);
290             tbb::flow::remove_edge(my_indexer, outq2);
291         }
292     }
293 };
294 
295 std::vector<int> last_index_seen;
296 
297 template<int ELEM, typename IType>
298 class serial_queue_helper {
299 public:
300     typedef typename IType::output_type OT;
301     typedef typename IType::tuple_types TT;
302     typedef typename std::tuple_element<ELEM-1,TT>::type IT;
303     static void print_remark() {
304         serial_queue_helper<ELEM-1,IType>::print_remark();
305         INFO("," << name_of<IT>::name());
306     }
307     static void fill_one_queue(int maxVal, IType &my_indexer) {
308         // fill queue to "left" of me
309         serial_queue_helper<ELEM-1,IType>::fill_one_queue(maxVal,my_indexer);
310         for(int i = 0; i < maxVal; ++i) {
311             CHECK_MESSAGE(tbb::flow::input_port<ELEM-1>(my_indexer).try_put((IT)(i*(ELEM+1))), "");
312         }
313     }
314     static void put_one_queue_val(int myVal, IType &my_indexer) {
315         // put this val to my "left".
316         serial_queue_helper<ELEM-1,IType>::put_one_queue_val(myVal, my_indexer);
317         CHECK_MESSAGE(tbb::flow::input_port<ELEM-1>(my_indexer).try_put((IT)(myVal*(ELEM+1))), "");
318     }
319     static void check_queue_value(OT &v) {
320         if(ELEM - 1 == v.tag()) {
321             // this assumes each or node input is queueing.
322             int rval = getval_helper<ELEM,IType>::get_integer_val(v);
323             CHECK_MESSAGE( rval == (last_index_seen[ELEM-1]+1)*(ELEM+1), "");
324             last_index_seen[ELEM-1] = rval / (ELEM+1);
325         }
326         else {
327             serial_queue_helper<ELEM-1,IType>::check_queue_value(v);
328         }
329     }
330 };
331 
332 template<typename IType>
333 class serial_queue_helper<1, IType> {
334 public:
335     typedef typename IType::output_type OT;
336     typedef typename IType::tuple_types TT;
337     typedef typename std::tuple_element<0,TT>::type IT;
338     static void print_remark() {
339         INFO("Serial test of indexer_node< " << name_of<IT>::name());
340     }
341     static void fill_one_queue(int maxVal, IType &my_indexer) {
342         for(int i = 0; i < maxVal; ++i) {
343             CHECK_MESSAGE(tbb::flow::input_port<0>(my_indexer).try_put((IT)(i*2)), "");
344         }
345     }
346     static void put_one_queue_val(int myVal, IType &my_indexer) {
347         CHECK_MESSAGE(tbb::flow::input_port<0>(my_indexer).try_put((IT)(myVal*2)), "");
348     }
349     static void check_queue_value(OT &v) {
350         CHECK_MESSAGE(v.tag() == 0, "");  // won't get here unless true
351         int rval = getval_helper<1,IType>::get_integer_val(v);
352         CHECK_MESSAGE( rval == (last_index_seen[0]+1)*2, "");
353         last_index_seen[0] = rval / 2;
354     }
355 };
356 
357 template<typename IType, typename TType, int SIZE>
358 void test_one_serial( IType &my_indexer, tbb::flow::graph &g) {
359     last_index_seen.clear();
360     for(int ii=0; ii < SIZE; ++ii) last_index_seen.push_back(-1);
361 
362     typedef TType q3_input_type;
363     tbb::flow::queue_node< q3_input_type >  q3(g);
364     q3_input_type v;
365 
366     tbb::flow::make_edge(my_indexer, q3);
367 
368     // fill each queue with its value one-at-a-time
369     for (int i = 0; i < Count; ++i ) {
370         serial_queue_helper<SIZE,IType>::put_one_queue_val(i,my_indexer);
371     }
372 
373     g.wait_for_all();
374     for (int i = 0; i < Count * SIZE; ++i ) {
375         g.wait_for_all();
376         CHECK_MESSAGE( (q3.try_get( v )), "Error in try_get()");
377         {
378             serial_queue_helper<SIZE,IType>::check_queue_value(v);
379         }
380     }
381     CHECK_MESSAGE( (!q3.try_get( v )), "extra values in output queue");
382     for(int ii=0; ii < SIZE; ++ii) last_index_seen[ii] = -1;
383 
384     // fill each queue completely before filling the next.
385     serial_queue_helper<SIZE, IType>::fill_one_queue(Count,my_indexer);
386 
387     g.wait_for_all();
388     for (int i = 0; i < Count*SIZE; ++i ) {
389         g.wait_for_all();
390         CHECK_MESSAGE( (q3.try_get( v )), "Error in try_get()");
391         {
392             serial_queue_helper<SIZE,IType>::check_queue_value(v);
393         }
394     }
395     CHECK_MESSAGE( (!q3.try_get( v )), "extra values in output queue");
396 }
397 
398 //
399 template<typename NodeType>
400 void test_input_ports_return_ref(NodeType& mip_node) {
401     typename NodeType::input_ports_type& input_ports1 = mip_node.input_ports();
402     typename NodeType::input_ports_type& input_ports2 = mip_node.input_ports();
403     CHECK_MESSAGE( (&input_ports1 == &input_ports2), "input_ports() should return reference");
404 }
405 
406 // Single predecessor at each port, single accepting successor
407 //   * put to buffer before port0, then put to buffer before port1, ...
408 //   * fill buffer before port0 then fill buffer before port1, ...
409 
410 template<typename IType>
411 class serial_test {
412     typedef typename IType::output_type TType;  // this is the union
413     typedef typename IType::tuple_types union_types;
414     static const int SIZE = std::tuple_size<union_types>::value;
415 public:
416 static void test() {
417     tbb::flow::graph g;
418     static const int ELEMS = 3;
419     IType* my_indexer = new IType(g); //makeIndexer<IType>::create(g);
420 
421     test_input_ports_return_ref(*my_indexer);
422 
423     serial_queue_helper<SIZE, IType>::print_remark(); INFO(" >\n");
424 
425     test_one_serial<IType,TType,SIZE>(*my_indexer, g);
426 
427     std::vector<IType> indexer_vector(ELEMS,*my_indexer);
428 
429     makeIndexer<IType>::destroy(my_indexer);
430 
431     for(int e = 0; e < ELEMS; ++e) {
432         test_one_serial<IType,TType,SIZE>(indexer_vector[e], g);
433     }
434 }
435 
436 }; // serial_test
437 
438 template<
439       template<typename> class TestType,  // serial_test or parallel_test
440       typename T0, typename T1=void, typename T2=void, typename T3=void, typename T4=void,
441       typename T5=void, typename T6=void, typename T7=void, typename T8=void, typename T9=void> // type of the inputs to the indexer_node
442 class generate_test {
443 public:
444     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4, T5, T6, T7, T8, T9>  indexer_node_type;
445     static void do_test() {
446         TestType<indexer_node_type>::test();
447     }
448 };
449 
450 //specializations for indexer node inputs
451 template<
452       template<typename> class TestType,
453       typename T0, typename T1, typename T2, typename T3, typename T4,
454       typename T5, typename T6, typename T7, typename T8>
455 class generate_test<TestType, T0, T1, T2, T3, T4, T5, T6, T7, T8> {
456 public:
457     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4, T5, T6, T7, T8>  indexer_node_type;
458     static void do_test() {
459         TestType<indexer_node_type>::test();
460     }
461 };
462 
463 template<
464       template<typename> class TestType,
465       typename T0, typename T1, typename T2, typename T3, typename T4,
466       typename T5, typename T6, typename T7>
467 class generate_test<TestType, T0, T1, T2, T3, T4, T5, T6, T7> {
468 public:
469     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4, T5, T6, T7>  indexer_node_type;
470     static void do_test() {
471         TestType<indexer_node_type>::test();
472     }
473 };
474 
475 template<
476       template<typename> class TestType,
477       typename T0, typename T1, typename T2, typename T3, typename T4,
478       typename T5, typename T6>
479 class generate_test<TestType, T0, T1, T2, T3, T4, T5, T6> {
480 public:
481     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4, T5, T6>  indexer_node_type;
482     static void do_test() {
483         TestType<indexer_node_type>::test();
484     }
485 };
486 
487 template<
488       template<typename> class TestType,
489       typename T0, typename T1, typename T2, typename T3, typename T4,
490       typename T5>
491 class generate_test<TestType, T0, T1, T2, T3, T4, T5>  {
492 public:
493     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4, T5>  indexer_node_type;
494     static void do_test() {
495         TestType<indexer_node_type>::test();
496     }
497 };
498 
499 template<
500       template<typename> class TestType,
501       typename T0, typename T1, typename T2, typename T3, typename T4>
502 class generate_test<TestType, T0, T1, T2, T3, T4>  {
503 public:
504     typedef tbb::flow::indexer_node<T0, T1, T2, T3, T4>  indexer_node_type;
505     static void do_test() {
506         TestType<indexer_node_type>::test();
507     }
508 };
509 
510 template<
511       template<typename> class TestType,
512       typename T0, typename T1, typename T2, typename T3>
513 class generate_test<TestType, T0, T1, T2, T3> {
514 public:
515     typedef tbb::flow::indexer_node<T0, T1, T2, T3>  indexer_node_type;
516     static void do_test() {
517         TestType<indexer_node_type>::test();
518     }
519 };
520 
521 template<
522       template<typename> class TestType,
523       typename T0, typename T1, typename T2>
524 class generate_test<TestType, T0, T1, T2> {
525 public:
526     typedef tbb::flow::indexer_node<T0, T1, T2>  indexer_node_type;
527     static void do_test() {
528         TestType<indexer_node_type>::test();
529     }
530 };
531 
532 template<
533       template<typename> class TestType,
534       typename T0, typename T1>
535 class generate_test<TestType, T0, T1> {
536 public:
537     typedef tbb::flow::indexer_node<T0, T1>  indexer_node_type;
538     static void do_test() {
539         TestType<indexer_node_type>::test();
540     }
541 };
542 
543 template<
544       template<typename> class TestType,
545       typename T0>
546 class generate_test<TestType, T0> {
547 public:
548     typedef tbb::flow::indexer_node<T0>  indexer_node_type;
549     static void do_test() {
550         TestType<indexer_node_type>::test();
551     }
552 };
553 
554 #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
555 template<typename tagged_msg_t, typename input_t>
556 bool check_edge(tbb::flow::graph& g,
557                 tbb::flow::broadcast_node<input_t>& start,
558                 tbb::flow::buffer_node<tagged_msg_t>& buf,
559                 input_t input_value) {
560     start.try_put(input_value);
561     g.wait_for_all();
562 
563     tagged_msg_t msg;
564     bool is_get_succeeded = buf.try_get(msg);
565 
566     CHECK_MESSAGE( ((is_get_succeeded)), "There is no item in the buffer");
567     CHECK_MESSAGE( ((tbb::flow::cast_to<input_t>(msg) == input_value)), "Wrong item value");
568     return true;
569 }
570 
571 template <typename... T>
572 void sink(T...) {}
573 
574 template <typename indexer_output_t, typename Type, typename BN, std::size_t... Seq>
575 void check_edge(tbb::flow::graph& g, BN& bn, tbb::flow::buffer_node<indexer_output_t>& buf, Type, tbb::detail::index_sequence<Seq...>) {
576     sink(check_edge<indexer_output_t>(g, std::get<Seq>(bn), buf, typename std::tuple_element<Seq, Type>::type(Seq))...);
577 }
578 
579 template <typename... Args, std::size_t... Seq>
580 void test_follows_impl(std::tuple<Args...> t, tbb::detail::index_sequence<Seq...> seq) {
581     using namespace tbb::flow;
582     using indexer_output_t = typename indexer_node<Args...>::output_type;
583 
584     graph g;
585     auto bn = std::make_tuple(broadcast_node<Args>(g)...);
586 
587     indexer_node<Args...> my_indexer(follows(std::get<Seq>(bn)...));
588 
589     buffer_node<indexer_output_t> buf(g);
590     make_edge(my_indexer, buf);
591 
592     check_edge<indexer_output_t>(g, bn, buf, t, seq);
593 }
594 
595 template <typename... Args>
596 void test_follows() {
597     test_follows_impl(std::tuple<Args...>(), tbb::detail::make_index_sequence<sizeof...(Args)>());
598 }
599 
600 void test_precedes() {
601     using namespace tbb::flow;
602 
603     using indexer_output_t = indexer_node<int, float, double>::output_type;
604 
605     graph g;
606 
607     broadcast_node<int> start1(g);
608     broadcast_node<float> start2(g);
609     broadcast_node<double> start3(g);
610 
611     buffer_node<indexer_output_t> buf1(g);
612     buffer_node<indexer_output_t> buf2(g);
613     buffer_node<indexer_output_t> buf3(g);
614 
615     indexer_node<int, float, double> node(precedes(buf1, buf2, buf3));
616 
617     make_edge(start1, input_port<0>(node));
618     make_edge(start2, input_port<1>(node));
619     make_edge(start3, input_port<2>(node));
620 
621     check_edge<indexer_output_t, int>(g, start1, buf1, 1);
622     check_edge<indexer_output_t, float>(g, start2, buf2, 2.2f);
623     check_edge<indexer_output_t, double>(g, start3, buf3, 3.3);
624 }
625 
626 void test_follows_and_precedes_api() {
627     test_follows<double>();
628     test_follows<int, double>();
629     test_follows<int, float, double>();
630     test_follows<float, double, int, double>();
631     test_follows<float, double, int, double, double>();
632     test_follows<float, double, int, double, double, float>();
633     test_follows<float, double, int, double, double, float, long>();
634     test_follows<float, double, int, double, double, float, long, int>();
635     test_follows<float, double, int, double, double, float, long, int, long>();
636     test_follows<float, double, int, double, double, float, long, int, float, long>();
637     test_precedes();
638 }
639 #endif // __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
640 
641 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
642 void test_deduction_guides() {
643     using namespace tbb::flow;
644     graph g;
645 
646     broadcast_node<int> b1(g);
647     broadcast_node<double> b2(g);
648     indexer_node<int, double> i0(g);
649 
650 #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
651     indexer_node i1(follows(b1, b2));
652     static_assert(std::is_same_v<decltype(i1), indexer_node<int, double>>);
653 #endif
654 
655     indexer_node i2(i0);
656     static_assert(std::is_same_v<decltype(i2), indexer_node<int, double>>);
657 }
658 
659 #endif
660 
661 //! Serial and parallel test on various tuple sizes
662 //! \brief \ref error_guessing
663 TEST_CASE("Serial and parallel test") {
664     INFO("Testing indexer_node, ");
665 
666    for (int p = 0; p < 2; ++p) {
667        generate_test<serial_test, float>::do_test();
668 #if MAX_TUPLE_TEST_SIZE >= 4
669        generate_test<serial_test, float, double, int, short>::do_test();
670 #endif
671 #if MAX_TUPLE_TEST_SIZE >= 6
672        generate_test<serial_test, double, double, int, long, int, short>::do_test();
673 #endif
674 #if MAX_TUPLE_TEST_SIZE >= 8
675        generate_test<serial_test, float, double, double, double, float, int, float, long>::do_test();
676 #endif
677 #if MAX_TUPLE_TEST_SIZE >= 10
678        generate_test<serial_test, float, double, int, double, double, float, long, int, float, long>::do_test();
679 #endif
680        generate_test<parallel_test, float, double>::do_test();
681 #if MAX_TUPLE_TEST_SIZE >= 3
682        generate_test<parallel_test, float, int, long>::do_test();
683 #endif
684 #if MAX_TUPLE_TEST_SIZE >= 5
685        generate_test<parallel_test, double, double, int, int, short>::do_test();
686 #endif
687 #if MAX_TUPLE_TEST_SIZE >= 7
688        generate_test<parallel_test, float, int, double, float, long, float, long>::do_test();
689 #endif
690 #if MAX_TUPLE_TEST_SIZE >= 9
691        generate_test<parallel_test, float, double, int, double, double, long, int, float, long>::do_test();
692 #endif
693    }
694 }
695 
696 #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET
697 //! Test follows and precedes API
698 //! \brief \ref error_guessing
699 TEST_CASE("Follows and precedes API") {
700     test_follows_and_precedes_api();
701 }
702 #endif
703 
704 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
705 //! Test deduction guides
706 //! \brief \ref requirement
707 TEST_CASE("Deduction guides") {
708     test_deduction_guides();
709 }
710 #endif
711 
712