1*51c0b2f7Stbbdev /*
2*51c0b2f7Stbbdev     Copyright (c) 2020 Intel Corporation
3*51c0b2f7Stbbdev 
4*51c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
5*51c0b2f7Stbbdev     you may not use this file except in compliance with the License.
6*51c0b2f7Stbbdev     You may obtain a copy of the License at
7*51c0b2f7Stbbdev 
8*51c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
9*51c0b2f7Stbbdev 
10*51c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
11*51c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
12*51c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*51c0b2f7Stbbdev     See the License for the specific language governing permissions and
14*51c0b2f7Stbbdev     limitations under the License.
15*51c0b2f7Stbbdev */
16*51c0b2f7Stbbdev 
17*51c0b2f7Stbbdev #include "common/test.h"
18*51c0b2f7Stbbdev 
19*51c0b2f7Stbbdev #include "common/utils.h"
20*51c0b2f7Stbbdev #include "common/graph_utils.h"
21*51c0b2f7Stbbdev 
22*51c0b2f7Stbbdev #include "tbb/flow_graph.h"
23*51c0b2f7Stbbdev #include "tbb/task_arena.h"
24*51c0b2f7Stbbdev #include "tbb/global_control.h"
25*51c0b2f7Stbbdev 
26*51c0b2f7Stbbdev #include "conformance_flowgraph.h"
27*51c0b2f7Stbbdev 
28*51c0b2f7Stbbdev //! \file conformance_multifunction_node.cpp
29*51c0b2f7Stbbdev //! \brief Test for [flow_graph.function_node] specification
30*51c0b2f7Stbbdev 
31*51c0b2f7Stbbdev /*
32*51c0b2f7Stbbdev TODO: implement missing conformance tests for multifunction_node:
33*51c0b2f7Stbbdev   - [ ] Implement test_forwarding that checks messages are broadcast to all the successors connected
34*51c0b2f7Stbbdev     to the output port the message is being sent to. And check that the value passed is the
35*51c0b2f7Stbbdev     actual one received.
36*51c0b2f7Stbbdev   - [ ] Explicit test for copy constructor of the node.
37*51c0b2f7Stbbdev   - [ ] Constructor with explicitly passed Policy parameter: `template<typename Body>
38*51c0b2f7Stbbdev     multifunction_node( graph &g, size_t concurrency, Body body, Policy(), node_priority_t priority = no_priority )'.
39*51c0b2f7Stbbdev   - [ ] Concurrency testing of the node: make a loop over possible concurrency levels. It is
40*51c0b2f7Stbbdev     important to test at least on five values: 1, tbb::flow::serial, `max_allowed_parallelism'
41*51c0b2f7Stbbdev     obtained from `tbb::global_control', `tbb::flow::unlimited', and, if `max allowed
42*51c0b2f7Stbbdev     parallelism' is > 2, use something in the middle of the [1, max_allowed_parallelism]
43*51c0b2f7Stbbdev     interval. Use `utils::ExactConcurrencyLevel' entity (extending it if necessary).
44*51c0b2f7Stbbdev   - [ ] make `test_rejecting' deterministic, i.e. avoid dependency on OS scheduling of the threads;
45*51c0b2f7Stbbdev     add check that `try_put()' returns `false'
46*51c0b2f7Stbbdev   - [ ] The `copy_body' function copies altered body (e.g. after successful `try_put()' call).
47*51c0b2f7Stbbdev   - [ ] `output_ports_type' is defined and accessible by the user.
48*51c0b2f7Stbbdev   - [ ] Explicit test on `mfn::output_ports()' method.
49*51c0b2f7Stbbdev   - [ ] The copy constructor and copy assignment are called for the node's input and output types.
50*51c0b2f7Stbbdev   - [ ] Add CTAD test.
51*51c0b2f7Stbbdev */
52*51c0b2f7Stbbdev 
53*51c0b2f7Stbbdev template< typename OutputType >
54*51c0b2f7Stbbdev struct mf_functor {
55*51c0b2f7Stbbdev 
56*51c0b2f7Stbbdev     std::atomic<std::size_t>& local_execute_count;
57*51c0b2f7Stbbdev 
58*51c0b2f7Stbbdev     mf_functor(std::atomic<std::size_t>& execute_count ) :
59*51c0b2f7Stbbdev         local_execute_count (execute_count)
60*51c0b2f7Stbbdev     {  }
61*51c0b2f7Stbbdev 
62*51c0b2f7Stbbdev     mf_functor( const mf_functor &f ) : local_execute_count(f.local_execute_count) { }
63*51c0b2f7Stbbdev     void operator=(const mf_functor &f) { local_execute_count = std::size_t(f.local_execute_count); }
64*51c0b2f7Stbbdev 
65*51c0b2f7Stbbdev     void operator()( const int& argument, tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
66*51c0b2f7Stbbdev        ++local_execute_count;
67*51c0b2f7Stbbdev        std::get<0>(op).try_put(argument);
68*51c0b2f7Stbbdev     }
69*51c0b2f7Stbbdev 
70*51c0b2f7Stbbdev };
71*51c0b2f7Stbbdev 
72*51c0b2f7Stbbdev template<typename I, typename O>
73*51c0b2f7Stbbdev void test_inheritance(){
74*51c0b2f7Stbbdev     using namespace tbb::flow;
75*51c0b2f7Stbbdev 
76*51c0b2f7Stbbdev     CHECK_MESSAGE( (std::is_base_of<graph_node, multifunction_node<I, O>>::value), "multifunction_node should be derived from graph_node");
77*51c0b2f7Stbbdev     CHECK_MESSAGE( (std::is_base_of<receiver<I>, multifunction_node<I, O>>::value), "multifunction_node should be derived from receiver<Input>");
78*51c0b2f7Stbbdev }
79*51c0b2f7Stbbdev 
80*51c0b2f7Stbbdev void test_multifunc_body(){
81*51c0b2f7Stbbdev     tbb::flow::graph g;
82*51c0b2f7Stbbdev     std::atomic<size_t> local_count(0);
83*51c0b2f7Stbbdev     mf_functor<std::tuple<int>> fun(local_count);
84*51c0b2f7Stbbdev 
85*51c0b2f7Stbbdev     tbb::flow::multifunction_node<int, std::tuple<int>, tbb::flow::rejecting> node1(g, tbb::flow::unlimited, fun);
86*51c0b2f7Stbbdev 
87*51c0b2f7Stbbdev     const size_t n = 10;
88*51c0b2f7Stbbdev     for(size_t i = 0; i < n; ++i) {
89*51c0b2f7Stbbdev         CHECK_MESSAGE((node1.try_put(1) == true), "try_put needs to return true");
90*51c0b2f7Stbbdev     }
91*51c0b2f7Stbbdev     g.wait_for_all();
92*51c0b2f7Stbbdev 
93*51c0b2f7Stbbdev     CHECK_MESSAGE( (local_count == n), "Body of the node needs to be executed N times");
94*51c0b2f7Stbbdev }
95*51c0b2f7Stbbdev 
96*51c0b2f7Stbbdev template<typename I, typename O>
97*51c0b2f7Stbbdev struct CopyCounterBody{
98*51c0b2f7Stbbdev     size_t copy_count;
99*51c0b2f7Stbbdev 
100*51c0b2f7Stbbdev     CopyCounterBody():
101*51c0b2f7Stbbdev         copy_count(0) {}
102*51c0b2f7Stbbdev 
103*51c0b2f7Stbbdev     CopyCounterBody(const CopyCounterBody<I, O>& other):
104*51c0b2f7Stbbdev         copy_count(other.copy_count + 1) {}
105*51c0b2f7Stbbdev 
106*51c0b2f7Stbbdev     CopyCounterBody& operator=(const CopyCounterBody<I, O>& other)
107*51c0b2f7Stbbdev     { copy_count = other.copy_count + 1; return *this;}
108*51c0b2f7Stbbdev 
109*51c0b2f7Stbbdev     void operator()( const I& argument, tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
110*51c0b2f7Stbbdev        std::get<0>(op).try_put(argument);
111*51c0b2f7Stbbdev     }
112*51c0b2f7Stbbdev };
113*51c0b2f7Stbbdev 
114*51c0b2f7Stbbdev void test_copies(){
115*51c0b2f7Stbbdev      using namespace tbb::flow;
116*51c0b2f7Stbbdev 
117*51c0b2f7Stbbdev      CopyCounterBody<int, std::tuple<int>> b;
118*51c0b2f7Stbbdev 
119*51c0b2f7Stbbdev      graph g;
120*51c0b2f7Stbbdev      multifunction_node<int, std::tuple<int>> fn(g, unlimited, b);
121*51c0b2f7Stbbdev 
122*51c0b2f7Stbbdev      CopyCounterBody<int, std::tuple<int>> b2 = copy_body<CopyCounterBody<int, std::tuple<int>>,
123*51c0b2f7Stbbdev                                                           multifunction_node<int, std::tuple<int>>>(fn);
124*51c0b2f7Stbbdev 
125*51c0b2f7Stbbdev      CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
126*51c0b2f7Stbbdev }
127*51c0b2f7Stbbdev 
128*51c0b2f7Stbbdev template< typename OutputType >
129*51c0b2f7Stbbdev struct id_functor {
130*51c0b2f7Stbbdev     void operator()( const int& argument, tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
131*51c0b2f7Stbbdev        std::get<0>(op).try_put(argument);
132*51c0b2f7Stbbdev     }
133*51c0b2f7Stbbdev };
134*51c0b2f7Stbbdev 
135*51c0b2f7Stbbdev void test_forwarding(){
136*51c0b2f7Stbbdev     tbb::flow::graph g;
137*51c0b2f7Stbbdev     id_functor<int> fun;
138*51c0b2f7Stbbdev 
139*51c0b2f7Stbbdev     tbb::flow::multifunction_node<int, std::tuple<int>> node1(g, tbb::flow::unlimited, fun);
140*51c0b2f7Stbbdev     test_push_receiver<int> node2(g);
141*51c0b2f7Stbbdev     test_push_receiver<int> node3(g);
142*51c0b2f7Stbbdev 
143*51c0b2f7Stbbdev     tbb::flow::make_edge(node1, node2);
144*51c0b2f7Stbbdev     tbb::flow::make_edge(node1, node3);
145*51c0b2f7Stbbdev 
146*51c0b2f7Stbbdev     node1.try_put(1);
147*51c0b2f7Stbbdev     g.wait_for_all();
148*51c0b2f7Stbbdev 
149*51c0b2f7Stbbdev     CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
150*51c0b2f7Stbbdev     CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
151*51c0b2f7Stbbdev }
152*51c0b2f7Stbbdev 
153*51c0b2f7Stbbdev void test_rejecting_buffering(){
154*51c0b2f7Stbbdev     tbb::flow::graph g;
155*51c0b2f7Stbbdev     id_functor<int> fun;
156*51c0b2f7Stbbdev 
157*51c0b2f7Stbbdev     tbb::flow::multifunction_node<int, std::tuple<int>, tbb::flow::rejecting> node(g, tbb::flow::unlimited, fun);
158*51c0b2f7Stbbdev     tbb::flow::limiter_node<int> rejecter(g, 0);
159*51c0b2f7Stbbdev 
160*51c0b2f7Stbbdev     tbb::flow::make_edge(node, rejecter);
161*51c0b2f7Stbbdev     node.try_put(1);
162*51c0b2f7Stbbdev 
163*51c0b2f7Stbbdev     int tmp = -1;
164*51c0b2f7Stbbdev     CHECK_MESSAGE( (std::get<0>(node.output_ports()).try_get(tmp) == false), "try_get after rejection should not succeed");
165*51c0b2f7Stbbdev     CHECK_MESSAGE( (tmp == -1), "try_get after rejection should alter passed value");
166*51c0b2f7Stbbdev     g.wait_for_all();
167*51c0b2f7Stbbdev }
168*51c0b2f7Stbbdev 
169*51c0b2f7Stbbdev void test_policy_ctors(){
170*51c0b2f7Stbbdev     using namespace tbb::flow;
171*51c0b2f7Stbbdev     graph g;
172*51c0b2f7Stbbdev 
173*51c0b2f7Stbbdev     id_functor<int> fun;
174*51c0b2f7Stbbdev 
175*51c0b2f7Stbbdev     multifunction_node<int, std::tuple<int>, lightweight> lw_node(g, tbb::flow::serial, fun);
176*51c0b2f7Stbbdev     multifunction_node<int, std::tuple<int>, queueing_lightweight> qlw_node(g, tbb::flow::serial, fun);
177*51c0b2f7Stbbdev     multifunction_node<int, std::tuple<int>, rejecting_lightweight> rlw_node(g, tbb::flow::serial, fun);
178*51c0b2f7Stbbdev 
179*51c0b2f7Stbbdev }
180*51c0b2f7Stbbdev 
181*51c0b2f7Stbbdev std::atomic<size_t> my_concurrency;
182*51c0b2f7Stbbdev std::atomic<size_t> my_max_concurrency;
183*51c0b2f7Stbbdev 
184*51c0b2f7Stbbdev struct concurrency_functor {
185*51c0b2f7Stbbdev     void operator()( const int& argument, tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ) {
186*51c0b2f7Stbbdev         ++my_concurrency;
187*51c0b2f7Stbbdev 
188*51c0b2f7Stbbdev         size_t old_value = my_max_concurrency;
189*51c0b2f7Stbbdev         while(my_max_concurrency < my_concurrency &&
190*51c0b2f7Stbbdev               !my_max_concurrency.compare_exchange_weak(old_value, my_concurrency))
191*51c0b2f7Stbbdev             ;
192*51c0b2f7Stbbdev 
193*51c0b2f7Stbbdev         size_t ms = 1000;
194*51c0b2f7Stbbdev         std::chrono::milliseconds sleep_time( ms );
195*51c0b2f7Stbbdev         std::this_thread::sleep_for( sleep_time );
196*51c0b2f7Stbbdev 
197*51c0b2f7Stbbdev         --my_concurrency;
198*51c0b2f7Stbbdev         std::get<0>(op).try_put(argument);
199*51c0b2f7Stbbdev     }
200*51c0b2f7Stbbdev 
201*51c0b2f7Stbbdev };
202*51c0b2f7Stbbdev 
203*51c0b2f7Stbbdev void test_node_concurrency(){
204*51c0b2f7Stbbdev     my_concurrency = 0;
205*51c0b2f7Stbbdev     my_max_concurrency = 0;
206*51c0b2f7Stbbdev 
207*51c0b2f7Stbbdev     tbb::flow::graph g;
208*51c0b2f7Stbbdev 
209*51c0b2f7Stbbdev     concurrency_functor counter;
210*51c0b2f7Stbbdev     tbb::flow::multifunction_node <int, std::tuple<int>> fnode(g, tbb::flow::serial, counter);
211*51c0b2f7Stbbdev 
212*51c0b2f7Stbbdev     test_push_receiver<int> sink(g);
213*51c0b2f7Stbbdev 
214*51c0b2f7Stbbdev     make_edge(std::get<0>(fnode.output_ports()), sink);
215*51c0b2f7Stbbdev 
216*51c0b2f7Stbbdev     for(int i = 0; i < 10; ++i){
217*51c0b2f7Stbbdev         fnode.try_put(i);
218*51c0b2f7Stbbdev     }
219*51c0b2f7Stbbdev 
220*51c0b2f7Stbbdev     g.wait_for_all();
221*51c0b2f7Stbbdev     CHECK_MESSAGE( ( my_max_concurrency.load() == 1), "Measured parallelism over limit");
222*51c0b2f7Stbbdev }
223*51c0b2f7Stbbdev 
224*51c0b2f7Stbbdev 
225*51c0b2f7Stbbdev void test_priority(){
226*51c0b2f7Stbbdev     size_t concurrency_limit = 1;
227*51c0b2f7Stbbdev     tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_limit);
228*51c0b2f7Stbbdev 
229*51c0b2f7Stbbdev     tbb::flow::graph g;
230*51c0b2f7Stbbdev 
231*51c0b2f7Stbbdev     tbb::flow::continue_node<int> source(g,
232*51c0b2f7Stbbdev                                          [](tbb::flow::continue_msg){ return 1;});
233*51c0b2f7Stbbdev     source.try_put(tbb::flow::continue_msg());
234*51c0b2f7Stbbdev 
235*51c0b2f7Stbbdev     first_functor<int>::first_id = -1;
236*51c0b2f7Stbbdev     first_functor<int> low_functor(1);
237*51c0b2f7Stbbdev     first_functor<int> high_functor(2);
238*51c0b2f7Stbbdev 
239*51c0b2f7Stbbdev     tbb::flow::multifunction_node<int, std::tuple<int>> high(g, tbb::flow::unlimited, high_functor, tbb::flow::node_priority_t(1));
240*51c0b2f7Stbbdev     tbb::flow::multifunction_node<int, std::tuple<int>> low(g, tbb::flow::unlimited, low_functor);
241*51c0b2f7Stbbdev 
242*51c0b2f7Stbbdev     make_edge(source, low);
243*51c0b2f7Stbbdev     make_edge(source, high);
244*51c0b2f7Stbbdev 
245*51c0b2f7Stbbdev     g.wait_for_all();
246*51c0b2f7Stbbdev 
247*51c0b2f7Stbbdev     CHECK_MESSAGE( (first_functor<int>::first_id == 2), "High priority node should execute first");
248*51c0b2f7Stbbdev }
249*51c0b2f7Stbbdev 
250*51c0b2f7Stbbdev void test_rejecting(){
251*51c0b2f7Stbbdev     tbb::flow::graph g;
252*51c0b2f7Stbbdev     tbb::flow::multifunction_node <int, std::tuple<int>, tbb::flow::rejecting> fnode(g, tbb::flow::serial,
253*51c0b2f7Stbbdev                                                                     [&](const int& argument, tbb::flow::multifunction_node<int, std::tuple<int>>::output_ports_type &op ){
254*51c0b2f7Stbbdev                                                                         size_t ms = 50;
255*51c0b2f7Stbbdev                                                                         std::chrono::milliseconds sleep_time( ms );
256*51c0b2f7Stbbdev                                                                         std::this_thread::sleep_for( sleep_time );
257*51c0b2f7Stbbdev                                                                         std::get<0>(op).try_put(argument);
258*51c0b2f7Stbbdev                                                                     });
259*51c0b2f7Stbbdev 
260*51c0b2f7Stbbdev     test_push_receiver<int> sink(g);
261*51c0b2f7Stbbdev 
262*51c0b2f7Stbbdev     make_edge(std::get<0>(fnode.output_ports()), sink);
263*51c0b2f7Stbbdev 
264*51c0b2f7Stbbdev     for(int i = 0; i < 10; ++i){
265*51c0b2f7Stbbdev         fnode.try_put(i);
266*51c0b2f7Stbbdev     }
267*51c0b2f7Stbbdev 
268*51c0b2f7Stbbdev     g.wait_for_all();
269*51c0b2f7Stbbdev     CHECK_MESSAGE( (get_count(sink) == 1), "Messages should be rejected while the first is being processed");
270*51c0b2f7Stbbdev }
271*51c0b2f7Stbbdev 
272*51c0b2f7Stbbdev //! Test multifunction_node with rejecting policy
273*51c0b2f7Stbbdev //! \brief \ref interface
274*51c0b2f7Stbbdev TEST_CASE("multifunction_node with rejecting policy"){
275*51c0b2f7Stbbdev     test_rejecting();
276*51c0b2f7Stbbdev }
277*51c0b2f7Stbbdev 
278*51c0b2f7Stbbdev //! Test priorities
279*51c0b2f7Stbbdev //! \brief \ref interface
280*51c0b2f7Stbbdev TEST_CASE("multifunction_node priority"){
281*51c0b2f7Stbbdev     test_priority();
282*51c0b2f7Stbbdev }
283*51c0b2f7Stbbdev 
284*51c0b2f7Stbbdev //! Test concurrency
285*51c0b2f7Stbbdev //! \brief \ref interface
286*51c0b2f7Stbbdev TEST_CASE("multifunction_node concurrency"){
287*51c0b2f7Stbbdev     test_node_concurrency();
288*51c0b2f7Stbbdev }
289*51c0b2f7Stbbdev 
290*51c0b2f7Stbbdev //! Test constructors
291*51c0b2f7Stbbdev //! \brief \ref interface
292*51c0b2f7Stbbdev TEST_CASE("multifunction_node constructors"){
293*51c0b2f7Stbbdev     test_policy_ctors();
294*51c0b2f7Stbbdev }
295*51c0b2f7Stbbdev 
296*51c0b2f7Stbbdev //! Test function_node buffering
297*51c0b2f7Stbbdev //! \brief \ref requirement
298*51c0b2f7Stbbdev TEST_CASE("multifunction_node buffering"){
299*51c0b2f7Stbbdev     test_rejecting_buffering();
300*51c0b2f7Stbbdev }
301*51c0b2f7Stbbdev 
302*51c0b2f7Stbbdev //! Test function_node broadcasting
303*51c0b2f7Stbbdev //! \brief \ref requirement
304*51c0b2f7Stbbdev TEST_CASE("multifunction_node broadcast"){
305*51c0b2f7Stbbdev     test_forwarding();
306*51c0b2f7Stbbdev }
307*51c0b2f7Stbbdev 
308*51c0b2f7Stbbdev //! Test body copying and copy_body logic
309*51c0b2f7Stbbdev //! \brief \ref interface
310*51c0b2f7Stbbdev TEST_CASE("multifunction_node constructors"){
311*51c0b2f7Stbbdev     test_copies();
312*51c0b2f7Stbbdev }
313*51c0b2f7Stbbdev 
314*51c0b2f7Stbbdev //! Test calling function body
315*51c0b2f7Stbbdev //! \brief \ref interface \ref requirement
316*51c0b2f7Stbbdev TEST_CASE("multifunction_node body") {
317*51c0b2f7Stbbdev     test_multifunc_body();
318*51c0b2f7Stbbdev }
319*51c0b2f7Stbbdev 
320*51c0b2f7Stbbdev //! Test inheritance relations
321*51c0b2f7Stbbdev //! \brief \ref interface
322*51c0b2f7Stbbdev TEST_CASE("multifunction_node superclasses"){
323*51c0b2f7Stbbdev     test_inheritance<int, std::tuple<int>>();
324*51c0b2f7Stbbdev     test_inheritance<void*, std::tuple<float>>();
325*51c0b2f7Stbbdev }
326