gtest_factory.cpp
Go to the documentation of this file.
1 #include <gtest/gtest.h>
2 #include "action_test_node.h"
3 #include "condition_test_node.h"
5 #include "environment.h"
6 #include "../sample_nodes/crossdoor_nodes.h"
7 #include "../sample_nodes/dummy_nodes.h"
8 
9 using namespace BT;
10 
11 // clang-format off
12 
13 static const char* xml_text = R"(
14 
15 <root main_tree_to_execute = "MainTree" >
16 
17  <BehaviorTree ID="MainTree">
18  <Fallback name="root_selector">
19 
20  <Sequence name="door_open_sequence">
21  <Action ID="IsDoorOpen" />
22  <Action ID="PassThroughDoor" />
23  </Sequence>
24 
25  <Sequence name="door_closed_sequence">
26  <Decorator ID="Inverter">
27  <Action ID="IsDoorOpen" />
28  </Decorator>
29  <Action ID="OpenDoor" />
30  <Action ID="PassThroughDoor" />
31  <Action ID="CloseDoor" />
32  </Sequence>
33 
34  <Action ID="PassThroughWindow" />
35 
36  </Fallback>
37  </BehaviorTree>
38 
39  <!-- TreeNodesModel is used only by the Graphic interface -->
40  <TreeNodesModel>
41  <Action ID="IsDoorOpen" />
42  <Action ID="PassThroughDoor" />
43  <Action ID="CloseDoor" />
44  <Action ID="OpenDoor" />
45  <Action ID="PassThroughWindow" />
46  </TreeNodesModel>
47 </root>
48  )";
49 
50 static const char* xml_text_subtree = R"(
51 
52 <root main_tree_to_execute = "MainTree" >
53 
54  <BehaviorTree ID="CrossDoorSubtree">
55  <Sequence name="door_sequence">
56  <Decorator ID="Inverter">
57  <Action ID="IsDoorLocked" />
58  </Decorator>
59  <Action ID="OpenDoor" />
60  <Action ID="PassThroughDoor" />
61  <Action ID="CloseDoor" />
62  </Sequence>
63  </BehaviorTree>
64 
65  <!-- This tree will include the other one -->
66  <BehaviorTree ID="MainTree">
67  <Fallback name="root_selector">
68  <SubTree ID="CrossDoorSubtree" />
69  <Action ID="PassThroughWindow" />
70  </Fallback>
71  </BehaviorTree>
72 
73 </root> )";
74 
75 static const char* xml_text_subtree_part1 = R"(
76 
77 <root>
78  <BehaviorTree ID="MainTree">
79  <Fallback name="root_selector">
80  <SubTree ID="CrossDoorSubtree" />
81  <Action ID="PassThroughWindow" />
82  </Fallback>
83  </BehaviorTree>
84 </root> )";
85 
86 static const char* xml_text_subtree_part2 = R"(
87 
88 <root>
89  <BehaviorTree ID="CrossDoorSubtree">
90  <Sequence name="door_sequence">
91  <Decorator ID="Inverter">
92  <Action ID="IsDoorLocked" />
93  </Decorator>
94  <Action ID="OpenDoor" />
95  <Action ID="PassThroughDoor" />
96  <Action ID="CloseDoor" />
97  </Sequence>
98  </BehaviorTree>
99 </root> )";
100 
101 // clang-format on
102 
103 TEST(BehaviorTreeFactory, XMLParsingOrder)
104 {
105  BehaviorTreeFactory factory;
106  CrossDoor::RegisterNodes(factory);
107 
108  {
109  XMLParser parser(factory);
111  auto trees = parser.registeredBehaviorTrees();
112  ASSERT_EQ(trees.size(), 2);
113  ASSERT_EQ(trees[0], "CrossDoorSubtree");
114  ASSERT_EQ(trees[1], "MainTree");
115  }
116  {
117  XMLParser parser(factory);
120  auto trees = parser.registeredBehaviorTrees();
121  ASSERT_EQ(trees.size(), 2);
122  ASSERT_EQ(trees[0], "CrossDoorSubtree");
123  ASSERT_EQ(trees[1], "MainTree");
124  }
125  {
126  XMLParser parser(factory);
129  auto trees = parser.registeredBehaviorTrees();
130  ASSERT_EQ(trees.size(), 2);
131  ASSERT_EQ(trees[0], "CrossDoorSubtree");
132  ASSERT_EQ(trees[1], "MainTree");
133  }
134 }
135 
136 TEST(BehaviorTreeFactory, VerifyLargeTree)
137 {
138  BehaviorTreeFactory factory;
139  CrossDoor::RegisterNodes(factory);
140 
141  Tree tree = factory.createTreeFromText(xml_text);
142 
144 
145  ASSERT_EQ(tree.rootNode()->name(), "root_selector");
146 
147  auto fallback = dynamic_cast<const FallbackNode*>(tree.rootNode());
148  ASSERT_TRUE(fallback != nullptr);
149 
150  ASSERT_EQ(fallback->children().size(), 3);
151  ASSERT_EQ(fallback->child(0)->name(), "door_open_sequence");
152  ASSERT_EQ(fallback->child(1)->name(), "door_closed_sequence");
153  ASSERT_EQ(fallback->child(2)->name(), "PassThroughWindow");
154 
155  auto sequence_open = dynamic_cast<const SequenceNode*>(fallback->child(0));
156  ASSERT_TRUE(sequence_open != nullptr);
157 
158  ASSERT_EQ(sequence_open->children().size(), 2);
159  ASSERT_EQ(sequence_open->child(0)->name(), "IsDoorOpen");
160  ASSERT_EQ(sequence_open->child(1)->name(), "PassThroughDoor");
161 
162  auto sequence_closed = dynamic_cast<const SequenceNode*>(fallback->child(1));
163  ASSERT_TRUE(sequence_closed != nullptr);
164 
165  ASSERT_EQ(sequence_closed->children().size(), 4);
166  ASSERT_EQ(sequence_closed->child(0)->name(), "Inverter");
167  ASSERT_EQ(sequence_closed->child(1)->name(), "OpenDoor");
168  ASSERT_EQ(sequence_closed->child(2)->name(), "PassThroughDoor");
169  ASSERT_EQ(sequence_closed->child(3)->name(), "CloseDoor");
170 
171  auto decorator = dynamic_cast<const InverterNode*>(sequence_closed->child(0));
172  ASSERT_TRUE(decorator != nullptr);
173 
174  ASSERT_EQ(decorator->child()->name(), "IsDoorOpen");
175 }
176 
178 {
179  BehaviorTreeFactory factory;
180  CrossDoor::RegisterNodes(factory);
181 
182  Tree tree = factory.createTreeFromText(xml_text_subtree);
183 
185 
186  ASSERT_EQ(tree.rootNode()->name(), "root_selector");
187 
188  auto root_selector = dynamic_cast<const FallbackNode*>(tree.rootNode());
189  ASSERT_TRUE(root_selector != nullptr);
190  ASSERT_EQ(root_selector->children().size(), 2);
191  ASSERT_EQ(root_selector->child(0)->name(), "CrossDoorSubtree");
192  ASSERT_EQ(root_selector->child(1)->name(), "PassThroughWindow");
193 
194  auto subtree = dynamic_cast<const SubtreeNode*>(root_selector->child(0));
195  ASSERT_TRUE(subtree != nullptr);
196 
197  auto sequence = dynamic_cast<const SequenceNode*>(subtree->child());
198  ASSERT_TRUE(sequence != nullptr);
199 
200  ASSERT_EQ(sequence->children().size(), 4);
201  ASSERT_EQ(sequence->child(0)->name(), "Inverter");
202  ASSERT_EQ(sequence->child(1)->name(), "OpenDoor");
203  ASSERT_EQ(sequence->child(2)->name(), "PassThroughDoor");
204  ASSERT_EQ(sequence->child(3)->name(), "CloseDoor");
205 
206  auto decorator = dynamic_cast<const InverterNode*>(sequence->child(0));
207  ASSERT_TRUE(decorator != nullptr);
208 
209  ASSERT_EQ(decorator->child()->name(), "IsDoorLocked");
210 }
211 
213 {
214  const std::string xml_text_issue = R"(
215 <root>
216  <BehaviorTree ID="ReceiveGuest">
217  </BehaviorTree>
218 </root> )";
219 
220  BehaviorTreeFactory factory;
221  XMLParser parser(factory);
222 
223  // We expect that an incorrectly-constructed behavior tree will fail to load
224  EXPECT_THROW(parser.loadFromText(xml_text_issue), RuntimeError);
225 
226  // We expect that no behavior trees will be registered after we unsuccessfully attempt to load a single tree
227  auto trees = parser.registeredBehaviorTrees();
228  EXPECT_TRUE( trees.empty() );
229 }
230 
231 // clang-format off
232 
233 static const char* xml_ports_subtree = R"(
234 
235 <root main_tree_to_execute = "MainTree" >
236 
237  <BehaviorTree ID="TalkToMe">
238  <Sequence>
239  <SaySomething message="{hello_msg}" />
240  <SaySomething message="{bye_msg}" />
241  <SetBlackboard output_key="output" value="done!" />
242  </Sequence>
243  </BehaviorTree>
244 
245  <BehaviorTree ID="MainTree">
246  <Sequence>
247  <SetBlackboard output_key="talk_hello" value="hello" />
248  <SetBlackboard output_key="talk_bye" value="bye bye" />
249  <SubTree ID="TalkToMe" hello_msg="talk_hello"
250  bye_msg="talk_bye"
251  output="talk_out" />
252  <SaySomething message="{talk_out}" />
253  </Sequence>
254  </BehaviorTree>
255 
256 </root> )";
257 
258 // clang-format on
259 
260 TEST(BehaviorTreeFactory, SubTreeWithRemapping)
261 {
262  BehaviorTreeFactory factory;
263  factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
264 
265  Tree tree = factory.createTreeFromText(xml_ports_subtree);
266 
267  auto main_bb = tree.blackboard_stack.at(0);
268  auto talk_bb = tree.blackboard_stack.at(1);
269 
270  std::cout << "\n --------------------------------- \n" << std::endl;
271  main_bb->debugMessage();
272  std::cout << "\n ----- \n" << std::endl;
273  talk_bb->debugMessage();
274  std::cout << "\n --------------------------------- \n" << std::endl;
275 
276  ASSERT_EQ(main_bb->portInfo("talk_hello")->type(), &typeid(std::string));
277  ASSERT_EQ(main_bb->portInfo("talk_bye")->type(), &typeid(std::string));
278  ASSERT_EQ(main_bb->portInfo("talk_out")->type(), &typeid(std::string));
279 
280  ASSERT_EQ(talk_bb->portInfo("bye_msg")->type(), &typeid(std::string));
281  ASSERT_EQ(talk_bb->portInfo("hello_msg")->type(), &typeid(std::string));
282 
283  // Should not throw
284  tree.tickRoot();
285 
286  std::cout << "\n --------------------------------- \n" << std::endl;
287  main_bb->debugMessage();
288  std::cout << "\n ----- \n" << std::endl;
289  talk_bb->debugMessage();
290  std::cout << "\n --------------------------------- \n" << std::endl;
291 
292  ASSERT_EQ(main_bb->portInfo("talk_hello")->type(), &typeid(std::string));
293  ASSERT_EQ(main_bb->portInfo("talk_bye")->type(), &typeid(std::string));
294  ASSERT_EQ(main_bb->portInfo("talk_out")->type(), &typeid(std::string));
295 
296  ASSERT_EQ(talk_bb->portInfo("bye_msg")->type(), &typeid(std::string));
297  ASSERT_EQ(talk_bb->portInfo("hello_msg")->type(), &typeid(std::string));
298  ASSERT_EQ(talk_bb->portInfo("output")->type(), &typeid(std::string));
299 
300  ASSERT_EQ(main_bb->get<std::string>("talk_hello"), "hello");
301  ASSERT_EQ(main_bb->get<std::string>("talk_bye"), "bye bye");
302  ASSERT_EQ(main_bb->get<std::string>("talk_out"), "done!");
303 
304  // these ports should not be present in the subtree TalkToMe
305  ASSERT_FALSE(talk_bb->getAny("talk_hello"));
306  ASSERT_FALSE(talk_bb->getAny("talk_bye"));
307  ASSERT_FALSE(talk_bb->getAny("talk_out"));
308 }
309 
310 #if !defined(USING_ROS) && !defined(USING_ROS2)
311 TEST(BehaviorTreeFactory, CreateTreeFromFile)
312 {
313  BehaviorTreeFactory factory;
314 
315  // should not throw
316  auto path = (environment->executable_path.parent_path() / "trees/"
317  "parent_no_include.xml");
318  Tree tree = factory.createTreeFromFile(path.str());
319  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
320 }
321 
322 TEST(BehaviorTreeFactory, CreateTreeFromFileWhichIncludesFileFromSameDirectory)
323 {
324  BehaviorTreeFactory factory;
325 
326  // should not throw
327  auto path = (environment->executable_path.parent_path() / "trees/child/"
328  "child_include_sibling.xml");
329  Tree tree = factory.createTreeFromFile(path.str());
330  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
331 }
332 
333 TEST(BehaviorTreeFactory, CreateTreeFromFileWhichIncludesFileFromChildDirectory)
334 {
335  BehaviorTreeFactory factory;
336 
337  // should not throw
338  auto path = (environment->executable_path.parent_path() / "trees/"
339  "parent_include_child.xml");
340  Tree tree = factory.createTreeFromFile(path.str());
341  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
342 }
343 
346  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromSameDirectory)
347 {
348  BehaviorTreeFactory factory;
349 
350  // should not throw
351  auto path = (environment->executable_path.parent_path() / "trees/"
352  "parent_include_child_"
353  "include_sibling.xml");
354  Tree tree = factory.createTreeFromFile(path.str());
355  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
356 }
357 
360  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromChildDirectory)
361 {
362  BehaviorTreeFactory factory;
363 
364  // should not throw
365  auto path = (environment->executable_path.parent_path() / "trees/"
366  "parent_include_child_"
367  "include_child.xml");
368  Tree tree = factory.createTreeFromFile(path.str());
369  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
370 }
371 
374  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromParentDirectory)
375 {
376  BehaviorTreeFactory factory;
377 
378  // should not throw
379  auto path = (environment->executable_path.parent_path() / "trees/"
380  "parent_include_child_"
381  "include_parent.xml");
382  Tree tree = factory.createTreeFromFile(path.str());
383  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickRoot());
384 }
385 #endif
386 
387 TEST(BehaviorTreeFactory, DecoratorWithoutChildThrows)
388 {
389  BehaviorTreeFactory factory;
390  const std::string tree_xml = R"(
391 <root>
392  <BehaviorTree ID="Main">
393  <ForceSuccess>
394  </ForceSuccess>
395  </BehaviorTree>
396 </root>
397 )";
398 
399  ASSERT_THROW(factory.createTreeFromText(tree_xml), BehaviorTreeException);
400 }
401 
402 TEST(BehaviorTreeFactory, DecoratorWithTwoChildrenThrows)
403 {
404  BehaviorTreeFactory factory;
405  const std::string tree_xml = R"(
406 <root>
407  <BehaviorTree ID="Main">
408  <ForceSuccess>
409  <AlwaysSuccess />
410  <AlwaysSuccess />
411  </ForceSuccess>
412  </BehaviorTree>
413 </root>
414 )";
415 
416  ASSERT_THROW(factory.createTreeFromText(xml_text), BehaviorTreeException);
417 }
418 
419 
420 TEST(BehaviorTreeFactory, RegisterValidAndInvalidTrees)
421 {
422 const std::string xml_text_ok = R"(
423 <root>
424  <BehaviorTree ID="ValidTree">
425  <Sequence name="door_open_sequence">
426  <Action ID="AlwaysSuccess" />
427  </Sequence>
428  </BehaviorTree>
429 </root> )";
430 
431 const std::string xml_text_invalid = R"(
432 <root>
433  <BehaviorTree ID="InvalidTreeWithNoChildren">
434  </BehaviorTree>
435 </root> )";
436 
437  BehaviorTreeFactory factory;
438  XMLParser parser(factory);
439 
440  // GIVEN that a valid tree has been loaded
441  ASSERT_NO_THROW(parser.loadFromText(xml_text_ok));
442 
443  // WHEN we attempt to load an invalid tree
444  ASSERT_THROW(parser.loadFromText(xml_text_invalid), RuntimeError);
445 
446  // THEN the valid tree is still registered
447  auto trees = parser.registeredBehaviorTrees();
448  ASSERT_EQ(trees.size(), 1);
449  EXPECT_EQ(trees[0], "ValidTree");
450 }
451 
452 TEST(BehaviorTreeFactory, RegisterInvalidXMLBadActionNodeThrows)
453 {
454  // GIVEN an invalid tree
455  // This tree contains invalid XML because the action node is missing a trailing `/`.
456  // A valid line would read: <Action ID="AlwaysSuccess" />
457 const std::string xml_text_invalid = R"(
458 <root>
459  <BehaviorTree ID="InvalidTreeWithBadChild">
460  <Sequence name="seq">
461  <Action ID="AlwaysSuccess" >
462  </Sequence>
463  </BehaviorTree>
464 </root> )";
465 
466  BehaviorTreeFactory factory;
467  XMLParser parser(factory);
468 
469  // WHEN we attempt to load an invalid tree
470  // THEN a RuntimeError exception is thrown
471  EXPECT_THROW(parser.loadFromText(xml_text_invalid), RuntimeError);
472 
473  // THEN no tree is registered
474  auto trees = parser.registeredBehaviorTrees();
475  EXPECT_TRUE(trees.empty());
476 }
477 
478 TEST(BehaviorTreeFactory, RegisterInvalidXMLNoRootThrows)
479 {
480  // GIVEN an invalid tree
481  // This tree contains invalid XML because it does not have a root node
482 const std::string xml_text_invalid = R"(
483  <BehaviorTree ID="InvalidTreeNoRoot">
484  <Sequence name="seq">
485  <Action ID="AlwaysSuccess" />
486  </Sequence>
487  </BehaviorTree> )";
488 
489  BehaviorTreeFactory factory;
490  XMLParser parser(factory);
491 
492  // WHEN we attempt to load an invalid tree
493  // THEN a RuntimeError exception is thrown
494  EXPECT_THROW(parser.loadFromText(xml_text_invalid), RuntimeError);
495 
496  // THEN no tree is registered
497  auto trees = parser.registeredBehaviorTrees();
498  EXPECT_TRUE(trees.empty());
499 }
500 
501 TEST(BehaviorTreeFactory, ParserClearRegisteredBehaviorTrees)
502 {
503  const std::string tree_xml = R"(
504 <root>
505  <BehaviorTree ID="Main">
506  <AlwaysSuccess />
507  </BehaviorTree>
508 </root>
509 )";
510 
511  BehaviorTreeFactory factory;
512  XMLParser parser(factory);
513 
514  ASSERT_NO_THROW(parser.loadFromText(tree_xml));
515 
516  const auto trees = parser.registeredBehaviorTrees();
517  ASSERT_FALSE(trees.empty());
518 
519  parser.clearInternalState();
520 
521  const auto trees_after_clear = parser.registeredBehaviorTrees();
522  EXPECT_TRUE(trees_after_clear.empty());
523 }
524 
525 TEST(BehaviorTreeFactory, FactoryClearRegisteredBehaviorTrees)
526 {
527  BehaviorTreeFactory factory;
528  const std::string tree_xml = R"(
529 <root>
530  <BehaviorTree ID="Main">
531  <AlwaysSuccess />
532  </BehaviorTree>
533 </root>
534 )";
535 
536  ASSERT_NO_THROW(factory.registerBehaviorTreeFromText(tree_xml));
537 
538  const auto trees = factory.registeredBehaviorTrees();
539  ASSERT_FALSE(trees.empty());
540 
542 
543  const auto trees_after_clear = factory.registeredBehaviorTrees();
544  EXPECT_TRUE(trees_after_clear.empty());
545 }
void clearRegisteredBehaviorTrees()
Clear previously-registered behavior trees.
Definition: bt_factory.cpp:238
void registerNodeType(const std::string &ID)
Definition: bt_factory.h:364
static const char * xml_ports_subtree
std::vector< Blackboard::Ptr > blackboard_stack
Definition: bt_factory.h:129
static const char * xml_text_subtree_part2
static const char * xml_text_subtree
TreeNode * rootNode() const
Definition: bt_factory.h:181
void clearInternalState() override
void RegisterNodes(BT::BehaviorTreeFactory &factory)
Environment * environment
Definition: gtest_tree.cpp:122
void registerBehaviorTreeFromText(const std::string &xml_text)
Definition: bt_factory.cpp:228
const std::string & name() const
Name of the instance, not the type.
Definition: tree_node.cpp:101
The BehaviorTreeFactory is used to create instances of a TreeNode at run-time.
Definition: bt_factory.h:251
std::vector< std::string > registeredBehaviorTrees() const
Definition: bt_factory.cpp:233
NodeStatus tickRoot()
tickRoot send the tick signal to the root node. It will propagate through the entire tree...
Definition: bt_factory.h:210
Tree createTreeFromText(const std::string &text, Blackboard::Ptr blackboard=Blackboard::create())
Definition: bt_factory.cpp:278
void printTreeRecursively(const TreeNode *root_node, std::ostream &stream=std::cout)
void loadFromText(const std::string &xml_text, bool add_includes=true) override
The SubtreeNode is a way to wrap an entire Subtree, creating a separated BlackBoard. If you want to have data flow through ports, you need to explicitly remap the ports.
Definition: subtree_node.h:14
The InverterNode returns SUCCESS if child fails of FAILURE is child succeeds. RUNNING status is propa...
Definition: inverter_node.h:25
Struct used to store a tree. If this object goes out of scope, the tree is destroyed.
Definition: bt_factory.h:125
Simple class for manipulating paths on Linux/Windows/Mac OS.
Definition: path.h:42
std::vector< std::string > registeredBehaviorTrees() const override
std::string str(path_type type=native_path) const
Definition: path.h:185
The XMLParser is a class used to read the model of a BehaviorTree from file or text and instantiate t...
Definition: xml_parsing.h:15
static const char * xml_text_subtree_part1
The FallbackNode is used to try different strategies, until one succeeds. If any child returns RUNNIN...
Definition: fallback_node.h:32
Tree createTreeFromFile(const std::string &file_path, Blackboard::Ptr blackboard=Blackboard::create())
Definition: bt_factory.cpp:295
static const char * xml_text
The SequenceNode is used to tick children in an ordered sequence. If any child returns RUNNING...
Definition: sequence_node.h:33
TEST(BehaviorTreeFactory, XMLParsingOrder)


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