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


behaviortree_cpp_v3
Author(s): Michele Colledanchise, Davide Faconti
autogenerated on Mon Jul 3 2023 02:50:14