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 ActionNodeBase, public TestNode
114 {
115  std::chrono::high_resolution_clock::time_point _initial_time;
116  public:
117  FollowPath(const std::string& name): ActionNodeBase(name, {}), TestNode(name), _halted(false){}
118 
119  NodeStatus tick() override
120  {
121  if( status() == NodeStatus::IDLE )
122  {
123  setStatus(NodeStatus::RUNNING);
124  _halted = false;
125  std::cout << "FollowPath::started" << std::endl;
126  _initial_time = Now();
127  }
128 
129  // Yield for 1 second
130  while( Now() < _initial_time + Milliseconds(600) || _halted )
131  {
132  return NodeStatus::RUNNING;
133  }
134  if( _halted )
135  {
136  return NodeStatus::IDLE;
137  }
138  return tickImpl();
139  }
140  void halt() override {
141  std::cout << "FollowPath::halt" << std::endl;
142  _halted = true;
143  }
144 
145  bool wasHalted() const { return _halted; }
146 
147  private:
148  bool _halted;
149 };
150 
151 //-------------------------------------
152 
153 template <typename Original, typename Casted>
154 void TryDynamicCastPtr(Original* ptr, Casted*& destination)
155 {
156  if( dynamic_cast<Casted*>(ptr) )
157  {
158  destination = dynamic_cast<Casted*>(ptr);
159  }
160 }
161 
162 /****************TESTS START HERE***************************/
163 
164 TEST(Navigationtest, MoveBaseRecovery)
165 {
166  BehaviorTreeFactory factory;
167 
168  factory.registerNodeType<IsStuck>("IsStuck");
169  factory.registerNodeType<BackUpAndSpin>("BackUpAndSpin");
170  factory.registerNodeType<ComputePathToPose>("ComputePathToPose");
171  factory.registerNodeType<FollowPath>("FollowPath");
172 
173  auto tree = factory.createTreeFromText(xml_text);
174 
175  // Need to retrieve the node pointers with dynamic cast
176  // In a normal application you would NEVER want to do such a thing.
177  IsStuck *first_stuck_node = nullptr;
178  IsStuck *second_stuck_node = nullptr;
179  BackUpAndSpin* back_spin_node = nullptr;
180  ComputePathToPose* compute_node = nullptr;
181  FollowPath* follow_node = nullptr;
182 
183  for (auto& node: tree.nodes)
184  {
185  auto ptr = node.get();
186 
187  if( !first_stuck_node )
188  {
189  TryDynamicCastPtr(ptr, first_stuck_node);
190  }
191  else{
192  TryDynamicCastPtr(ptr, second_stuck_node);
193  }
194  TryDynamicCastPtr(ptr, back_spin_node);
195  TryDynamicCastPtr(ptr, follow_node);
196  TryDynamicCastPtr(ptr, compute_node);
197  }
198 
199  ASSERT_TRUE( first_stuck_node );
200  ASSERT_TRUE( second_stuck_node );
201  ASSERT_TRUE( back_spin_node );
202  ASSERT_TRUE( compute_node );
203  ASSERT_TRUE( follow_node );
204 
205  std::cout << "-----------------------" << std::endl;
206 
207  // First case: not stuck, everything fine.
208  NodeStatus status = NodeStatus::IDLE;
209 
210  first_stuck_node->setExpectedResult(false);
211 
212  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
213  {
214  status = tree.tickRoot();
215  std::this_thread::sleep_for(Milliseconds(100));
216  }
217 
218  // SUCCESS expected
219  ASSERT_EQ( status, NodeStatus::SUCCESS );
220  // IsStuck on the left branch must run several times
221  ASSERT_GE( first_stuck_node->tickCount(), 6);
222  // Never take the right branch (recovery)
223  ASSERT_EQ( second_stuck_node->tickCount(), 0 );
224  ASSERT_EQ( back_spin_node->tickCount(), 0 );
225 
226  ASSERT_EQ( compute_node->tickCount(), 1 );
227  ASSERT_EQ( follow_node->tickCount(), 1 );
228  ASSERT_FALSE( follow_node->wasHalted() );
229 
230  std::cout << "-----------------------" << std::endl;
231 
232  // Second case: get stuck after a while
233 
234  // Initialize evrything first
235  first_stuck_node->resetTickCount();
236  second_stuck_node->resetTickCount();
237  compute_node->resetTickCount();
238  follow_node->resetTickCount();
239  back_spin_node->resetTickCount();
240  status = NodeStatus::IDLE;
241  int cycle = 0;
242 
243  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
244  {
245  // At the fifth cycle get stucked
246  if( ++cycle == 2 )
247  {
248  first_stuck_node->setExpectedResult(true);
249  second_stuck_node->setExpectedResult(true);
250  }
251  status = tree.tickRoot();
252  std::this_thread::sleep_for(Milliseconds(100));
253  }
254 
255  // SUCCESS expected
256  ASSERT_EQ( status, NodeStatus::SUCCESS );
257 
258  // First IsStuck must run several times
259  ASSERT_GE( first_stuck_node->tickCount(), 2);
260  // Second IsStuck probably only once
261  ASSERT_EQ( second_stuck_node->tickCount(), 1 );
262  ASSERT_EQ( back_spin_node->tickCount(), 1 );
263 
264  // compute done once and follow started but halted
265  ASSERT_EQ( compute_node->tickCount(), 1 );
266 
267  ASSERT_EQ( follow_node->tickCount(), 0 ); // started but never completed
268  ASSERT_TRUE( follow_node->wasHalted() );
269 
270  ASSERT_EQ( compute_node->status(), NodeStatus::IDLE );
271  ASSERT_EQ( follow_node->status(), NodeStatus::IDLE );
272  ASSERT_EQ( back_spin_node->status(), NodeStatus::IDLE );
273 
274 
275  std::cout << "-----------------------" << std::endl;
276 
277  // Third case: execute again
278 
279  // Initialize everything first
280  first_stuck_node->resetTickCount();
281  second_stuck_node->resetTickCount();
282  compute_node->resetTickCount();
283  follow_node->resetTickCount();
284  back_spin_node->resetTickCount();
285  status = NodeStatus::IDLE;
286  first_stuck_node->setExpectedResult(false);
287  second_stuck_node->setExpectedResult(false);
288 
289  while( status == NodeStatus::IDLE || status == NodeStatus::RUNNING )
290  {
291  status = tree.tickRoot();
292  std::this_thread::sleep_for(Milliseconds(100));
293  }
294 
295  // SUCCESS expected
296  ASSERT_EQ( status, NodeStatus::SUCCESS );
297 
298  ASSERT_GE( first_stuck_node->tickCount(), 6);
299  ASSERT_EQ( second_stuck_node->tickCount(), 0 );
300  ASSERT_EQ( back_spin_node->tickCount(), 0 );
301 
302  ASSERT_EQ( compute_node->status(), NodeStatus::IDLE );
303  ASSERT_EQ( follow_node->status(), NodeStatus::IDLE );
304  ASSERT_EQ( back_spin_node->status(), NodeStatus::IDLE );
305  ASSERT_FALSE( follow_node->wasHalted() );
306 
307 }
308 
309 
The ActionNodeBase is the base class to use to create any kind of action. A particular derived class ...
Definition: action_node.h:35
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:290
bool _expected_result
ComputePathToPose(const std::string &name)
NodeStatus tick() override
Method to be implemented by the user.
NodeStatus tick() override
Method to be implemented by the user.
NodeStatus tickImpl()
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:53
void resetTickCount()
The BehaviorTreeFactory is used to create instances of a TreeNode at run-time.
Definition: bt_factory.h:207
std::string _name
void halt() override
Tree createTreeFromText(const std::string &text, Blackboard::Ptr blackboard=Blackboard::create())
Definition: bt_factory.cpp:249
bool wasHalted() const
std::chrono::high_resolution_clock::time_point _initial_time
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)


behaviotree_cpp_v3
Author(s): Michele Colledanchise, Davide Faconti
autogenerated on Tue May 4 2021 02:56:25