Skip to content

Commit 1da6d64

Browse files
facontidavideclaude
andcommitted
Add test for Issue #819: Sequence vs ReactiveSequence behavior
Demonstrates that regular Sequence does NOT re-evaluate conditions while a child action is RUNNING, whereas ReactiveSequence DOES. This is expected behavior - users should use ReactiveSequence when they need conditions to be re-evaluated every tick. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent c7c0ea7 commit 1da6d64

1 file changed

Lines changed: 103 additions & 0 deletions

File tree

tests/gtest_parallel.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,3 +591,106 @@ TEST(Parallel, PauseWithRetry)
591591
// the whole process should take about 300 milliseconds
592592
ASSERT_LE(toMsec(t2 - t1) - 300, margin_msec * 2);
593593
}
594+
595+
// Issue #819: Demonstrates that Sequence does NOT re-evaluate conditions
596+
// while a sibling action is RUNNING, whereas ReactiveSequence DOES.
597+
// This is expected behavior, not a bug.
598+
TEST(Parallel, Issue819_SequenceVsReactiveSequence)
599+
{
600+
using namespace BT;
601+
602+
// Test 1: Regular Sequence - condition NOT re-evaluated
603+
{
604+
static const char* xml_text = R"(
605+
<root BTCPP_format="4">
606+
<BehaviorTree ID="TestTree">
607+
<Parallel success_count="2" failure_count="1">
608+
<Sequence>
609+
<TestCondition name="cond1"/>
610+
<Sleep msec="200"/>
611+
</Sequence>
612+
<Sequence>
613+
<TestCondition name="cond2"/>
614+
<Sleep msec="200"/>
615+
</Sequence>
616+
</Parallel>
617+
</BehaviorTree>
618+
</root>
619+
)";
620+
BehaviorTreeFactory factory;
621+
std::array<int, 2> tick_counts = { 0, 0 };
622+
623+
// Register conditions that count their ticks
624+
factory.registerSimpleCondition("TestCondition", [&](TreeNode& node) {
625+
const std::string& name = node.name();
626+
if(name == "cond1")
627+
tick_counts[0]++;
628+
else if(name == "cond2")
629+
tick_counts[1]++;
630+
return NodeStatus::SUCCESS;
631+
});
632+
633+
auto tree = factory.createTreeFromText(xml_text);
634+
635+
// First tick: both conditions evaluated
636+
auto status = tree.tickExactlyOnce();
637+
ASSERT_EQ(NodeStatus::RUNNING, status);
638+
ASSERT_EQ(1, tick_counts[0]); // cond1 ticked once
639+
ASSERT_EQ(1, tick_counts[1]); // cond2 ticked once
640+
641+
// Second tick: conditions should NOT be re-evaluated (Sequence behavior)
642+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
643+
status = tree.tickExactlyOnce();
644+
ASSERT_EQ(NodeStatus::RUNNING, status);
645+
// Conditions are NOT re-ticked because Sequence remembers current_child_idx_
646+
ASSERT_EQ(1, tick_counts[0]); // Still 1 - NOT re-evaluated
647+
ASSERT_EQ(1, tick_counts[1]); // Still 1 - NOT re-evaluated
648+
}
649+
650+
// Test 2: ReactiveSequence - condition IS re-evaluated every tick
651+
{
652+
static const char* xml_text = R"(
653+
<root BTCPP_format="4">
654+
<BehaviorTree ID="TestTree">
655+
<Parallel success_count="2" failure_count="1">
656+
<ReactiveSequence>
657+
<TestCondition name="cond1"/>
658+
<Sleep msec="200"/>
659+
</ReactiveSequence>
660+
<ReactiveSequence>
661+
<TestCondition name="cond2"/>
662+
<Sleep msec="200"/>
663+
</ReactiveSequence>
664+
</Parallel>
665+
</BehaviorTree>
666+
</root>
667+
)";
668+
BehaviorTreeFactory factory;
669+
std::array<int, 2> tick_counts = { 0, 0 };
670+
671+
factory.registerSimpleCondition("TestCondition", [&](TreeNode& node) {
672+
const std::string& name = node.name();
673+
if(name == "cond1")
674+
tick_counts[0]++;
675+
else if(name == "cond2")
676+
tick_counts[1]++;
677+
return NodeStatus::SUCCESS;
678+
});
679+
680+
auto tree = factory.createTreeFromText(xml_text);
681+
682+
// First tick
683+
auto status = tree.tickExactlyOnce();
684+
ASSERT_EQ(NodeStatus::RUNNING, status);
685+
ASSERT_EQ(1, tick_counts[0]);
686+
ASSERT_EQ(1, tick_counts[1]);
687+
688+
// Second tick: conditions SHOULD be re-evaluated (ReactiveSequence behavior)
689+
std::this_thread::sleep_for(std::chrono::milliseconds(50));
690+
status = tree.tickExactlyOnce();
691+
ASSERT_EQ(NodeStatus::RUNNING, status);
692+
// Conditions ARE re-ticked because ReactiveSequence always starts from index 0
693+
ASSERT_EQ(2, tick_counts[0]); // Re-evaluated!
694+
ASSERT_EQ(2, tick_counts[1]); // Re-evaluated!
695+
}
696+
}

0 commit comments

Comments
 (0)