Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue #755 : add backchaining test and change reactive nodes checks #770

Merged
merged 1 commit into from
Mar 13, 2024
Merged
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
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);
}

}
Loading