gtest_factory.cpp
Go to the documentation of this file.
1 #include <gtest/gtest.h>
2 #include <filesystem>
3 #include <string>
4 #include <utility>
5 #include <vector>
7 #include "../sample_nodes/crossdoor_nodes.h"
8 #include "../sample_nodes/dummy_nodes.h"
9 
10 using namespace BT;
11 
12 // clang-format off
13 
14 static const char* xml_text = R"(
15 
16 <root BTCPP_format="4" >
17 
18  <BehaviorTree ID="MainTree">
19  <Fallback name="root_selector">
20 
21  <Sequence name="door_open_sequence">
22  <Action ID="IsDoorOpen" />
23  <Action ID="PassThroughDoor" />
24  </Sequence>
25 
26  <Sequence name="door_closed_sequence">
27  <Decorator ID="Inverter">
28  <Action ID="IsDoorOpen" />
29  </Decorator>
30  <Action ID="OpenDoor" />
31  <Action ID="PassThroughDoor" />
32  <Action ID="CloseDoor" />
33  </Sequence>
34 
35  <Action ID="PassThroughWindow" />
36 
37  </Fallback>
38  </BehaviorTree>
39 
40  <!-- TreeNodesModel is used only by the Graphic interface -->
41  <TreeNodesModel>
42  <Action ID="IsDoorOpen" />
43  <Action ID="PassThroughDoor" />
44  <Action ID="CloseDoor" />
45  <Action ID="OpenDoor" />
46  <Action ID="PassThroughWindow" />
47  </TreeNodesModel>
48 </root>
49  )";
50 
51 static const char* xml_text_subtree = R"(
52 
53 <root BTCPP_format="4" main_tree_to_execute="MainTree" >
54 
55  <BehaviorTree ID="MainTree">
56  <Sequence>
57  <Fallback>
58  <Inverter>
59  <IsDoorClosed/>
60  </Inverter>
61  <SubTree ID="DoorClosedSubtree"/>
62  </Fallback>
63  <PassThroughDoor/>
64  </Sequence>
65  </BehaviorTree>
66 
67  <BehaviorTree ID="DoorClosedSubtree">
68  <Fallback>
69  <OpenDoor/>
70  <RetryUntilSuccessful num_attempts="5">
71  <PickLock/>
72  </RetryUntilSuccessful>
73  <SmashDoor/>
74  </Fallback>
75  </BehaviorTree>
76 
77 </root> )";
78 
79 static const char* xml_text_subtree_part1 = R"(
80 
81 <root BTCPP_format="4">
82  <BehaviorTree ID="MainTree">
83  <Fallback name="root_selector">
84  <SubTree ID="DoorClosedSubtree" />
85  <Action ID="PassThroughWindow" />
86  </Fallback>
87  </BehaviorTree>
88 </root> )";
89 
90 static const char* xml_text_subtree_part2 = R"(
91 
92 <root BTCPP_format="4">
93  <BehaviorTree ID="DoorClosedSubtree">
94  <Sequence name="door_sequence">
95  <Decorator ID="Inverter">
96  <Action ID="IsDoorLocked" />
97  </Decorator>
98  <Action ID="OpenDoor" />
99  <Action ID="PassThroughDoor" />
100  <Action ID="CloseDoor" />
101  </Sequence>
102  </BehaviorTree>
103 </root> )";
104 
105 // clang-format on
106 
107 TEST(BehaviorTreeFactory, NotRegisteredNode)
108 {
109  BehaviorTreeFactory factory;
110  ASSERT_ANY_THROW(factory.createTreeFromText(xml_text));
111  ASSERT_ANY_THROW(std::make_shared<BT::Tree>(factory.createTreeFromText(xml_text)));
112 }
113 
114 TEST(BehaviorTreeFactory, XMLParsingOrder)
115 {
116  BehaviorTreeFactory factory;
117  CrossDoor cross_door;
118  cross_door.registerNodes(factory);
119 
120  {
121  XMLParser parser(factory);
123  auto trees = parser.registeredBehaviorTrees();
124  ASSERT_EQ(trees[0], "DoorClosedSubtree");
125  ASSERT_EQ(trees[1], "MainTree");
126  }
127  {
128  XMLParser parser(factory);
131  auto trees = parser.registeredBehaviorTrees();
132  ASSERT_EQ(trees[0], "DoorClosedSubtree");
133  ASSERT_EQ(trees[1], "MainTree");
134  }
135  {
136  XMLParser parser(factory);
139  auto trees = parser.registeredBehaviorTrees();
140  ASSERT_EQ(trees[0], "DoorClosedSubtree");
141  ASSERT_EQ(trees[1], "MainTree");
142  }
143 }
144 
146 {
147  BehaviorTreeFactory factory;
148  CrossDoor cross_door;
149  cross_door.registerNodes(factory);
150 
151  Tree tree = factory.createTreeFromText(xml_text_subtree);
152 
154 
155  ASSERT_EQ(tree.subtrees.size(), 2);
156 
157  auto const& main_tree = tree.subtrees[0];
158  auto const& subtree = tree.subtrees[1];
159 
160  ASSERT_EQ(main_tree->nodes.size(), 6);
161  ASSERT_EQ(main_tree->nodes[0]->name(), "Sequence");
162  ASSERT_EQ(main_tree->nodes[1]->name(), "Fallback");
163  ASSERT_EQ(main_tree->nodes[2]->name(), "Inverter");
164  ASSERT_EQ(main_tree->nodes[3]->name(), "IsDoorClosed");
165  ASSERT_EQ(main_tree->nodes[4]->type(), NodeType::SUBTREE);
166  ASSERT_EQ(main_tree->nodes[5]->name(), "PassThroughDoor");
167 
168  ASSERT_EQ(subtree->nodes.size(), 5);
169  ASSERT_EQ(subtree->nodes[0]->name(), "Fallback");
170  ASSERT_EQ(subtree->nodes[1]->name(), "OpenDoor");
171  ASSERT_EQ(subtree->nodes[2]->name(), "RetryUntilSuccessful");
172  ASSERT_EQ(subtree->nodes[3]->name(), "PickLock");
173  ASSERT_EQ(subtree->nodes[4]->name(), "SmashDoor");
174 }
175 
177 {
178  const std::string xml_text_issue = R"(
179 <root BTCPP_format="4">
180  <BehaviorTree ID="ReceiveGuest">
181  </BehaviorTree>
182 </root> )";
183 
184  BehaviorTreeFactory factory;
185  XMLParser parser(factory);
186 
187  EXPECT_THROW(parser.loadFromText(xml_text_issue), RuntimeError);
188 }
189 
190 // clang-format off
191 
192 static const char* xml_ports_subtree = R"(
193 
194 <root BTCPP_format="4" main_tree_to_execute="MainTree">
195 
196  <BehaviorTree ID="TalkToMe">
197  <Sequence>
198  <SaySomething message="{hello_msg}" />
199  <SaySomething message="{bye_msg}" />
200  <Script code=" output:='done!' " />
201  </Sequence>
202  </BehaviorTree>
203 
204  <BehaviorTree ID="MainTree">
205  <Sequence>
206  <Script code = " talk_hello:='hello' " />
207  <Script code = " talk_bye:='bye bye' " />
208  <SubTree ID="TalkToMe" hello_msg="{talk_hello}"
209  bye_msg="{talk_bye}"
210  output="{talk_out}" />
211  <SaySomething message="{talk_out}" />
212  </Sequence>
213  </BehaviorTree>
214 
215 </root> )";
216 
217 // clang-format on
218 
219 TEST(BehaviorTreeFactory, SubTreeWithRemapping)
220 {
221  BehaviorTreeFactory factory;
222  factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
223 
224  Tree tree = factory.createTreeFromText(xml_ports_subtree);
225 
226  auto main_bb = tree.subtrees.at(0)->blackboard;
227  auto talk_bb = tree.subtrees.at(1)->blackboard;
228 
229  std::cout << "\n --------------------------------- \n" << std::endl;
230  main_bb->debugMessage();
231  std::cout << "\n ----- \n" << std::endl;
232  talk_bb->debugMessage();
233  std::cout << "\n --------------------------------- \n" << std::endl;
234 
235  // Should not throw
236  tree.tickWhileRunning();
237 
238  ASSERT_EQ(main_bb->entryInfo("talk_hello")->type(), typeid(std::string));
239  ASSERT_EQ(main_bb->entryInfo("talk_bye")->type(), typeid(std::string));
240  ASSERT_EQ(main_bb->entryInfo("talk_out")->type(), typeid(std::string));
241 
242  ASSERT_EQ(talk_bb->entryInfo("bye_msg")->type(), typeid(std::string));
243  ASSERT_EQ(talk_bb->entryInfo("hello_msg")->type(), typeid(std::string));
244 
245  std::cout << "\n --------------------------------- \n" << std::endl;
246  main_bb->debugMessage();
247  std::cout << "\n ----- \n" << std::endl;
248  talk_bb->debugMessage();
249  std::cout << "\n --------------------------------- \n" << std::endl;
250 
251  ASSERT_EQ(main_bb->entryInfo("talk_hello")->type(), typeid(std::string));
252  ASSERT_EQ(main_bb->entryInfo("talk_bye")->type(), typeid(std::string));
253  ASSERT_EQ(main_bb->entryInfo("talk_out")->type(), typeid(std::string));
254 
255  ASSERT_EQ(talk_bb->entryInfo("bye_msg")->type(), typeid(std::string));
256  ASSERT_EQ(talk_bb->entryInfo("hello_msg")->type(), typeid(std::string));
257  ASSERT_EQ(talk_bb->entryInfo("output")->type(), typeid(std::string));
258 
259  ASSERT_EQ(main_bb->get<std::string>("talk_hello"), "hello");
260  ASSERT_EQ(main_bb->get<std::string>("talk_bye"), "bye bye");
261  ASSERT_EQ(main_bb->get<std::string>("talk_out"), "done!");
262 
263  // these ports should not be present in the subtree TalkToMe
264  ASSERT_FALSE(talk_bb->getAnyLocked("talk_hello"));
265  ASSERT_FALSE(talk_bb->getAnyLocked("talk_bye"));
266  ASSERT_FALSE(talk_bb->getAnyLocked("talk_out"));
267 }
268 
269 std::string FilePath(const std::filesystem::path& relative_path)
270 {
271  // clang-format off
272  static const std::filesystem::path search_paths[] = {
273  BT_TEST_FOLDER,
274  std::filesystem::current_path() / "tests"};
275  // clang-format on
276 
277  for(auto const& path : search_paths)
278  {
279  if(std::filesystem::exists(path / relative_path))
280  {
281  return (path / relative_path).string();
282  }
283  }
284  return {};
285 }
286 
287 TEST(BehaviorTreeFactory, CreateTreeFromFile)
288 {
289  BehaviorTreeFactory factory;
290 
291  // should not throw
292  std::string path = FilePath("trees/parent_no_include.xml");
293  Tree tree = factory.createTreeFromFile(path);
294  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
295 }
296 
297 TEST(BehaviorTreeFactory, CreateTreeFromFileWhichIncludesFileFromSameDirectory)
298 {
299  BehaviorTreeFactory factory;
300 
301  // should not throw
302  std::string path = FilePath("trees/child/child_include_sibling.xml");
303  Tree tree = factory.createTreeFromFile(path);
304  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
305 }
306 
307 TEST(BehaviorTreeFactory, CreateTreeFromFileWhichIncludesFileFromChildDirectory)
308 {
309  BehaviorTreeFactory factory;
310 
311  // should not throw
312  std::string path = FilePath("trees/parent_include_child.xml");
313  Tree tree = factory.createTreeFromFile(path);
314  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
315 }
316 
319  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromSameDirectory)
320 {
321  BehaviorTreeFactory factory;
322 
323  // should not throw
324  std::string path = FilePath("trees/parent_include_child_include_sibling.xml");
325  Tree tree = factory.createTreeFromFile(path);
326  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
327 }
328 
331  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromChildDirectory)
332 {
333  BehaviorTreeFactory factory;
334 
335  // should not throw
336  std::string path = FilePath("trees/parent_include_child_include_child.xml");
337  Tree tree = factory.createTreeFromFile(path);
338  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
339 }
340 
343  CreateTreeFromFileWhichIncludesFileFromChildDirectoryWhichIncludesFileFromParentDirectory)
344 {
345  BehaviorTreeFactory factory;
346 
347  // should not throw
348  std::string path = FilePath("trees/parent_include_child_include_parent.xml");
349  Tree tree = factory.createTreeFromFile(path);
350  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
351 }
352 
353 TEST(BehaviorTreeFactory, WrongTreeName)
354 {
355  const char* xmlA = R"(
356  <root BTCPP_format="4" >
357  <BehaviorTree ID="MainTree">
358  <AlwaysSuccess/>
359  </BehaviorTree>
360  </root> )";
361 
362  BehaviorTreeFactory factory;
363 
364  factory.registerBehaviorTreeFromText(xmlA);
365  EXPECT_ANY_THROW(auto tree = factory.createTree("Wrong Name"));
366 }
367 
368 TEST(BehaviorTreeReload, ReloadSameTree)
369 {
370  const char* xmlA = R"(
371 <root BTCPP_format="4" >
372  <BehaviorTree ID="MainTree">
373  <AlwaysSuccess/>
374  </BehaviorTree>
375 </root> )";
376 
377  const char* xmlB = R"(
378 <root BTCPP_format="4" >
379  <BehaviorTree ID="MainTree">
380  <AlwaysFailure/>
381  </BehaviorTree>
382 </root> )";
383 
384  BehaviorTreeFactory factory;
385 
386  factory.registerBehaviorTreeFromText(xmlA);
387  {
388  auto tree = factory.createTree("MainTree");
389  ASSERT_EQ(NodeStatus::SUCCESS, tree.tickWhileRunning());
390  }
391 
392  factory.registerBehaviorTreeFromText(xmlB);
393  {
394  auto tree = factory.createTree("MainTree");
395  ASSERT_EQ(NodeStatus::FAILURE, tree.tickWhileRunning());
396  }
397 }
398 
400 {
401  return {
402  std::make_pair<std::string, std::string>("foo", "hello"),
403  std::make_pair<std::string, std::string>("bar", "42"),
404  };
405 }
406 
408 {
409 public:
410  ActionWithMetadata(const std::string& name, const NodeConfig& config)
411  : SyncActionNode(name, config)
412  {}
413 
414  BT::NodeStatus tick() override
415  {
416  return NodeStatus::SUCCESS;
417  }
418 
420  {
421  return {};
422  }
423 
425  {
426  return makeTestMetadata();
427  }
428 };
429 
430 TEST(BehaviorTreeFactory, ManifestMethod)
431 {
432  const char* expectedXML = R"(
433  <Action ID="ActionWithMetadata">
434  <MetadataFields>
435  <Metadata foo="hello"/>
436  <Metadata bar="42"/>
437  </MetadataFields>
438  </Action>)";
439 
440  BehaviorTreeFactory factory;
441  factory.registerNodeType<ActionWithMetadata>("ActionWithMetadata");
442  const auto& manifest = factory.manifests().at("ActionWithMetadata");
443  EXPECT_EQ(manifest.metadata, makeTestMetadata());
444 
445  auto xml = writeTreeNodesModelXML(factory, false);
446  std::cout << xml << std::endl;
447 
448  EXPECT_NE(xml.find(expectedXML), std::string::npos);
449 }
450 
451 TEST(BehaviorTreeFactory, addMetadataToManifest)
452 {
453  BehaviorTreeFactory factory;
454  factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
455  const auto& initial_manifest = factory.manifests().at("SaySomething");
456  EXPECT_TRUE(initial_manifest.metadata.empty());
457  factory.addMetadataToManifest("SaySomething", makeTestMetadata());
458  const auto& modified_manifest = factory.manifests().at("SaySomething");
459  EXPECT_EQ(modified_manifest.metadata, makeTestMetadata());
460 }
BT
Definition: ex01_wrap_legacy.cpp:29
xml_text_subtree
static const char * xml_text_subtree
Definition: gtest_factory.cpp:51
BT::XMLParser::loadFromText
void loadFromText(const std::string &xml_text, bool add_includes=true) override
Definition: xml_parsing.cpp:171
BT::BehaviorTreeFactory::createTree
Tree createTree(const std::string &tree_name, Blackboard::Ptr blackboard=Blackboard::create())
Definition: bt_factory.cpp:421
makeTestMetadata
KeyValueVector makeTestMetadata()
Definition: gtest_factory.cpp:399
BT::XMLParser::registeredBehaviorTrees
std::vector< std::string > registeredBehaviorTrees() const override
Definition: xml_parsing.cpp:181
BT::Tree
Struct used to store a tree. If this object goes out of scope, the tree is destroyed.
Definition: bt_factory.h:91
BT::NodeType::SUBTREE
@ SUBTREE
BT::BehaviorTreeFactory::addMetadataToManifest
void addMetadataToManifest(const std::string &node_id, const KeyValueVector &metadata)
Definition: bt_factory.cpp:429
xml_text_subtree_part2
static const char * xml_text_subtree_part2
Definition: gtest_factory.cpp:90
manifest
string manifest
xml_text_subtree_part1
static const char * xml_text_subtree_part1
Definition: gtest_factory.cpp:79
BT::BehaviorTreeFactory::manifests
const std::unordered_map< std::string, TreeNodeManifest > & manifests() const
Manifests of all the registered TreeNodes.
Definition: bt_factory.cpp:374
BT::Tree::tickWhileRunning
NodeStatus tickWhileRunning(std::chrono::milliseconds sleep_time=std::chrono::milliseconds(10))
Definition: bt_factory.cpp:586
BT::PortsList
std::unordered_map< std::string, PortInfo > PortsList
Definition: basic_types.h:587
BT::NodeStatus::FAILURE
@ FAILURE
BT::Tree::rootNode
TreeNode * rootNode() const
Definition: bt_factory.cpp:555
CrossDoor::registerNodes
void registerNodes(BT::BehaviorTreeFactory &factory)
Definition: crossdoor_nodes.cpp:56
BT::BehaviorTreeFactory::registerNodeType
void registerNodeType(const std::string &ID, const PortsList &ports, ExtraArgs... args)
Definition: bt_factory.h:326
BT::BehaviorTreeFactory::createTreeFromText
Tree createTreeFromText(const std::string &text, Blackboard::Ptr blackboard=Blackboard::create())
createTreeFromText will parse the XML directly from string. The XML needs to contain either a single ...
Definition: bt_factory.cpp:384
xml_ports_subtree
static const char * xml_ports_subtree
Definition: gtest_factory.cpp:192
ActionWithMetadata::ActionWithMetadata
ActionWithMetadata(const std::string &name, const NodeConfig &config)
Definition: gtest_factory.cpp:410
BT::RuntimeError
Definition: exceptions.h:58
xml_parsing.h
DummyNodes::SaySomething
Definition: dummy_nodes.h:47
BT::BehaviorTreeFactory
The BehaviorTreeFactory is used to create instances of a TreeNode at run-time.
Definition: bt_factory.h:209
BT::KeyValueVector
std::vector< std::pair< std::string, std::string > > KeyValueVector
Definition: basic_types.h:66
BT::BehaviorTreeFactory::createTreeFromFile
Tree createTreeFromFile(const std::filesystem::path &file_path, Blackboard::Ptr blackboard=Blackboard::create())
createTreeFromFile will parse the XML from a given file. The XML needs to contain either a single <Be...
Definition: bt_factory.cpp:402
BT::NodeStatus::SUCCESS
@ SUCCESS
FilePath
std::string FilePath(const std::filesystem::path &relative_path)
Definition: gtest_factory.cpp:269
BT::Tree::subtrees
std::vector< Subtree::Ptr > subtrees
Definition: bt_factory.h:104
TEST
TEST(BehaviorTreeFactory, NotRegisteredNode)
Definition: gtest_factory.cpp:107
BT::printTreeRecursively
void printTreeRecursively(const TreeNode *root_node, std::ostream &stream=std::cout)
Definition: behavior_tree.cpp:66
ActionWithMetadata::tick
BT::NodeStatus tick() override
Method to be implemented by the user.
Definition: gtest_factory.cpp:414
xml_text
static const char * xml_text
Definition: gtest_factory.cpp:14
CrossDoor
Definition: crossdoor_nodes.h:5
BT::BehaviorTreeFactory::registerBehaviorTreeFromText
void registerBehaviorTreeFromText(const std::string &xml_text)
Definition: bt_factory.cpp:266
ActionWithMetadata::providedPorts
static PortsList providedPorts()
Definition: gtest_factory.cpp:419
ActionWithMetadata::metadata
static KeyValueVector metadata()
Definition: gtest_factory.cpp:424
BT::XMLParser
The XMLParser is a class used to read the model of a BehaviorTree from file or text and instantiate t...
Definition: xml_parsing.h:16
BT::NodeConfig
Definition: tree_node.h:82
ActionWithMetadata
Definition: gtest_factory.cpp:407
BT::SyncActionNode
The SyncActionNode is an ActionNode that explicitly prevents the status RUNNING and doesn't require a...
Definition: action_node.h:52
BT::NodeStatus
NodeStatus
Definition: basic_types.h:33
BT::writeTreeNodesModelXML
std::string writeTreeNodesModelXML(const BehaviorTreeFactory &factory, bool include_builtin=false)
writeTreeNodesModelXML generates an XMl that contains the manifests in the <TreeNodesModel>
Definition: xml_parsing.cpp:1234


behaviortree_cpp_v4
Author(s): Davide Faconti
autogenerated on Wed Apr 16 2025 02:20:56