1*51c0b2f7Stbbdev /* 2*51c0b2f7Stbbdev Copyright (c) 2005-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/config.h" 18*51c0b2f7Stbbdev 19*51c0b2f7Stbbdev #include "test_join_node.h" 20*51c0b2f7Stbbdev 21*51c0b2f7Stbbdev 22*51c0b2f7Stbbdev //! \file test_join_node.cpp 23*51c0b2f7Stbbdev //! \brief Test for [flow_graph.join_node] specification 24*51c0b2f7Stbbdev 25*51c0b2f7Stbbdev 26*51c0b2f7Stbbdev static std::atomic<int> output_count; 27*51c0b2f7Stbbdev 28*51c0b2f7Stbbdev // get the tag from the output tuple and emit it. 29*51c0b2f7Stbbdev // the first tuple component is tag * 2 cast to the type 30*51c0b2f7Stbbdev template<typename OutputTupleType> 31*51c0b2f7Stbbdev class recirc_output_func_body { 32*51c0b2f7Stbbdev public: 33*51c0b2f7Stbbdev // we only need this to use input_node_helper 34*51c0b2f7Stbbdev typedef typename tbb::flow::join_node<OutputTupleType, tbb::flow::tag_matching> join_node_type; 35*51c0b2f7Stbbdev static const int N = std::tuple_size<OutputTupleType>::value; 36*51c0b2f7Stbbdev int operator()(const OutputTupleType &v) { 37*51c0b2f7Stbbdev int out = int(std::get<0>(v))/2; 38*51c0b2f7Stbbdev input_node_helper<N, join_node_type>::only_check_value(out, v); 39*51c0b2f7Stbbdev ++output_count; 40*51c0b2f7Stbbdev return out; 41*51c0b2f7Stbbdev } 42*51c0b2f7Stbbdev }; 43*51c0b2f7Stbbdev 44*51c0b2f7Stbbdev template<typename JType> 45*51c0b2f7Stbbdev class tag_recirculation_test { 46*51c0b2f7Stbbdev public: 47*51c0b2f7Stbbdev typedef typename JType::output_type TType; 48*51c0b2f7Stbbdev typedef typename std::tuple<int, tbb::flow::continue_msg> input_tuple_type; 49*51c0b2f7Stbbdev typedef tbb::flow::join_node<input_tuple_type, tbb::flow::reserving> input_join_type; 50*51c0b2f7Stbbdev static const int N = std::tuple_size<TType>::value; 51*51c0b2f7Stbbdev static void test() { 52*51c0b2f7Stbbdev input_node_helper<N, JType>::print_remark("Recirculation test of tag-matching join"); 53*51c0b2f7Stbbdev INFO(" >\n"); 54*51c0b2f7Stbbdev for(int maxTag = 1; maxTag <10; maxTag *= 3) { 55*51c0b2f7Stbbdev for(int i = 0; i < N; ++i) all_input_nodes[i][0] = NULL; 56*51c0b2f7Stbbdev 57*51c0b2f7Stbbdev tbb::flow::graph g; 58*51c0b2f7Stbbdev // this is the tag-matching join we're testing 59*51c0b2f7Stbbdev JType * my_join = makeJoin<N, JType, tbb::flow::tag_matching>::create(g); 60*51c0b2f7Stbbdev // input_node for continue messages 61*51c0b2f7Stbbdev tbb::flow::input_node<tbb::flow::continue_msg> snode(g, recirc_input_node_body()); 62*51c0b2f7Stbbdev // reserving join that matches recirculating tags with continue messages. 63*51c0b2f7Stbbdev input_join_type * my_input_join = makeJoin<2, input_join_type, tbb::flow::reserving>::create(g); 64*51c0b2f7Stbbdev // tbb::flow::make_edge(snode, tbb::flow::input_port<1>(*my_input_join)); 65*51c0b2f7Stbbdev tbb::flow::make_edge(snode, std::get<1>(my_input_join->input_ports())); 66*51c0b2f7Stbbdev // queue to hold the tags 67*51c0b2f7Stbbdev tbb::flow::queue_node<int> tag_queue(g); 68*51c0b2f7Stbbdev tbb::flow::make_edge(tag_queue, tbb::flow::input_port<0>(*my_input_join)); 69*51c0b2f7Stbbdev // add all the function_nodes that are inputs to the tag-matching join 70*51c0b2f7Stbbdev input_node_helper<N, JType>::add_recirc_func_nodes(*my_join, *my_input_join, g); 71*51c0b2f7Stbbdev // add the function_node that accepts the output of the join and emits the int tag it was based on 72*51c0b2f7Stbbdev tbb::flow::function_node<TType, int> recreate_tag(g, tbb::flow::unlimited, recirc_output_func_body<TType>()); 73*51c0b2f7Stbbdev tbb::flow::make_edge(*my_join, recreate_tag); 74*51c0b2f7Stbbdev // now the recirculating part (output back to the queue) 75*51c0b2f7Stbbdev tbb::flow::make_edge(recreate_tag, tag_queue); 76*51c0b2f7Stbbdev 77*51c0b2f7Stbbdev // put the tags into the queue 78*51c0b2f7Stbbdev for(int t = 1; t<=maxTag; ++t) tag_queue.try_put(t); 79*51c0b2f7Stbbdev 80*51c0b2f7Stbbdev input_count = Recirc_count; 81*51c0b2f7Stbbdev output_count = 0; 82*51c0b2f7Stbbdev 83*51c0b2f7Stbbdev // start up the source node to get things going 84*51c0b2f7Stbbdev snode.activate(); 85*51c0b2f7Stbbdev 86*51c0b2f7Stbbdev // wait for everything to stop 87*51c0b2f7Stbbdev g.wait_for_all(); 88*51c0b2f7Stbbdev 89*51c0b2f7Stbbdev CHECK_MESSAGE( (output_count==Recirc_count), "not all instances were received"); 90*51c0b2f7Stbbdev 91*51c0b2f7Stbbdev int j{}; 92*51c0b2f7Stbbdev // grab the tags from the queue, record them 93*51c0b2f7Stbbdev std::vector<bool> out_tally(maxTag, false); 94*51c0b2f7Stbbdev for(int i = 0; i < maxTag; ++i) { 95*51c0b2f7Stbbdev CHECK_MESSAGE( (tag_queue.try_get(j)), "not enough tags in queue"); 96*51c0b2f7Stbbdev CHECK_MESSAGE( (!out_tally.at(j-1)), "duplicate tag from queue"); 97*51c0b2f7Stbbdev out_tally[j-1] = true; 98*51c0b2f7Stbbdev } 99*51c0b2f7Stbbdev CHECK_MESSAGE( (!tag_queue.try_get(j)), "Extra tags in recirculation queue"); 100*51c0b2f7Stbbdev 101*51c0b2f7Stbbdev // deconstruct graph 102*51c0b2f7Stbbdev input_node_helper<N, JType>::remove_recirc_func_nodes(*my_join, *my_input_join); 103*51c0b2f7Stbbdev tbb::flow::remove_edge(*my_join, recreate_tag); 104*51c0b2f7Stbbdev makeJoin<N, JType, tbb::flow::tag_matching>::destroy(my_join); 105*51c0b2f7Stbbdev tbb::flow::remove_edge(tag_queue, tbb::flow::input_port<0>(*my_input_join)); 106*51c0b2f7Stbbdev tbb::flow::remove_edge(snode, tbb::flow::input_port<1>(*my_input_join)); 107*51c0b2f7Stbbdev makeJoin<2, input_join_type, tbb::flow::reserving>::destroy(my_input_join); 108*51c0b2f7Stbbdev } 109*51c0b2f7Stbbdev } 110*51c0b2f7Stbbdev }; 111*51c0b2f7Stbbdev 112*51c0b2f7Stbbdev template<typename JType> 113*51c0b2f7Stbbdev class generate_recirc_test { 114*51c0b2f7Stbbdev public: 115*51c0b2f7Stbbdev typedef tbb::flow::join_node<JType, tbb::flow::tag_matching> join_node_type; 116*51c0b2f7Stbbdev static void do_test() { 117*51c0b2f7Stbbdev tag_recirculation_test<join_node_type>::test(); 118*51c0b2f7Stbbdev } 119*51c0b2f7Stbbdev }; 120*51c0b2f7Stbbdev 121*51c0b2f7Stbbdev #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET 122*51c0b2f7Stbbdev #include <array> 123*51c0b2f7Stbbdev #include <vector> 124*51c0b2f7Stbbdev void test_follows_and_precedes_api() { 125*51c0b2f7Stbbdev using msg_t = tbb::flow::continue_msg; 126*51c0b2f7Stbbdev using JoinOutputType = std::tuple<msg_t, msg_t, msg_t>; 127*51c0b2f7Stbbdev 128*51c0b2f7Stbbdev std::array<msg_t, 3> messages_for_follows = { {msg_t(), msg_t(), msg_t()} }; 129*51c0b2f7Stbbdev std::vector<msg_t> messages_for_precedes = {msg_t(), msg_t(), msg_t()}; 130*51c0b2f7Stbbdev 131*51c0b2f7Stbbdev follows_and_precedes_testing::test_follows 132*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType>, tbb::flow::buffer_node<msg_t>>(messages_for_follows); 133*51c0b2f7Stbbdev follows_and_precedes_testing::test_follows 134*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::queueing>>(messages_for_follows); 135*51c0b2f7Stbbdev follows_and_precedes_testing::test_follows 136*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::reserving>, tbb::flow::buffer_node<msg_t>>(messages_for_follows); 137*51c0b2f7Stbbdev auto b = [](msg_t) { return msg_t(); }; 138*51c0b2f7Stbbdev class hash_compare { 139*51c0b2f7Stbbdev public: 140*51c0b2f7Stbbdev std::size_t hash(msg_t) const { return 0; } 141*51c0b2f7Stbbdev bool equal(msg_t, msg_t) const { return true; } 142*51c0b2f7Stbbdev }; 143*51c0b2f7Stbbdev follows_and_precedes_testing::test_follows 144*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::key_matching<msg_t, hash_compare>>, tbb::flow::buffer_node<msg_t>> 145*51c0b2f7Stbbdev (messages_for_follows, b, b, b); 146*51c0b2f7Stbbdev 147*51c0b2f7Stbbdev follows_and_precedes_testing::test_precedes 148*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType>>(messages_for_precedes); 149*51c0b2f7Stbbdev follows_and_precedes_testing::test_precedes 150*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::queueing>>(messages_for_precedes); 151*51c0b2f7Stbbdev follows_and_precedes_testing::test_precedes 152*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::reserving>>(messages_for_precedes); 153*51c0b2f7Stbbdev follows_and_precedes_testing::test_precedes 154*51c0b2f7Stbbdev <msg_t, tbb::flow::join_node<JoinOutputType, tbb::flow::key_matching<msg_t, hash_compare>>> 155*51c0b2f7Stbbdev (messages_for_precedes, b, b, b); 156*51c0b2f7Stbbdev } 157*51c0b2f7Stbbdev #endif 158*51c0b2f7Stbbdev 159*51c0b2f7Stbbdev #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 160*51c0b2f7Stbbdev void test_deduction_guides() { 161*51c0b2f7Stbbdev using namespace tbb::flow; 162*51c0b2f7Stbbdev 163*51c0b2f7Stbbdev graph g; 164*51c0b2f7Stbbdev using tuple_type = std::tuple<int, int, int>; 165*51c0b2f7Stbbdev broadcast_node<int> b1(g), b2(g), b3(g); 166*51c0b2f7Stbbdev broadcast_node<tuple_type> b4(g); 167*51c0b2f7Stbbdev join_node<tuple_type> j0(g); 168*51c0b2f7Stbbdev 169*51c0b2f7Stbbdev #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET 170*51c0b2f7Stbbdev join_node j1(follows(b1, b2, b3)); 171*51c0b2f7Stbbdev static_assert(std::is_same_v<decltype(j1), join_node<tuple_type>>); 172*51c0b2f7Stbbdev 173*51c0b2f7Stbbdev join_node j2(follows(b1, b2, b3), reserving()); 174*51c0b2f7Stbbdev static_assert(std::is_same_v<decltype(j2), join_node<tuple_type, reserving>>); 175*51c0b2f7Stbbdev 176*51c0b2f7Stbbdev join_node j3(precedes(b4)); 177*51c0b2f7Stbbdev static_assert(std::is_same_v<decltype(j3), join_node<tuple_type>>); 178*51c0b2f7Stbbdev 179*51c0b2f7Stbbdev join_node j4(precedes(b4), reserving()); 180*51c0b2f7Stbbdev static_assert(std::is_same_v<decltype(j4), join_node<tuple_type, reserving>>); 181*51c0b2f7Stbbdev #endif 182*51c0b2f7Stbbdev 183*51c0b2f7Stbbdev join_node j5(j0); 184*51c0b2f7Stbbdev static_assert(std::is_same_v<decltype(j5), join_node<tuple_type>>); 185*51c0b2f7Stbbdev } 186*51c0b2f7Stbbdev 187*51c0b2f7Stbbdev #endif 188*51c0b2f7Stbbdev 189*51c0b2f7Stbbdev namespace multiple_predecessors { 190*51c0b2f7Stbbdev 191*51c0b2f7Stbbdev using namespace tbb::flow; 192*51c0b2f7Stbbdev 193*51c0b2f7Stbbdev using join_node_t = join_node<std::tuple<continue_msg, continue_msg, continue_msg>, reserving>; 194*51c0b2f7Stbbdev using queue_node_t = queue_node<std::tuple<continue_msg, continue_msg, continue_msg>>; 195*51c0b2f7Stbbdev 196*51c0b2f7Stbbdev void twist_join_connections( 197*51c0b2f7Stbbdev buffer_node<continue_msg>& bn1, buffer_node<continue_msg>& bn2, buffer_node<continue_msg>& bn3, 198*51c0b2f7Stbbdev join_node_t& jn) 199*51c0b2f7Stbbdev { 200*51c0b2f7Stbbdev // order, in which edges are created/destroyed, is important 201*51c0b2f7Stbbdev make_edge(bn1, input_port<0>(jn)); 202*51c0b2f7Stbbdev make_edge(bn2, input_port<0>(jn)); 203*51c0b2f7Stbbdev make_edge(bn3, input_port<0>(jn)); 204*51c0b2f7Stbbdev 205*51c0b2f7Stbbdev remove_edge(bn3, input_port<0>(jn)); 206*51c0b2f7Stbbdev make_edge (bn3, input_port<2>(jn)); 207*51c0b2f7Stbbdev 208*51c0b2f7Stbbdev remove_edge(bn2, input_port<0>(jn)); 209*51c0b2f7Stbbdev make_edge (bn2, input_port<1>(jn)); 210*51c0b2f7Stbbdev } 211*51c0b2f7Stbbdev 212*51c0b2f7Stbbdev std::unique_ptr<join_node_t> connect_join_via_make_edge( 213*51c0b2f7Stbbdev graph& g, buffer_node<continue_msg>& bn1, buffer_node<continue_msg>& bn2, 214*51c0b2f7Stbbdev buffer_node<continue_msg>& bn3, queue_node_t& qn) 215*51c0b2f7Stbbdev { 216*51c0b2f7Stbbdev std::unique_ptr<join_node_t> jn( new join_node_t(g) ); 217*51c0b2f7Stbbdev twist_join_connections( bn1, bn2, bn3, *jn ); 218*51c0b2f7Stbbdev make_edge(*jn, qn); 219*51c0b2f7Stbbdev return jn; 220*51c0b2f7Stbbdev } 221*51c0b2f7Stbbdev 222*51c0b2f7Stbbdev #if TBB_PREVIEW_FLOW_GRAPH_FEATURES 223*51c0b2f7Stbbdev std::unique_ptr<join_node_t> connect_join_via_follows( 224*51c0b2f7Stbbdev graph&, buffer_node<continue_msg>& bn1, buffer_node<continue_msg>& bn2, 225*51c0b2f7Stbbdev buffer_node<continue_msg>& bn3, queue_node_t& qn) 226*51c0b2f7Stbbdev { 227*51c0b2f7Stbbdev auto bn_set = make_node_set(bn1, bn2, bn3); 228*51c0b2f7Stbbdev std::unique_ptr<join_node_t> jn( new join_node_t(follows(bn_set)) ); 229*51c0b2f7Stbbdev make_edge(*jn, qn); 230*51c0b2f7Stbbdev return jn; 231*51c0b2f7Stbbdev } 232*51c0b2f7Stbbdev 233*51c0b2f7Stbbdev std::unique_ptr<join_node_t> connect_join_via_precedes( 234*51c0b2f7Stbbdev graph&, buffer_node<continue_msg>& bn1, buffer_node<continue_msg>& bn2, 235*51c0b2f7Stbbdev buffer_node<continue_msg>& bn3, queue_node_t& qn) 236*51c0b2f7Stbbdev { 237*51c0b2f7Stbbdev auto qn_set = make_node_set(qn); 238*51c0b2f7Stbbdev auto qn_copy_set = qn_set; 239*51c0b2f7Stbbdev std::unique_ptr<join_node_t> jn( new join_node_t(precedes(qn_copy_set)) ); 240*51c0b2f7Stbbdev twist_join_connections( bn1, bn2, bn3, *jn ); 241*51c0b2f7Stbbdev return jn; 242*51c0b2f7Stbbdev } 243*51c0b2f7Stbbdev #endif // TBB_PREVIEW_FLOW_GRAPH_FEATURES 244*51c0b2f7Stbbdev 245*51c0b2f7Stbbdev void run_and_check( 246*51c0b2f7Stbbdev graph& g, buffer_node<continue_msg>& bn1, buffer_node<continue_msg>& bn2, 247*51c0b2f7Stbbdev buffer_node<continue_msg>& bn3, queue_node_t& qn, bool expected) 248*51c0b2f7Stbbdev { 249*51c0b2f7Stbbdev std::tuple<continue_msg, continue_msg, continue_msg> msg; 250*51c0b2f7Stbbdev 251*51c0b2f7Stbbdev bn1.try_put(continue_msg()); 252*51c0b2f7Stbbdev bn2.try_put(continue_msg()); 253*51c0b2f7Stbbdev bn3.try_put(continue_msg()); 254*51c0b2f7Stbbdev g.wait_for_all(); 255*51c0b2f7Stbbdev 256*51c0b2f7Stbbdev CHECK_MESSAGE( 257*51c0b2f7Stbbdev (qn.try_get(msg) == expected), 258*51c0b2f7Stbbdev "Unexpected message absence/existence at the end of the graph." 259*51c0b2f7Stbbdev ); 260*51c0b2f7Stbbdev } 261*51c0b2f7Stbbdev 262*51c0b2f7Stbbdev template<typename ConnectJoinNodeFunc> 263*51c0b2f7Stbbdev void test(ConnectJoinNodeFunc&& connect_join_node) { 264*51c0b2f7Stbbdev graph g; 265*51c0b2f7Stbbdev buffer_node<continue_msg> bn1(g); 266*51c0b2f7Stbbdev buffer_node<continue_msg> bn2(g); 267*51c0b2f7Stbbdev buffer_node<continue_msg> bn3(g); 268*51c0b2f7Stbbdev queue_node_t qn(g); 269*51c0b2f7Stbbdev 270*51c0b2f7Stbbdev auto jn = connect_join_node(g, bn1, bn2, bn3, qn); 271*51c0b2f7Stbbdev 272*51c0b2f7Stbbdev run_and_check(g, bn1, bn2, bn3, qn, /*expected=*/true); 273*51c0b2f7Stbbdev 274*51c0b2f7Stbbdev remove_edge(bn3, input_port<2>(*jn)); 275*51c0b2f7Stbbdev remove_edge(bn2, input_port<1>(*jn)); 276*51c0b2f7Stbbdev remove_edge(bn1, input_port<0>(*jn)); 277*51c0b2f7Stbbdev remove_edge(*jn, qn); 278*51c0b2f7Stbbdev 279*51c0b2f7Stbbdev run_and_check(g, bn1, bn2, bn3, qn, /*expected=*/false); 280*51c0b2f7Stbbdev } 281*51c0b2f7Stbbdev } // namespace multiple_predecessors 282*51c0b2f7Stbbdev 283*51c0b2f7Stbbdev 284*51c0b2f7Stbbdev #if __TBB_PREVIEW_FLOW_GRAPH_NODE_SET 285*51c0b2f7Stbbdev //! Test follows and precedes API 286*51c0b2f7Stbbdev //! \brief \ref error_guessing 287*51c0b2f7Stbbdev TEST_CASE("Test follows and preceedes API"){ 288*51c0b2f7Stbbdev test_follows_and_precedes_api(); 289*51c0b2f7Stbbdev } 290*51c0b2f7Stbbdev #endif 291*51c0b2f7Stbbdev 292*51c0b2f7Stbbdev #if __TBB_CPP17_DEDUCTION_GUIDES_PRESENT 293*51c0b2f7Stbbdev //! Test deduction guides 294*51c0b2f7Stbbdev //! \brief \ref requirement 295*51c0b2f7Stbbdev TEST_CASE("Deduction guides test"){ 296*51c0b2f7Stbbdev test_deduction_guides(); 297*51c0b2f7Stbbdev } 298*51c0b2f7Stbbdev #endif 299*51c0b2f7Stbbdev 300*51c0b2f7Stbbdev //! Test hash buffers behavior 301*51c0b2f7Stbbdev //! \brief \ref error_guessing 302*51c0b2f7Stbbdev TEST_CASE("Tagged buffers test"){ 303*51c0b2f7Stbbdev TestTaggedBuffers(); 304*51c0b2f7Stbbdev } 305*51c0b2f7Stbbdev 306*51c0b2f7Stbbdev //! Test with various policies and tuple sizes 307*51c0b2f7Stbbdev //! \brief \ref error_guessing 308*51c0b2f7Stbbdev TEST_CASE("Main test"){ 309*51c0b2f7Stbbdev test_main<tbb::flow::queueing>(); 310*51c0b2f7Stbbdev test_main<tbb::flow::reserving>(); 311*51c0b2f7Stbbdev test_main<tbb::flow::tag_matching>(); 312*51c0b2f7Stbbdev } 313*51c0b2f7Stbbdev 314*51c0b2f7Stbbdev //! Test with recirculating tags 315*51c0b2f7Stbbdev //! \brief \ref error_guessing 316*51c0b2f7Stbbdev TEST_CASE("Recirculation test"){ 317*51c0b2f7Stbbdev generate_recirc_test<std::tuple<int,float> >::do_test(); 318*51c0b2f7Stbbdev } 319*51c0b2f7Stbbdev 320*51c0b2f7Stbbdev //! Test maintaining correct count of ports without input 321*51c0b2f7Stbbdev //! \brief \ref error_guessing 322*51c0b2f7Stbbdev TEST_CASE("Test removal of the predecessor while having none") { 323*51c0b2f7Stbbdev using namespace multiple_predecessors; 324*51c0b2f7Stbbdev 325*51c0b2f7Stbbdev test(connect_join_via_make_edge); 326*51c0b2f7Stbbdev #if TBB_PREVIEW_FLOW_GRAPH_FEATURES 327*51c0b2f7Stbbdev test(connect_join_via_follows); 328*51c0b2f7Stbbdev test(connect_join_via_precedes); 329*51c0b2f7Stbbdev #endif 330*51c0b2f7Stbbdev } 331