Skip to content

Commit

Permalink
issue #755 : add backchaining test and change reactive nodes checks
Browse files Browse the repository at this point in the history
  • Loading branch information
facontidavide committed Feb 13, 2024
1 parent 83fce13 commit 3de7cc7
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 7 deletions.
2 changes: 1 addition & 1 deletion conanfile.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[requires]
gtest/1.12.1
gtest/1.14.0
zeromq/4.3.4
sqlite3/3.40.1

Expand Down
2 changes: 1 addition & 1 deletion src/controls/reactive_fallback.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace BT
{

bool ReactiveFallback::throw_if_multiple_running = true;
bool ReactiveFallback::throw_if_multiple_running = false;

void ReactiveFallback::EnableException(bool enable)
{
Expand Down
2 changes: 1 addition & 1 deletion src/controls/reactive_sequence.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
namespace BT
{

bool ReactiveSequence::throw_if_multiple_running = true;
bool ReactiveSequence::throw_if_multiple_running = false;

void ReactiveSequence::EnableException(bool enable)
{
Expand Down
8 changes: 4 additions & 4 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ set(BT_TESTS
gtest_match.cpp
gtest_json.cpp
gtest_reactive.cpp
gtest_reactive_backchaining.cpp
gtest_sequence.cpp
gtest_skipping.cpp
gtest_substitution.cpp
Expand Down Expand Up @@ -55,12 +56,11 @@ elseif(catkin_FOUND AND CATKIN_ENABLE_TESTING)

else()

find_package(GTest)
find_package(GTest REQUIRED)
enable_testing()

add_executable(${BTCPP_LIBRARY}_test ${BT_TESTS}
gtest_enums.cpp
gtest_any.cpp)
add_executable(${BTCPP_LIBRARY}_test ${BT_TESTS})

target_link_libraries(${PROJECT_NAME}_test
${TEST_DEPENDECIES}
Threads::Threads
Expand Down
184 changes: 184 additions & 0 deletions tests/gtest_reactive_backchaining.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#include <gtest/gtest.h>
#include "behaviortree_cpp/loggers/bt_observer.h"
#include "behaviortree_cpp/bt_factory.h"

namespace BT::test
{

class SimpleCondition : public BT::ConditionNode
{
private:
std::string port_name_;
public:
SimpleCondition(const std::string& name, const BT::NodeConfig& config,
std::string port_name) :
BT::ConditionNode(name, config),
port_name_(port_name)
{}
static BT::PortsList providedPorts() {
return {};
}
BT::NodeStatus tick() override
{
auto val = config().blackboard->get<bool>(port_name_);
return (val) ? NodeStatus::SUCCESS : NodeStatus::FAILURE;
}
};

//--------------------------
class AsyncTestAction : public BT::StatefulActionNode
{
int counter_ = 0;
std::string port_name_;
public:
AsyncTestAction(const std::string& name, const BT::NodeConfig& config,
std::string port_name) :
BT::StatefulActionNode(name, config),
port_name_(port_name)
{}

static BT::PortsList providedPorts(){ return {}; }

NodeStatus onStart() override {
counter_ = 0;
return NodeStatus::RUNNING;
}

NodeStatus onRunning() override {
if(++counter_ == 2) {
config().blackboard->set<bool>(port_name_, true);
return NodeStatus::SUCCESS;
}
return NodeStatus::RUNNING;
}
void onHalted() override {}
};
//--------------------------

TEST(ReactiveBackchaining, EnsureWarm)
{
// This test shows the basic structure of a PPA: a fallback
// of a postcondition and an action to make that
// postcondition true.
static const char* xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm name="warm"/>
<ReactiveSequence>
<IsHoldingJacket name="jacket" />
<WearJacket name="wear" />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
</root>
)";

// The final condition of the PPA; the thing that make_warm achieves.
// For this example, we're only warm after WearJacket returns success.
BehaviorTreeFactory factory;
factory.registerNodeType<SimpleCondition>("IsWarm", "is_warm");
factory.registerNodeType<SimpleCondition>("IsHoldingJacket", "holding_jacket");
factory.registerNodeType<AsyncTestAction>("WearJacket", "is_warm");

Tree tree = factory.createTreeFromText(xml_text);
BT::TreeObserver observer(tree);

auto& blackboard = tree.subtrees.front()->blackboard;
blackboard->set("is_warm", false);
blackboard->set("holding_jacket", true);

// first tick: not warm, have a jacket: start wearing it
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);
EXPECT_FALSE(blackboard->get<bool>("is_warm"));

// second tick: not warm (still wearing)
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);
EXPECT_FALSE(blackboard->get<bool>("is_warm"));

// third tick: warm (wearing succeeded)
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::SUCCESS);
EXPECT_TRUE(blackboard->get<bool>("is_warm"));

// fourth tick: still warm (just the condition ticked)
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::SUCCESS);

EXPECT_EQ(observer.getStatistics("warm").failure_count, 3);
EXPECT_EQ(observer.getStatistics("warm").success_count, 1);

EXPECT_EQ(observer.getStatistics("jacket").transitions_count, 3);
EXPECT_EQ(observer.getStatistics("jacket").success_count, 3);

EXPECT_EQ(observer.getStatistics("wear").success_count, 1);
}


TEST(ReactiveBackchaining, EnsureWarmWithEnsureHoldingHacket)
{
// This test backchains on HoldingHacket => EnsureHoldingHacket to iteratively add reactivity and functionality to the tree.
// The general structure of the PPA remains the same.
static const char* xml_text = R"(
<root BTCPP_format="4">
<BehaviorTree ID="EnsureWarm">
<ReactiveFallback>
<IsWarm />
<ReactiveSequence>
<SubTree ID="EnsureHoldingJacket" />
<WearJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
<BehaviorTree ID="EnsureHoldingJacket">
<ReactiveFallback>
<IsHoldingJacket />
<ReactiveSequence>
<IsNearCloset />
<GrabJacket />
</ReactiveSequence>
</ReactiveFallback>
</BehaviorTree>
</root>
)";

BehaviorTreeFactory factory;
factory.registerNodeType<SimpleCondition>("IsWarm", "is_warm");
factory.registerNodeType<SimpleCondition>("IsHoldingJacket", "holding_jacket");
factory.registerNodeType<SimpleCondition>("IsNearCloset", "near_closet");
factory.registerNodeType<AsyncTestAction>("WearJacket", "is_warm");
factory.registerNodeType<AsyncTestAction>("GrabJacket", "holding_jacket");

factory.registerBehaviorTreeFromText(xml_text);
Tree tree = factory.createTree("EnsureWarm");
BT::TreeObserver observer(tree);

tree.subtrees[0]->blackboard->set("is_warm", false);
tree.subtrees[1]->blackboard->set("holding_jacket", false);
tree.subtrees[1]->blackboard->set("near_closet", true);

// first tick: not warm, no jacket, start GrabJacket
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);
EXPECT_FALSE(tree.subtrees[0]->blackboard->get<bool>("is_warm"));
EXPECT_FALSE(tree.subtrees[1]->blackboard->get<bool>("holding_jacket"));
EXPECT_TRUE(tree.subtrees[1]->blackboard->get<bool>("near_closet"));

// second tick: still GrabJacket
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);

// third tick: GrabJacket succeeded, start wearing
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);
EXPECT_FALSE(tree.subtrees[0]->blackboard->get<bool>("is_warm"));
EXPECT_TRUE(tree.subtrees[1]->blackboard->get<bool>("holding_jacket"));

// fourth tick: still WearingJacket
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::RUNNING);

// fifth tick: warm (WearingJacket succeeded)
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::SUCCESS);
EXPECT_TRUE(tree.subtrees[0]->blackboard->get<bool>("is_warm"));

// sixr tick: still warm (just the condition ticked)
EXPECT_EQ(tree.tickExactlyOnce(), NodeStatus::SUCCESS);
}

}

0 comments on commit 3de7cc7

Please sign in to comment.