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