1 /*
2     Copyright (c) 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 #include "common/test.h"
18 
19 #include "common/utils.h"
20 #include "common/graph_utils.h"
21 
22 #include "tbb/flow_graph.h"
23 #include "tbb/task_arena.h"
24 
25 #include "tbb/global_control.h"
26 #include "conformance_flowgraph.h"
27 
28 //! \file conformance_continue_node.cpp
29 //! \brief Test for [flow_graph.continue_node] specification
30 
31 /*
32 TODO: implement missing conformance tests for continue_node:
33   - [ ] For `test_forwarding' check that the value passed is the actual one received.
34   - [ ] The `copy_body' function copies altered body (e.g. after its successful invocation).
35   - [ ] Improve CTAD test.
36   - [ ] Improve constructors test, including addition of calls to constructors with
37     `number_of_predecessors' parameter.
38   - [ ] Explicit test for copy constructor of the node.
39   - [ ] Rewrite test_priority.
40   - [ ] Check `Output' type indeed copy-constructed and copy-assigned while working with the node.
41   - [ ] Explicit test for correct working of `number_of_predecessors' constructor parameter,
42     including taking it into account when making and removing edges.
43   - [ ] Add testing of `try_put' statement. In particular that it does not wait for the execution of
44     the body to complete.
45 */
46 
47 void test_cont_body(){
48     tbb::flow::graph g;
49     inc_functor<int> cf;
50     cf.execute_count = 0;
51 
52     tbb::flow::continue_node<int> node1(g, cf);
53 
54     const size_t n = 10;
55     for(size_t i = 0; i < n; ++i) {
56         CHECK_MESSAGE((node1.try_put(tbb::flow::continue_msg()) == true),
57                       "continue_node::try_put() should never reject a message.");
58     }
59     g.wait_for_all();
60 
61     CHECK_MESSAGE( (cf.execute_count == n), "Body of the first node needs to be executed N times");
62 }
63 
64 template<typename O>
65 void test_inheritance(){
66     using namespace tbb::flow;
67 
68     CHECK_MESSAGE( (std::is_base_of<graph_node, continue_node<O>>::value), "continue_node should be derived from graph_node");
69     CHECK_MESSAGE( (std::is_base_of<receiver<continue_msg>, continue_node<O>>::value), "continue_node should be derived from receiver<Input>");
70     CHECK_MESSAGE( (std::is_base_of<sender<O>, continue_node<O>>::value), "continue_node should be derived from sender<Output>");
71 }
72 
73 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
74 void test_deduction_guides(){
75     tbb::flow::graph g;
76     inc_functor<int> fun;
77     tbb::flow::continue_node node1(g, fun);
78 }
79 #endif
80 
81 void test_forwarding(){
82     tbb::flow::graph g;
83     inc_functor<int> fun;
84     fun.execute_count = 0;
85 
86     tbb::flow::continue_node<int> node1(g, fun);
87     test_push_receiver<int> node2(g);
88     test_push_receiver<int> node3(g);
89 
90     tbb::flow::make_edge(node1, node2);
91     tbb::flow::make_edge(node1, node3);
92 
93     node1.try_put(tbb::flow::continue_msg());
94     g.wait_for_all();
95 
96     CHECK_MESSAGE( (get_count(node2) == 1), "Descendant of the node must receive one message.");
97     CHECK_MESSAGE( (get_count(node3) == 1), "Descendant of the node must receive one message.");
98 }
99 
100 void test_buffering(){
101     tbb::flow::graph g;
102     inc_functor<int> fun;
103 
104     tbb::flow::continue_node<int> node(g, fun);
105     tbb::flow::limiter_node<int> rejecter(g, 0);
106 
107     tbb::flow::make_edge(node, rejecter);
108     node.try_put(tbb::flow::continue_msg());
109 
110     int tmp = -1;
111     CHECK_MESSAGE( (node.try_get(tmp) == false), "try_get after rejection should not succeed");
112     CHECK_MESSAGE( (tmp == -1), "try_get after rejection should not alter passed value");
113     g.wait_for_all();
114 }
115 
116 void test_policy_ctors(){
117     using namespace tbb::flow;
118     graph g;
119 
120     inc_functor<int> fun;
121 
122     continue_node<int, lightweight> lw_node(g, fun);
123 }
124 
125 void test_ctors(){
126     using namespace tbb::flow;
127     graph g;
128 
129     inc_functor<int> fun;
130 
131     continue_node<int> proto1(g, 2, fun, tbb::flow::node_priority_t(1));
132 }
133 
134 template<typename O>
135 struct CopyCounterBody{
136     size_t copy_count;
137 
138     CopyCounterBody():
139         copy_count(0) {}
140 
141     CopyCounterBody(const CopyCounterBody<O>& other):
142         copy_count(other.copy_count + 1) {}
143 
144     CopyCounterBody& operator=(const CopyCounterBody<O>& other){
145         copy_count = other.copy_count + 1;
146         return *this;
147     }
148 
149     O operator()(tbb::flow::continue_msg){
150         return 1;
151     }
152 };
153 
154 void test_copies(){
155     using namespace tbb::flow;
156 
157     CopyCounterBody<int> b;
158 
159     graph g;
160     continue_node<int> fn(g, b);
161 
162     CopyCounterBody<int> b2 = copy_body<CopyCounterBody<int>,
163                                              continue_node<int>>(fn);
164 
165     CHECK_MESSAGE( (b.copy_count + 2 <= b2.copy_count), "copy_body and constructor should copy bodies");
166 }
167 
168 
169 void test_priority(){
170     size_t concurrency_limit = 1;
171     tbb::global_control control(tbb::global_control::max_allowed_parallelism, concurrency_limit);
172 
173     tbb::flow::graph g;
174 
175     tbb::flow::continue_node<tbb::flow::continue_msg> source(g,
176                                                              [](tbb::flow::continue_msg){ return tbb::flow::continue_msg();});
177     source.try_put(tbb::flow::continue_msg());
178 
179     first_functor<int>::first_id = -1;
180     first_functor<int> low_functor(1);
181     first_functor<int> high_functor(2);
182 
183     tbb::flow::continue_node<int, int> high(g, high_functor, tbb::flow::node_priority_t(1));
184     tbb::flow::continue_node<int, int> low(g, low_functor);
185 
186     make_edge(source, low);
187     make_edge(source, high);
188 
189     g.wait_for_all();
190 
191     CHECK_MESSAGE( (first_functor<int>::first_id == 2), "High priority node should execute first");
192 }
193 
194 //! Test node costructors
195 //! \brief \ref requirement
196 TEST_CASE("continue_node constructors"){
197     test_ctors();
198 }
199 
200 //! Test priorities work in single-threaded configuration
201 //! \brief \ref requirement
202 TEST_CASE("continue_node priority support"){
203     test_priority();
204 }
205 
206 //! Test body copying and copy_body logic
207 //! \brief \ref interface
208 TEST_CASE("continue_node and body copying"){
209     test_copies();
210 }
211 
212 //! Test constructors
213 //! \brief \ref interface
214 TEST_CASE("continue_node constructors"){
215     test_policy_ctors();
216 }
217 
218 //! Test continue_node buffering
219 //! \brief \ref requirement
220 TEST_CASE("continue_node buffering"){
221     test_buffering();
222 }
223 
224 //! Test function_node broadcasting
225 //! \brief \ref requirement
226 TEST_CASE("continue_node broadcast"){
227     test_forwarding();
228 }
229 
230 //! Test deduction guides
231 //! \brief \ref interface \ref requirement
232 TEST_CASE("Deduction guides"){
233 #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT
234     test_deduction_guides();
235 #endif
236 }
237 
238 //! Test inheritance relations
239 //! \brief \ref interface
240 TEST_CASE("continue_node superclasses"){
241     test_inheritance<int>();
242     test_inheritance<void*>();
243 }
244 
245 //! Test body execution
246 //! \brief \ref interface \ref requirement
247 TEST_CASE("continue body") {
248     test_cont_body();
249 }
250