test: cover testBehavior

This commit is contained in:
dualfroz committed 2026-09-10 19:09:50 +02:00
1 parent a53c700c17
commit 8103be3f96
1 file changed
+157
+157
View File
@@ -156,3 +156,160 @@ VF_TEST(aRunningNodeResumesNextTickInsteadOfRestartingTheSequence) {
int slowRuns = 0; int slowRuns = 0;
int slowProgress = 0; int slowProgress = 0;
auto root = std::unique_ptr<SequenceNode>(new SequenceNode("sequence"));
root->add(constantAction("first", BehaviorStatus::Success, &firstRuns));
root->add(slowAction("slow", 2, &slowRuns, &slowProgress));
BehaviorTree tree(std::move(root));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK_EQ(firstRuns, 1);
CHECK_EQ(slowRuns, 1);
// The whole point: the first child must not run again while the second is
// still Running.
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK_EQ(firstRuns, 1);
CHECK_EQ(slowRuns, 2);
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(firstRuns, 1);
CHECK_EQ(slowRuns, 3);
// Once settled the sequence starts over from the top.
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK_EQ(firstRuns, 2);
CHECK_EQ(slowRuns, 4);
}
VF_TEST(selectorResumesTheRunningBranchWithoutRecheckingEarlierOnes) {
int guardChecks = 0;
int slowRuns = 0;
int slowProgress = 0;
auto root = std::unique_ptr<SelectorNode>(new SelectorNode("selector"));
root->add(std::unique_ptr<BehaviorNode>(
new ConditionNode("guard", [&guardChecks](BehaviorContext&) {
++guardChecks;
return false;
})));
root->add(slowAction("slow", 3, &slowRuns, &slowProgress));
BehaviorTree tree(std::move(root));
BehaviorContext ctx;
tree.tick(ctx);
CHECK_EQ(guardChecks, 1);
tree.tick(ctx);
tree.tick(ctx);
CHECK_EQ(guardChecks, 1);
CHECK_EQ(slowRuns, 3);
CHECK_EQ(tree.root()->childAt(1)->name(), std::string("slow"));
}
VF_TEST(cooldownBlocksForExactlyTheRequestedNumberOfTicks) {
int runs = 0;
auto gated = std::unique_ptr<BehaviorNode>(
new CooldownNode("cooldown", constantAction("fire", BehaviorStatus::Success, &runs), 3));
BehaviorTree tree(std::move(gated));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(runs, 1);
for (int blocked = 0; blocked < 3; ++blocked) {
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(runs, 1);
}
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(runs, 2);
// And the block starts again immediately after the second firing.
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(runs, 2);
}
VF_TEST(resettingTheTreeClearsALiveCooldown) {
int runs = 0;
auto gated = std::unique_ptr<BehaviorNode>(
new CooldownNode("cooldown", constantAction("fire", BehaviorStatus::Success, &runs), 5));
BehaviorTree tree(std::move(gated));
BehaviorContext ctx;
tree.tick(ctx);
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
tree.reset();
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(runs, 2);
}
VF_TEST(inverterFlipsSuccessAndFailureButLetsRunningThrough) {
int progress = 0;
int runs = 0;
BehaviorContext ctx;
BehaviorTree yes(std::unique_ptr<BehaviorNode>(new InverterNode(
"invert", constantAction("succeeds", BehaviorStatus::Success, nullptr))));
CHECK(yes.tick(ctx) == BehaviorStatus::Failure);
BehaviorTree no(std::unique_ptr<BehaviorNode>(
new InverterNode("invert", constantAction("fails", BehaviorStatus::Failure, nullptr))));
CHECK(no.tick(ctx) == BehaviorStatus::Success);
BehaviorTree waiting(std::unique_ptr<BehaviorNode>(
new InverterNode("invert", slowAction("slow", 2, &runs, &progress))));
CHECK(waiting.tick(ctx) == BehaviorStatus::Running);
}
VF_TEST(repeaterSucceedsOnlyAfterTheRequestedNumberOfCompletions) {
int runs = 0;
auto repeat = std::unique_ptr<BehaviorNode>(new RepeaterNode(
"repeat", constantAction("beat", BehaviorStatus::Success, &runs), 3));
BehaviorTree tree(std::move(repeat));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Success);
CHECK_EQ(runs, 3);
int failingRuns = 0;
auto stopping = std::unique_ptr<BehaviorNode>(new RepeaterNode(
"repeat", constantAction("beat", BehaviorStatus::Failure, &failingRuns), 3, true));
BehaviorTree strict(std::move(stopping));
CHECK(strict.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(failingRuns, 1);
}
VF_TEST(retryUntilSuccessGivesUpAfterTheAttemptBudget) {
int runs = 0;
auto retry = std::unique_ptr<BehaviorNode>(new RetryUntilSuccessNode(
"retry", constantAction("attempt", BehaviorStatus::Failure, &runs), 3));
BehaviorTree tree(std::move(retry));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(runs, 3);
}
VF_TEST(timeLimitCutsOffAChildThatNeverSettles) {
int runs = 0;
auto limited = std::unique_ptr<BehaviorNode>(new TimeLimitNode(
"window", constantAction("stall", BehaviorStatus::Running, &runs), 3));
BehaviorTree tree(std::move(limited));
BehaviorContext ctx;
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK(tree.tick(ctx) == BehaviorStatus::Failure);
CHECK_EQ(runs, 3);
// The budget is refilled once the decorator gave its verdict.
CHECK(tree.tick(ctx) == BehaviorStatus::Running);
CHECK_EQ(runs, 4);
}