Skip to content

Commit c852132

Browse files
authored
Merge pull request #16 from PickNikRobotics/add-id-to-validation
Refactor VerifyXML to clarify logic
2 parents 4ede94d + ceadf1a commit c852132

File tree

4 files changed

+64
-87
lines changed

4 files changed

+64
-87
lines changed

package.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
<depend>libzmq3-dev</depend>
2525

2626
<test_depend >ament_cmake_gtest</test_depend>
27+
<test_depend >ament_cmake_gmock</test_depend>
2728

2829
<export>
2930
<build_type >ament_cmake</build_type>

src/xml_parsing.cpp

Lines changed: 53 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -429,119 +429,76 @@ void VerifyXML(const std::string& xml_text,
429429
const std::string ID = node->Attribute("ID") ? node->Attribute("ID") : "";
430430
const int line_number = node->GetLineNum();
431431

432-
if(name == "Decorator")
432+
// Precondition: built-in XML element types must define attribute [ID]
433+
const bool is_builtin =
434+
(name == "Decorator" || name == "Action" || name == "Condition" ||
435+
name == "Control" || name == "SubTree");
436+
if(is_builtin && ID.empty())
433437
{
434-
if(children_count != 1)
435-
{
436-
ThrowError(line_number, "The tag <Decorator> must have exactly 1 "
437-
"child");
438-
}
439-
if(ID.empty())
440-
{
441-
ThrowError(line_number, "The tag <Decorator> must have the "
442-
"attribute [ID]");
443-
}
444-
}
445-
else if(name == "Action")
446-
{
447-
if(children_count != 0)
448-
{
449-
ThrowError(line_number, "The tag <Action> must not have any "
450-
"child");
451-
}
452-
if(ID.empty())
453-
{
454-
ThrowError(line_number, "The tag <Action> must have the "
455-
"attribute [ID]");
456-
}
438+
ThrowError(line_number,
439+
std::string("The tag <") + name + "> must have the attribute [ID]");
457440
}
458-
else if(name == "Condition")
441+
442+
if(name == "BehaviorTree")
459443
{
460-
if(children_count != 0)
461-
{
462-
ThrowError(line_number, "The tag <Condition> must not have any "
463-
"child");
464-
}
465-
if(ID.empty())
444+
if(ID.empty() && behavior_tree_count > 1)
466445
{
467-
ThrowError(line_number, "The tag <Condition> must have the "
468-
"attribute [ID]");
446+
ThrowError(line_number, "The tag <BehaviorTree> must have the attribute [ID]");
469447
}
470-
}
471-
else if(name == "Control")
472-
{
473-
if(children_count == 0)
448+
if(registered_nodes.count(ID) != 0)
474449
{
475-
ThrowError(line_number, "The tag <Control> must have at least 1 "
476-
"child");
450+
ThrowError(line_number, "The attribute [ID] of tag <BehaviorTree> must not use "
451+
"the name of a registered Node");
477452
}
478-
if(ID.empty())
453+
if(children_count != 1)
479454
{
480-
ThrowError(line_number, "The tag <Control> must have the "
481-
"attribute [ID]");
455+
ThrowError(line_number, "The tag <BehaviorTree> with ID '" + ID +
456+
"' must have exactly 1 child");
482457
}
483458
}
484459
else if(name == "SubTree")
485460
{
486461
if(children_count != 0)
487462
{
488-
ThrowError(line_number, "<SubTree> should not have any child");
489-
}
490-
if(ID.empty())
491-
{
492-
ThrowError(line_number, "The tag <SubTree> must have the "
493-
"attribute [ID]");
463+
ThrowError(line_number,
464+
"<SubTree> with ID '" + ID + "' should not have any child");
494465
}
495466
if(registered_nodes.count(ID) != 0)
496467
{
497-
ThrowError(line_number, "The attribute [ID] of tag <SubTree> must "
498-
"not use the name of a registered Node");
499-
}
500-
}
501-
else if(name == "BehaviorTree")
502-
{
503-
if(ID.empty() && behavior_tree_count > 1)
504-
{
505-
ThrowError(line_number, "The tag <BehaviorTree> must have the "
506-
"attribute [ID]");
507-
}
508-
if(children_count != 1)
509-
{
510-
ThrowError(line_number, "The tag <BehaviorTree> must have exactly 1 "
511-
"child");
512-
}
513-
if(registered_nodes.count(ID) != 0)
514-
{
515-
ThrowError(line_number, "The attribute [ID] of tag <BehaviorTree> "
516-
"must not use the name of a registered Node");
468+
ThrowError(line_number, "The attribute [ID] of tag <SubTree> must not use the "
469+
"name of a registered Node");
517470
}
518471
}
519472
else
520473
{
521-
// search in the factory and the list of subtrees
522-
const auto search = registered_nodes.find(name);
474+
// use ID for builtin node types, otherwise use the element name
475+
const auto lookup_name = is_builtin ? ID : name;
476+
const auto search = registered_nodes.find(lookup_name);
523477
bool found = (search != registered_nodes.end());
524478
if(!found)
525479
{
526-
ThrowError(line_number, std::string("Node not recognized: ") + name);
480+
ThrowError(line_number, std::string("Node not recognized: ") + lookup_name);
527481
}
528482

529-
if(search->second == NodeType::DECORATOR)
483+
const auto node_type = search->second;
484+
const std::string& registered_name = search->first;
485+
486+
if(node_type == NodeType::DECORATOR)
530487
{
531488
if(children_count != 1)
532489
{
533-
ThrowError(line_number,
534-
std::string("The node <") + name + "> must have exactly 1 child");
490+
ThrowError(line_number, std::string("The node '") + registered_name +
491+
"' must have exactly 1 child");
535492
}
536493
}
537-
else if(search->second == NodeType::CONTROL)
494+
else if(node_type == NodeType::CONTROL)
538495
{
539496
if(children_count == 0)
540497
{
541-
ThrowError(line_number,
542-
std::string("The node <") + name + "> must have 1 or more children");
498+
ThrowError(line_number, std::string("The node '") + registered_name +
499+
"' must have 1 or more children");
543500
}
544-
if(name == "ReactiveSequence")
501+
if(registered_name == "ReactiveSequence")
545502
{
546503
size_t async_count = 0;
547504
for(auto child = node->FirstChildElement(); child != nullptr;
@@ -563,13 +520,29 @@ void VerifyXML(const std::string& xml_text,
563520
++async_count;
564521
if(async_count > 1)
565522
{
566-
ThrowError(line_number, std::string("A ReactiveSequence cannot have more "
567-
"than one async child."));
523+
ThrowError(line_number, std::string("A ReactiveSequence cannot have "
524+
"more than one async child."));
568525
}
569526
}
570527
}
571528
}
572529
}
530+
else if(node_type == NodeType::ACTION)
531+
{
532+
if(children_count != 0)
533+
{
534+
ThrowError(line_number, std::string("The node '") + registered_name +
535+
"' must not have any child");
536+
}
537+
}
538+
else if(node_type == NodeType::CONDITION)
539+
{
540+
if(children_count != 0)
541+
{
542+
ThrowError(line_number, std::string("The node '") + registered_name +
543+
"' must not have any child");
544+
}
545+
}
573546
}
574547
//recursion
575548
for(auto child = node->FirstChildElement(); child != nullptr;

tests/CMakeLists.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ set(BT_TESTS
3737
if(ament_cmake_FOUND)
3838

3939
find_package(ament_cmake_gtest REQUIRED)
40+
find_package(ament_cmake_gmock REQUIRED)
4041

41-
ament_add_gtest(${BTCPP_LIBRARY}_test ${BT_TESTS})
42+
ament_add_gmock(${BTCPP_LIBRARY}_test ${BT_TESTS})
4243
target_link_libraries(${BTCPP_LIBRARY}_test
4344
${TEST_DEPENDECIES}
4445
${ament_LIBRARIES})
@@ -53,7 +54,9 @@ else()
5354

5455
target_link_libraries(behaviortree_cpp_test
5556
GTest::gtest
56-
GTest::gtest_main)
57+
GTest::gtest_main
58+
GTest::gmock
59+
)
5760

5861
endif()
5962

tests/gtest_factory.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
#include <gtest/gtest.h>
2+
#include <gmock/gmock.h>
23
#include <filesystem>
34
#include <string>
45
#include <utility>
@@ -83,7 +84,7 @@ static const char* xml_text_subtree_part1 = R"(
8384
<BehaviorTree ID="MainTree">
8485
<Fallback name="root_selector">
8586
<SubTree ID="DoorClosedSubtree" />
86-
<Action ID="PassThroughWindow" />
87+
<Action ID="PassThroughDoor" />
8788
</Fallback>
8889
</BehaviorTree>
8990
</root> )";
@@ -94,11 +95,10 @@ static const char* xml_text_subtree_part2 = R"(
9495
<BehaviorTree ID="DoorClosedSubtree">
9596
<Sequence name="door_sequence">
9697
<Decorator ID="Inverter">
97-
<Action ID="IsDoorLocked" />
98+
<Action ID="IsDoorClosed" />
9899
</Decorator>
99100
<Action ID="OpenDoor" />
100101
<Action ID="PassThroughDoor" />
101-
<Action ID="CloseDoor" />
102102
</Sequence>
103103
</BehaviorTree>
104104
</root> )";
@@ -259,7 +259,7 @@ TEST(BehaviorTreeFactory, SubtreeParsingError)
259259
catch(const BT::RuntimeError& e)
260260
{
261261
std::string error_msg = e.what();
262-
EXPECT_TRUE(error_msg.find("line 36") != std::string::npos);
262+
EXPECT_THAT(error_msg, ::testing::HasSubstr("line 11"));
263263
}
264264
try
265265
{
@@ -269,7 +269,7 @@ TEST(BehaviorTreeFactory, SubtreeParsingError)
269269
catch(const BT::RuntimeError& e)
270270
{
271271
std::string error_msg = e.what();
272-
EXPECT_TRUE(error_msg.find("line 7") != std::string::npos);
272+
EXPECT_THAT(error_msg, ::testing::HasSubstr("line 5"));
273273
}
274274
}
275275

0 commit comments

Comments
 (0)