Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ CompileExample("t12_default_ports")
CompileExample("t13_access_by_ref")
CompileExample("t14_subtree_model")
CompileExample("t15_nodes_mocking")
CompileExample("t15_nodes_mocking_strict_failure")
CompileExample("t16_global_blackboard")
CompileExample("t17_blackboard_backup")
CompileExample("t18_waypoints")
Expand Down
45 changes: 33 additions & 12 deletions examples/t15_nodes_mocking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,14 @@ const char* xml_text = R"(

<SubTree ID="MySub" name="mysub"/>

<Script name="set_message" code="msg:= 'the original message' "/>
<SaySomething message="{msg}"/>
<Script name="set_mock_flag" code="mock_should_fail:= false"/>
<Fallback name="report_mock_message">
<Sequence>
<Script name="set_message" code="msg:= 'the original message' "/>
<SaySomething message="{msg}"/>
</Sequence>
<SaySomething message="{msg}"/>
</Fallback>

<Sequence name="counting">
<SaySomething message="1"/>
Expand All @@ -39,7 +45,14 @@ const char* xml_text = R"(

/**
* @brief In this example we will see how we can substitute some nodes
* in the Tree above with
* in the Tree above with mocks.
*
* This variant is optimized for observability: even when the substituted
* TestNode resolves to FAILURE, a Fallback logs the final message from inside
* the tree.
*
* See t15_nodes_mocking_strict_failure.cpp for the companion example that
* preserves strict failure propagation instead.
* @param argc
* @param argv
* @return
Expand Down Expand Up @@ -90,12 +103,15 @@ int main(int /*argc*/, char** /*argv*/)

// This is the configuration passed to the TestNode
BT::TestNodeConfig test_config;
// we want this to return always SUCCESS
test_config.return_status = BT::NodeStatus::SUCCESS;
// the returned status can also be computed from a script.
// Change mock_should_fail to true in the XML above to see the failure path.
test_config.return_status.reset();
test_config.return_status_script = "(mock_should_fail == true) ? FAILURE : SUCCESS";
// Convert the node in asynchronous and wait 2000 ms
test_config.async_delay = std::chrono::milliseconds(2000);
// Execute this postcondition, once completed
test_config.post_script = "msg := 'message SUBSTITUTED' ";
// Execute a different script depending on the resolved return status.
test_config.success_script = "msg := 'message SUBSTITUTED' ";
test_config.failure_script = "msg := 'message FAILURE branch' ";

// this will be synchronous (async_delay is 0)
BT::TestNodeConfig counting_config;
Expand Down Expand Up @@ -130,8 +146,9 @@ int main(int /*argc*/, char** /*argv*/)
"TestNodeConfigs": {
"NewMessage": {
"async_delay": 2000,
"return_status": "SUCCESS",
"post_script": "msg ='message SUBSTITUTED'"
"return_status_script": "(mock_should_fail == true) ? FAILURE : SUCCESS",
"success_script": "msg ='message SUBSTITUTED'",
"failure_script": "msg ='message FAILURE branch'"
},
"NoCounting": {
"return_status": "SUCCESS"
Expand Down Expand Up @@ -169,12 +186,16 @@ mysub
mysub/Sequence::4
mysub/action_subA
mysub/action_subB
set_mock_flag
report_mock_message
Sequence::9
set_message
SaySomething::8
counting
SaySomething::10
SaySomething::11
SaySomething::12
counting
SaySomething::14
SaySomething::15
SaySomething::16

------ Output (original) ------
Robot says: hello world
Expand Down
144 changes: 144 additions & 0 deletions examples/t15_nodes_mocking_strict_failure.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
#include "dummy_nodes.h"

#include "behaviortree_cpp/bt_factory.h"

// clang-format off
namespace
{
const char* xml_text = R"(
<root BTCPP_format="4">

<BehaviorTree ID="MainTree">
<Sequence>
<SaySomething name="talk" message="hello world"/>

<SubTree ID="MySub" name="mysub"/>

<Script name="set_mock_flag" code="mock_should_fail:= false"/>
<Script name="set_message" code="msg:= 'the original message' "/>
<SaySomething message="{msg}"/>

<Sequence name="counting">
<SaySomething message="1"/>
<SaySomething message="2"/>
<SaySomething message="3"/>
</Sequence>
</Sequence>
</BehaviorTree>

<BehaviorTree ID="MySub">
<Sequence>
<AlwaysSuccess name="action_subA"/>
<AlwaysSuccess name="action_subB"/>
</Sequence>
</BehaviorTree>

</root>
)";
} // namespace
// clang-format on

/**
* @brief Companion to tutorial 15 that preserves strict failure propagation.
*
* If the substituted TestNode resolves to FAILURE, the enclosing Sequence stops
* immediately. The final blackboard message is still printed by this executable,
* but the tree itself does not log the failure branch through a fallback.
*/

int main(int /*argc*/, char** /*argv*/)
{
using namespace DummyNodes;
BT::BehaviorTreeFactory factory;
factory.registerNodeType<SaySomething>("SaySomething");
factory.registerBehaviorTreeFromText(xml_text);

{
auto tree = factory.createTree("MainTree");

std::cout << "----- Nodes fullPath() -------\n";
tree.applyVisitor(
[](BT::TreeNode* node) { std::cout << node->fullPath() << std::endl; });

std::cout << "\n------ Output (original) ------\n";
auto status = tree.tickWhileRunning();
std::cout << "Original status: " << BT::toStr(status, false) << std::endl;
}

factory.registerSimpleAction("DummyAction", [](BT::TreeNode& self) {
std::cout << "DummyAction substituting node with fullPath(): " << self.fullPath()
<< std::endl;
return BT::NodeStatus::SUCCESS;
});

factory.registerSimpleAction("DummySaySomething", [](BT::TreeNode& self) {
auto msg = self.getInput<std::string>("message");
std::cout << "DummySaySomething: " << msg.value() << std::endl;
return BT::NodeStatus::SUCCESS;
});

BT::TestNodeConfig test_config;
test_config.return_status.reset();
test_config.return_status_script = "(mock_should_fail == true) ? FAILURE : SUCCESS";
test_config.async_delay = std::chrono::milliseconds(2000);
test_config.success_script = "msg := 'message SUBSTITUTED' ";
test_config.failure_script = "msg := 'message FAILURE branch' ";

BT::TestNodeConfig counting_config;
counting_config.return_status = BT::NodeStatus::SUCCESS;

factory.addSubstitutionRule("mysub/action_*", "DummyAction");
factory.addSubstitutionRule("talk", "DummySaySomething");
factory.addSubstitutionRule("set_message", test_config);
factory.addSubstitutionRule("counting", counting_config);

auto blackboard = BT::Blackboard::create();
auto tree = factory.createTree("MainTree", blackboard);
std::cout << "\n------ Output (substituted, strict failure) ------\n";
auto status = tree.tickWhileRunning();
std::cout << "Substituted status: " << BT::toStr(status, false) << std::endl;
if(blackboard->getEntry("msg"))
{
std::cout << "Substituted final msg: " << blackboard->get<std::string>("msg")
<< std::endl;
}

return 0;
}

/* Expected output:

----- Nodes fullPath() -------
Sequence::1
talk
mysub
mysub/Sequence::4
mysub/action_subA
mysub/action_subB
set_mock_flag
set_message
SaySomething::9
counting
SaySomething::11
SaySomething::12
SaySomething::13

------ Output (original) ------
Robot says: hello world
Robot says: the original message
Robot says: 1
Robot says: 2
Robot says: 3
Original status: SUCCESS

------ Output (substituted, strict failure) ------
DummySaySomething: hello world
DummyAction substituting node with fullPath(): mysub/action_subA
DummyAction substituting node with fullPath(): mysub/action_subB
Substituted status: SUCCESS
Substituted final msg: message SUBSTITUTED

If you change mock_should_fail to true in the XML above, the substituted status
becomes FAILURE and the final msg becomes "message FAILURE branch".

*/
31 changes: 25 additions & 6 deletions include/behaviortree_cpp/actions/test_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,28 @@
#include "behaviortree_cpp/scripting/script_parser.hpp"
#include "behaviortree_cpp/utils/timer_queue.h"

#include <memory>
#include <optional>
#include <string>

namespace BT
{

struct TestNodeConfig
{
/// status to return when the action is completed.
NodeStatus return_status = NodeStatus::SUCCESS;
/// Status to return when the action is completed.
///
/// If both return_status and return_status_script are specified,
/// return_status_script takes precedence.
std::optional<NodeStatus> return_status = NodeStatus::SUCCESS;

/// Optional script to compute the completion status dynamically.
///
/// This script is evaluated when the TestNode completes, after any
/// async_delay has elapsed, using the current blackboard state.
/// The result must resolve to the same set of statuses supported by
/// return_status, except IDLE which is always rejected.
std::string return_status_script;

/// script to execute when complete_func() returns SUCCESS
std::string success_script;
Expand All @@ -38,14 +53,16 @@ struct TestNodeConfig
std::chrono::milliseconds async_delay = std::chrono::milliseconds(0);

/// Function invoked when the action is completed.
/// If not specified, the node will return [return_status]
/// If not specified, the node will use return_status_script when present,
/// otherwise it will return [return_status].
std::function<NodeStatus(void)> complete_func;
};

/**
* @brief The TestNode is a Node that can be configure to:
*
* 1. Return a specific status (SUCCESS / FAILURE)
* 1.b Compute the returned status from a script evaluated at completion time
* 2. Execute a post condition script (unless halted)
* 3. Either complete immediately (synchronous action), or after a
* given period of time (asynchronous action)
Expand Down Expand Up @@ -79,15 +96,17 @@ class TestNode : public BT::StatefulActionNode
}

protected:
virtual NodeStatus onStart() override;
NodeStatus onStart() override;

virtual NodeStatus onRunning() override;
NodeStatus onRunning() override;

virtual void onHalted() override;
void onHalted() override;

NodeStatus onCompleted();

std::shared_ptr<TestNodeConfig> _config;
EnumsTablePtr _script_enums;
ScriptFunction _return_status_executor;
ScriptFunction _success_executor;
ScriptFunction _failure_executor;
ScriptFunction _post_executor;
Expand Down
Loading
Loading