Skip to content

Commit d31406b

Browse files
facontidavideclaude
andcommitted
Merge master into fix/930-mock-substitution-hang
Resolve conflict in tests/gtest_substitution.cpp: keep both the #934 SubTreeNodeSubstitution test (from master) and the #930 tests. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2 parents 9264196 + 25407ad commit d31406b

File tree

5 files changed

+70
-3
lines changed

5 files changed

+70
-3
lines changed

include/behaviortree_cpp/bt_factory.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ class BehaviorTreeFactory
224224
BehaviorTreeFactory(const BehaviorTreeFactory& other) = delete;
225225
BehaviorTreeFactory& operator=(const BehaviorTreeFactory& other) = delete;
226226

227-
BehaviorTreeFactory(BehaviorTreeFactory&& other) noexcept = default;
228-
BehaviorTreeFactory& operator=(BehaviorTreeFactory&& other) noexcept = default;
227+
BehaviorTreeFactory(BehaviorTreeFactory&& other) noexcept;
228+
BehaviorTreeFactory& operator=(BehaviorTreeFactory&& other) noexcept;
229229

230230
/// Remove a registered ID.
231231
bool unregisterBuilder(const std::string& ID);

src/bt_factory.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ BehaviorTreeFactory::BehaviorTreeFactory() : _p(new PImpl)
106106

107107
BehaviorTreeFactory::~BehaviorTreeFactory() = default;
108108

109+
BehaviorTreeFactory::BehaviorTreeFactory(BehaviorTreeFactory&& other) noexcept = default;
110+
BehaviorTreeFactory&
111+
BehaviorTreeFactory::operator=(BehaviorTreeFactory&& other) noexcept = default;
112+
109113
bool BehaviorTreeFactory::unregisterBuilder(const std::string& ID)
110114
{
111115
if(builtinNodes().count(ID) != 0)

src/xml_parsing.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -867,8 +867,13 @@ TreeNode::Ptr XMLParser::PImpl::createNodeFromXML(const XMLElement* element,
867867
config.input_ports = port_remap;
868868
new_node =
869869
factory->instantiateTreeNode(instance_name, toStr(NodeType::SUBTREE), config);
870+
// If a substitution rule replaced the SubTree with a different node
871+
// (e.g. a TestNode), the dynamic_cast will return nullptr.
870872
auto subtree_node = dynamic_cast<SubTreeNode*>(new_node.get());
871-
subtree_node->setSubtreeID(type_ID);
873+
if(subtree_node != nullptr)
874+
{
875+
subtree_node->setSubtreeID(type_ID);
876+
}
872877
}
873878
else
874879
{

tests/gtest_factory.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,28 @@ TEST(BehaviorTreeFactory, ManifestMethod)
450450
EXPECT_NE(xml.find(expectedXML), std::string::npos);
451451
}
452452

453+
TEST(BehaviorTreeFactory, ReturnByValue)
454+
{
455+
// Issue #937: BehaviorTreeFactory cannot be returned by value from
456+
// a function because the move constructor/assignment were defaulted
457+
// in the header where PImpl is an incomplete type.
458+
auto make_factory = []() -> BT::BehaviorTreeFactory {
459+
BT::BehaviorTreeFactory factory;
460+
factory.registerNodeType<DummyNodes::SaySomething>("SaySomething");
461+
return factory;
462+
};
463+
auto factory = make_factory();
464+
465+
// Verify the moved-into factory is fully functional
466+
const auto& manifests = factory.manifests();
467+
ASSERT_TRUE(manifests.count("SaySomething"));
468+
469+
// Also test move assignment
470+
BT::BehaviorTreeFactory factory2;
471+
factory2 = std::move(factory);
472+
ASSERT_TRUE(factory2.manifests().count("SaySomething"));
473+
}
474+
453475
TEST(BehaviorTreeFactory, addMetadataToManifest)
454476
{
455477
BehaviorTreeFactory factory;

tests/gtest_substitution.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,42 @@ TEST(Substitution, Parser)
5353
ASSERT_EQ(*std::get_if<std::string>(&rules.at("actionC")), "NotAConfig");
5454
}
5555

56+
// Regression test for issue #934: segfault when substituting a SubTree node
57+
TEST(Substitution, SubTreeNodeSubstitution)
58+
{
59+
static const char* parent_xml = R"(
60+
<root BTCPP_format="4">
61+
<BehaviorTree ID="Parent">
62+
<SubTree ID="Child" name="child" />
63+
</BehaviorTree>
64+
</root>
65+
)";
66+
67+
static const char* child_xml = R"(
68+
<root BTCPP_format="4">
69+
<BehaviorTree ID="Child">
70+
<AlwaysSuccess />
71+
</BehaviorTree>
72+
</root>
73+
)";
74+
75+
BehaviorTreeFactory factory;
76+
factory.registerBehaviorTreeFromText(parent_xml);
77+
factory.registerBehaviorTreeFromText(child_xml);
78+
79+
BT::TestNodeConfig config;
80+
config.return_status = BT::NodeStatus::SUCCESS;
81+
factory.addSubstitutionRule("child", config);
82+
83+
// This should not crash (was a segfault before the fix)
84+
Tree tree;
85+
ASSERT_NO_THROW(tree = factory.createTree("Parent"));
86+
87+
// The substituted tree should tick successfully
88+
auto status = tree.tickWhileRunning();
89+
ASSERT_EQ(status, BT::NodeStatus::SUCCESS);
90+
}
91+
5692
// Test for issue #930: Mock substitution with registerSimpleAction
5793
// should not hang when using string-based substitution from JSON.
5894
TEST(Substitution, StringSubstitutionWithSimpleAction_Issue930)

0 commit comments

Comments
 (0)