navigation_test.cpp
Go to the documentation of this file.
3 #include <gtest/gtest.h>
4 
5 using namespace BT;
6 
7 // clang-format off
8 static const char* xml_text = R"(
9 
10 <root main_tree_to_execute="BehaviorTree">
11  <BehaviorTree ID="BehaviorTree">
12  <Fallback name="root">
13 
14  <ReactiveSequence name="navigation_subtree">
15  <Inverter>
16  <Condition ID="IsStuck"/>
17  </Inverter>
18  <SequenceStar name="navigate">
19  <Action ID="ComputePathToPose"/>
20  <Action ID="FollowPath"/>
21  </SequenceStar>
22  </ReactiveSequence>
23 
24  <SequenceStar name="stuck_recovery">
25  <Condition ID="IsStuck"/>
26  <Action ID="BackUpAndSpin"/>
27  </SequenceStar>
28 
29  </Fallback>
30  </BehaviorTree>
31 </root>
32  )";
33 
34 // clang-format on
35 
36 using Milliseconds = std::chrono::milliseconds;
37 
38 inline std::chrono::high_resolution_clock::time_point Now()
39 {
40  return std::chrono::high_resolution_clock::now();
41 }
42 
43 
44 
45 //--------------------------------------------
46 
47 class TestNode
48 {
49  public:
50  TestNode(const std::string& name):
51  _expected_result(true),
52  _tick_count(0),
53  _name(name)
54  { }
55 
56  void setExpectedResult(bool will_succeed)
57  {
58  _expected_result = will_succeed;
59  }
61  {
62  return _expected_result ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
63  }
64  void resetTickCount() { _tick_count = 0; }
65  int tickCount() const { return _tick_count;}
66 
68  {
69  std::cout << _name << ": "<< (_expected_result? "true" : "false") << std::endl;
70  _tick_count++;
71  return expectedResult();
72  }
73 
74  private:
77  std::string _name;
78 };
79 
80 class IsStuck: public ConditionNode, public TestNode
81 {
82  public:
83  IsStuck(const std::string& name): ConditionNode(name, {}), TestNode(name) {}
84 
85  NodeStatus tick() override
86  {
87  return tickImpl();
88  }
89 };
90 
91 class BackUpAndSpin: public SyncActionNode, public TestNode
92 {
93  public:
94  BackUpAndSpin(const std::string& name): SyncActionNode(name, {}), TestNode(name){}
95 
96  NodeStatus tick() override
97  {
98  return tickImpl();
99  }
100 };
101 
103 {
104  public:
105  ComputePathToPose(const std::string& name): SyncActionNode(name, {}), TestNode(name){}
106 
107  NodeStatus tick() override
108  {
109  return tickImpl();
110  }
111 };
112 
113 class FollowPath: public CoroActionNode, public TestNode
114 {
115  public:
116  FollowPath(const std::string& name): CoroActionNode(name, {}), TestNode(name), _halted(false){}
117 
118  NodeStatus tick() override
119  {
120  _halted = false;
121  std::cout << "FollowPath::started" << std::endl;
122  auto initial_time = Now();
123 
124  // Yield for 1 second
125  while( Now() < initial_time + Milliseconds(600) )
126  {
127  setStatusRunningAndYield();
128  }
129  return tickImpl();
130  }
131  void halt() override {
132  std::cout << "FollowPath::halt" << std::endl;
133  _halted = true;
135  }
136 
137  bool wasHalted() const { return _halted; }
138 
139  private:
140  bool _halted;
141 };
142 
143 //-------------------------------------
144 
145 template <typename Original, typename Casted>
146 void TryDynamicCastPtr(Original* ptr, Casted*& destination)
147 {
148  if( dynamic_cast<Casted*>(ptr) )
149  {
150  destination = dynamic_cast<Casted*>(ptr);
151  }
152 }
153 
154 /****************TESTS START HERE***************************/
155 
156 TEST(Navigationtest, MoveBaseRecovery)
157 {
158  BehaviorTreeFactory factory;
159 
160  factory.registerNodeType<IsStuck>("IsStuck");
161  factory.registerNodeType<BackUpAndSpin>("BackUpAndSpin");
162  factory.registerNodeType<ComputePathToPose>("ComputePathToPose");
163  factory.registerNodeType<FollowPath>("FollowPath");
164 
165  auto tree = factory.createTreeFromText(xml_text);
166 
167  // Need to retrieve the node pointers with dynamic cast
168  // In a normal application you would NEVER want to do such a thing.
169  IsStuck *first_stuck_node = nullptr;
170  IsStuck *second_stuck_node = nullptr;
171  BackUpAndSpin* back_spin_node = nullptr;
172  ComputePathToPose* compute_node = nullptr;
173  FollowPath* follow_node = nullptr;
174 
175  for (auto& node: tree.nodes)
176  {
177  auto ptr = node.get();
178 
179  if( !first_stuck_node )
180  {
181  TryDynamicCastPtr(ptr, first_stuck_node);
182  }
183  else{
184  TryDynamicCastPtr(ptr, second_stuck_node);
185  }
186  TryDynamicCastPtr(ptr, back_spin_node);
187  TryDynamicCastPtr(ptr, follow_node);
188  TryDynamicCastPtr(ptr, compute_node);
189  }
190 
191  ASSERT_TRUE( first_stuck_node );
192  ASSERT_TRUE( second_stuck_node );
193  ASSERT_TRUE( back_spin_node );
194  ASSERT_TRUE( compute_node );
195  ASSERT_TRUE( follow_node );
196 
197  std::cout << "-----------------------" << std::endl;
198 
199  // First case: not stuck, everything fine.
200  NodeStatus status = NodeStatus::IDLE;
201 
202  first_stuck_node->setExpectedResult(false);
203 
204  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
205  {
206  status = tree.root_node->executeTick();
207  std::this_thread::sleep_for(Milliseconds(100));
208  }
209 
210  // SUCCESS expected
211  ASSERT_EQ( status, NodeStatus::SUCCESS );
212  // IsStuck on the left branch must run several times
213  ASSERT_GE( first_stuck_node->tickCount(), 6);
214  // Never take the right branch (recovery)
215  ASSERT_EQ( second_stuck_node->tickCount(), 0 );
216  ASSERT_EQ( back_spin_node->tickCount(), 0 );
217 
218  ASSERT_EQ( compute_node->tickCount(), 1 );
219  ASSERT_EQ( follow_node->tickCount(), 1 );
220  ASSERT_FALSE( follow_node->wasHalted() );
221 
222  std::cout << "-----------------------" << std::endl;
223 
224  // Second case: get stuck after a while
225 
226  // Initialize evrything first
227  first_stuck_node->resetTickCount();
228  second_stuck_node->resetTickCount();
229  compute_node->resetTickCount();
230  follow_node->resetTickCount();
231  back_spin_node->resetTickCount();
232  status = NodeStatus::IDLE;
233  int cycle = 0;
234 
235  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
236  {
237  // At the fifth cycle get stucked
238  if( ++cycle == 2 )
239  {
240  first_stuck_node->setExpectedResult(true);
241  second_stuck_node->setExpectedResult(true);
242  }
243  status = tree.root_node->executeTick();
244  std::this_thread::sleep_for(Milliseconds(100));
245  }
246 
247  // SUCCESS expected
248  ASSERT_EQ( status, NodeStatus::SUCCESS );
249 
250  // First IsStuck must run several times
251  ASSERT_GE( first_stuck_node->tickCount(), 2);
252  // Second IsStuck probably only once
253  ASSERT_EQ( second_stuck_node->tickCount(), 1 );
254  ASSERT_EQ( back_spin_node->tickCount(), 1 );
255 
256  // compute done once and follow started but halted
257  ASSERT_EQ( compute_node->tickCount(), 1 );
258 
259  ASSERT_EQ( follow_node->tickCount(), 0 ); // started but never completed
260  ASSERT_TRUE( follow_node->wasHalted() );
261 
262  ASSERT_EQ( compute_node->status(), NodeStatus::IDLE );
263  ASSERT_EQ( follow_node->status(), NodeStatus::IDLE );
264  ASSERT_EQ( back_spin_node->status(), NodeStatus::IDLE );
265 
266 
267  std::cout << "-----------------------" << std::endl;
268 
269  // Third case: execute again
270 
271  // Initialize everything first
272  first_stuck_node->resetTickCount();
273  second_stuck_node->resetTickCount();
274  compute_node->resetTickCount();
275  follow_node->resetTickCount();
276  back_spin_node->resetTickCount();
277  status = NodeStatus::IDLE;
278  first_stuck_node->setExpectedResult(false);
279  second_stuck_node->setExpectedResult(false);
280 
281  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
282  {
283  status = tree.root_node->executeTick();
284  std::this_thread::sleep_for(Milliseconds(100));
285  }
286 
287  // SUCCESS expected
288  ASSERT_EQ( status, NodeStatus::SUCCESS );
289 
290  ASSERT_GE( first_stuck_node->tickCount(), 6);
291  ASSERT_EQ( second_stuck_node->tickCount(), 0 );
292  ASSERT_EQ( back_spin_node->tickCount(), 0 );
293 
294  ASSERT_EQ( compute_node->status(), NodeStatus::IDLE );
295  ASSERT_EQ( follow_node->status(), NodeStatus::IDLE );
296  ASSERT_EQ( back_spin_node->status(), NodeStatus::IDLE );
297  ASSERT_FALSE( follow_node->wasHalted() );
298 
299 }
300 
301 
NodeStatus tick() override
Method to be implemented by the user.
BackUpAndSpin(const std::string &name)
void registerNodeType(const std::string &ID)
Definition: bt_factory.h:174
bool _expected_result
virtual NodeStatus executeTick() overridefinal
The method that should be used to invoke tick() and setStatus();.
ComputePathToPose(const std::string &name)
NodeStatus tick() override
Method to be implemented by the user.
void halt() override
NodeStatus tick() override
Method to be implemented by the user.
NodeStatus tickImpl()
The CoroActionNode class is an ideal candidate for asynchronous actions which need to communicate wit...
Definition: action_node.h:147
void setExpectedResult(bool will_succeed)
FollowPath(const std::string &name)
The SyncActionNode is an ActionNode that explicitly prevents the status RUNNING and doesn&#39;t require a...
Definition: action_node.h:52
void resetTickCount()
The BehaviorTreeFactory is used to create instances of a TreeNode at run-time.
Definition: bt_factory.h:98
std::string _name
void halt() override
Tree createTreeFromText(const std::string &text, Blackboard::Ptr blackboard=Blackboard::create())
Definition: bt_factory.cpp:170
bool wasHalted() const
NodeStatus tick() override
Method to be implemented by the user.
NodeStatus status() const
Definition: tree_node.cpp:56
TestNode(const std::string &name)
NodeStatus
Definition: basic_types.h:35
NodeStatus expectedResult() const
int tickCount() const
IsStuck(const std::string &name)


behaviortree_cpp
Author(s): Michele Colledanchise, Davide Faconti
autogenerated on Sat Jun 8 2019 18:04:05