test: add PIStateMachine test suite (states, transitions, guards, final/parallel states, timeouts, nested posts)

This commit is contained in:
2026-08-31 21:47:29 +03:00
parent fa7ff781fe
commit 5bde1603cd
2 changed files with 692 additions and 0 deletions
+1
View File
@@ -33,6 +33,7 @@ endmacro()
pip_test(math)
pip_test(core)
pip_test(state_machine)
pip_test(piobject)
if(TARGET pip_client_server)
pip_test(client_server pip_client_server)
+691
View File
@@ -0,0 +1,691 @@
#include "pistatemachine.h"
#include "pisystemtime.h"
#include "pithread.h"
#include "gtest/gtest.h"
#include <atomic>
#include <ostream>
typedef PIVector<PIString> Log;
// gtest printer for PIString (picked up via Koenig lookup): makes Log values
// readable in assertion failure messages
inline void PrintTo(const PIString & s, ::std::ostream * os) {
*os << '"' << s.data() << '"';
}
// Waits until \a cond() returns true or \a timeout_ms expires, polling every 10 ms.
template<typename F>
static bool waitFor(F cond, int timeout_ms) {
const PISystemTime step = PISystemTime::fromMilliseconds(10);
for (int waited = 0; !cond() && waited < timeout_ms; waited += 10)
step.sleep();
return cond();
}
// States and transitions are owned by the machine, which is owned by the fixture,
// so everything is cleaned up in TearDown even when a test fails early.
class PIStateMachineTest: public ::testing::Test {
protected:
PIStateMachine * machine = nullptr;
void TearDown() override {
delete machine;
machine = nullptr;
}
};
TEST_F(PIStateMachineTest, Start_ActivatesInitialState) {
Log log;
auto * idle = new PIStateLambda([&log]() { log << "enter_idle"; }, [&log]() { log << "exit_idle"; }, "idle");
auto * running = new PIStateLambda([&log]() { log << "enter_running"; }, [&log]() { log << "exit_running"; }, "running");
machine = new PIStateMachine("Main");
machine->addState(idle);
machine->addState(running);
machine->setInitialState(idle);
EXPECT_FALSE(machine->isRunning());
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->isRunning());
EXPECT_TRUE(idle->isActive());
EXPECT_FALSE(running->isActive());
EXPECT_EQ(machine->activeChild(), idle);
Log expected;
expected << "enter_idle";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, Start_NoInitialState_ReturnsFalse) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, nullptr, "a");
machine = new PIStateMachine("Main");
machine->addState(a);
EXPECT_FALSE(machine->start());
EXPECT_FALSE(machine->isRunning());
EXPECT_FALSE(a->isActive());
EXPECT_EQ(log.size(), 0u);
}
TEST_F(PIStateMachineTest, PostEvent_BeforeStart_ReturnsFalse) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, nullptr, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1);
EXPECT_FALSE(machine->postEvent(1));
EXPECT_FALSE(machine->isRunning());
EXPECT_FALSE(a->isActive());
EXPECT_FALSE(b->isActive());
}
TEST_F(PIStateMachineTest, PostEvent_SimpleTransition) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, [&log]() { log << "exit_b"; }, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_FALSE(a->isActive());
EXPECT_TRUE(b->isActive());
EXPECT_EQ(machine->activeChild(), b);
Log expected;
expected << "enter_a" << "exit_a" << "enter_b";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, PostEvent_NoMatchingEvent_ReturnsFalse) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1);
EXPECT_TRUE(machine->start());
EXPECT_FALSE(machine->postEvent(42));
EXPECT_TRUE(a->isActive());
EXPECT_FALSE(b->isActive());
Log expected;
expected << "enter_a";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, PostEvent_InactiveSourceTransition_NotTriggered) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, nullptr, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
auto * c = new PIStateLambda([&log]() { log << "enter_c"; }, nullptr, "c");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->addState(c);
machine->setInitialState(a);
b->addTransition(c, 1);
EXPECT_TRUE(machine->start());
EXPECT_FALSE(machine->postEvent(1));
EXPECT_TRUE(a->isActive());
EXPECT_FALSE(b->isActive());
EXPECT_FALSE(c->isActive());
}
TEST_F(PIStateMachineTest, PostEvent_AfterFinish_ReturnsFalse) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * f = new PIStateFinal(nullptr, "finish");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(f);
machine->setInitialState(a);
a->addTransition(f, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_FALSE(machine->isRunning());
EXPECT_FALSE(machine->postEvent(1));
EXPECT_TRUE(f->isActive());
}
TEST_F(PIStateMachineTest, SelfTransition_OnlyActionRuns) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->setInitialState(a);
a->addTransition(a, 1)->addAction([&log]() { log << "action"; });
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(a->isActive());
Log expected;
expected << "enter_a" << "action";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, Transition_ActionRunsBetweenExitAndEnter) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1)->addAction([&log]() { log << "action"; });
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
Log expected;
expected << "enter_a" << "exit_a" << "action" << "enter_b";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, ChildTransition_WinsOverParent) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * x = new PIStateLambda([&log]() { log << "enter_x"; }, nullptr, "x");
auto * y = new PIStateLambda([&log]() { log << "enter_y"; }, nullptr, "y");
machine = new PIStateMachine("Main");
machine->addState(p);
machine->addState(x);
machine->addState(y);
p->addState(c1);
p->setInitialState(c1);
machine->setInitialState(p);
c1->addTransition(x, 1);
p->addTransition(y, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(x->isActive());
EXPECT_FALSE(y->isActive());
EXPECT_FALSE(c1->isActive());
EXPECT_FALSE(p->isActive());
}
TEST_F(PIStateMachineTest, Guard_False_NoTransition) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1)->addGuard([](int value) -> bool { return value > 10; });
EXPECT_TRUE(machine->start());
EXPECT_FALSE(machine->postEvent(1, 5));
EXPECT_TRUE(a->isActive());
EXPECT_FALSE(b->isActive());
EXPECT_TRUE(machine->postEvent(1, 15));
EXPECT_FALSE(a->isActive());
EXPECT_TRUE(b->isActive());
}
TEST_F(PIStateMachineTest, Guard_ReceivesEventArgs) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
// note: guard parameters must be taken by value: postEvent passes args by value,
// a guard with a reference parameter would never match the arguments format
a->addTransition(b, 1)->addGuard([](PIString msg) -> bool { return msg == "allowed"; });
EXPECT_TRUE(machine->start());
EXPECT_FALSE(machine->postEvent(1, PIString("blocked")));
EXPECT_TRUE(a->isActive());
EXPECT_FALSE(b->isActive());
EXPECT_TRUE(machine->postEvent(1, PIString("allowed")));
EXPECT_FALSE(a->isActive());
EXPECT_TRUE(b->isActive());
}
TEST_F(PIStateMachineTest, Guard_WrongArgFormat_NoTransition) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda([&log]() { log << "enter_b"; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1)->addGuard([](int) -> bool { return true; });
EXPECT_TRUE(machine->start());
// guard expects int, event carries PIString: transition must not fire
EXPECT_FALSE(machine->postEvent(1, PIString("unexpected")));
EXPECT_TRUE(a->isActive());
EXPECT_FALSE(b->isActive());
}
TEST_F(PIStateMachineTest, CompoundState_InitialChildActive) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * c2 = new PIStateLambda([&log]() { log << "enter_c2"; }, [&log]() { log << "exit_c2"; }, "c2");
machine = new PIStateMachine("Main");
machine->addState(p);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(p);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(p->isActive());
EXPECT_TRUE(p->isCompound());
EXPECT_TRUE(c1->isAtomic());
EXPECT_TRUE(c1->isActive());
EXPECT_FALSE(c2->isActive());
EXPECT_EQ(p->activeChild(), c1);
Log expected;
expected << "enter_p" << "enter_c1";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, CompoundState_InternalTransition) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * c2 = new PIStateLambda([&log]() { log << "enter_c2"; }, [&log]() { log << "exit_c2"; }, "c2");
machine = new PIStateMachine("Main");
machine->addState(p);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(p);
c1->addTransition(c2, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(p->isActive());
EXPECT_FALSE(c1->isActive());
EXPECT_TRUE(c2->isActive());
EXPECT_EQ(p->activeChild(), c2);
Log expected;
expected << "enter_p" << "enter_c1" << "exit_c1" << "enter_c2";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, CompoundState_ExitFromNestedChild) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * x = new PIStateLambda([&log]() { log << "enter_x"; }, nullptr, "x");
machine = new PIStateMachine("Main");
machine->addState(p);
machine->addState(x);
p->addState(c1);
p->setInitialState(c1);
machine->setInitialState(p);
c1->addTransition(x, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(x->isActive());
EXPECT_FALSE(c1->isActive());
EXPECT_FALSE(p->isActive());
Log expected;
expected << "enter_p" << "enter_c1" << "exit_c1" << "exit_p" << "enter_x";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, CompoundState_EnterFromOutsideToChild) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * c2 = new PIStateLambda([&log]() { log << "enter_c2"; }, [&log]() { log << "exit_c2"; }, "c2");
auto * e = new PIStateLambda([&log]() { log << "enter_e"; }, [&log]() { log << "exit_e"; }, "e");
machine = new PIStateMachine("Main");
machine->addState(p);
machine->addState(e);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(e);
e->addTransition(c2, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(p->isActive());
EXPECT_TRUE(c2->isActive());
EXPECT_FALSE(c1->isActive());
EXPECT_FALSE(e->isActive());
EXPECT_EQ(p->activeChild(), c2);
Log expected;
expected << "enter_e" << "exit_e" << "enter_p" << "enter_c2";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, CompoundState_TransitionToParentState) {
Log log;
auto * p = new PIStateLambda([&log]() { log << "enter_p"; }, [&log]() { log << "exit_p"; }, "p");
auto * c1 = new PIStateLambda([&log]() { log << "enter_c1"; }, [&log]() { log << "exit_c1"; }, "c1");
auto * c2 = new PIStateLambda([&log]() { log << "enter_c2"; }, [&log]() { log << "exit_c2"; }, "c2");
machine = new PIStateMachine("Main");
machine->addState(p);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(p);
c1->addTransition(p, 1);
EXPECT_TRUE(machine->start());
// transition to the compound parent re-enters its initial child
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(p->isActive());
EXPECT_TRUE(c1->isActive());
EXPECT_FALSE(c2->isActive());
Log expected;
expected << "enter_p" << "enter_c1" << "exit_c1" << "enter_c1";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, ParallelState_ActivatesAllChildren) {
Log log;
auto * par = new PIStateLambda([&log]() { log << "enter_par"; }, [&log]() { log << "exit_par"; }, "par");
auto * s1 = new PIStateLambda([&log]() { log << "enter_s1"; }, [&log]() { log << "exit_s1"; }, "s1");
auto * s2 = new PIStateLambda([&log]() { log << "enter_s2"; }, [&log]() { log << "exit_s2"; }, "s2");
machine = new PIStateMachine("Main");
machine->addState(par);
par->setParallel(true);
par->addState(s1);
par->addState(s2);
machine->setInitialState(par);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(par->isActive());
EXPECT_TRUE(par->isParallel());
EXPECT_TRUE(s1->isActive());
EXPECT_TRUE(s2->isActive());
EXPECT_EQ(par->activeChild(), nullptr);
PIVector<PIStateBase *> expected_children;
expected_children << s1 << s2;
EXPECT_EQ(par->activeChildren(), expected_children);
Log expected;
expected << "enter_par" << "enter_s1" << "enter_s2";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, ParallelState_ExitFromChild_ExitsWholeParallel) {
Log log;
auto * par = new PIStateLambda([&log]() { log << "enter_par"; }, [&log]() { log << "exit_par"; }, "par");
auto * s1 = new PIStateLambda([&log]() { log << "enter_s1"; }, [&log]() { log << "exit_s1"; }, "s1");
auto * s2 = new PIStateLambda([&log]() { log << "enter_s2"; }, [&log]() { log << "exit_s2"; }, "s2");
auto * x = new PIStateLambda([&log]() { log << "enter_x"; }, nullptr, "x");
machine = new PIStateMachine("Main");
machine->addState(par);
machine->addState(x);
par->setParallel(true);
par->addState(s1);
par->addState(s2);
machine->setInitialState(par);
s1->addTransition(x, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(x->isActive());
EXPECT_FALSE(par->isActive());
EXPECT_FALSE(s1->isActive());
EXPECT_FALSE(s2->isActive());
Log expected;
expected << "enter_par" << "enter_s1" << "enter_s2" << "exit_s1" << "exit_s2" << "exit_par" << "enter_x";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, ParallelState_EnterFromOutside_ActivatesAllChildren) {
Log log;
auto * par = new PIStateLambda([&log]() { log << "enter_par"; }, [&log]() { log << "exit_par"; }, "par");
auto * s1 = new PIStateLambda([&log]() { log << "enter_s1"; }, [&log]() { log << "exit_s1"; }, "s1");
auto * s2 = new PIStateLambda([&log]() { log << "enter_s2"; }, [&log]() { log << "exit_s2"; }, "s2");
auto * y = new PIStateLambda([&log]() { log << "enter_y"; }, [&log]() { log << "exit_y"; }, "y");
machine = new PIStateMachine("Main");
machine->addState(par);
machine->addState(y);
par->setParallel(true);
par->addState(s1);
par->addState(s2);
machine->setInitialState(y);
y->addTransition(s1, 1);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(par->isActive());
EXPECT_TRUE(s1->isActive());
EXPECT_TRUE(s2->isActive());
EXPECT_FALSE(y->isActive());
}
TEST_F(PIStateMachineTest, FinalState_FinishesMachine) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
int final_calls = 0;
int finish_calls = 0;
auto * f = new PIStateFinal([&final_calls]() { final_calls++; }, "finish");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(f);
machine->setInitialState(a);
a->addTransition(f, 1);
machine->setOnFinish([&finish_calls]() { finish_calls++; });
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_FALSE(machine->isRunning());
EXPECT_TRUE(f->isActive());
EXPECT_TRUE(f->isFinal());
EXPECT_FALSE(a->isActive());
EXPECT_EQ(final_calls, 1);
EXPECT_EQ(finish_calls, 1);
Log expected;
expected << "enter_a" << "exit_a";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, ActiveAtomics_ReturnsActiveLeaves) {
auto * p = new PIStateLambda(nullptr, nullptr, "p");
auto * c1 = new PIStateLambda(nullptr, nullptr, "c1");
auto * c2 = new PIStateLambda(nullptr, nullptr, "c2");
auto * x = new PIStateLambda(nullptr, nullptr, "x");
machine = new PIStateMachine("Main");
machine->addState(p);
machine->addState(x);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(p);
EXPECT_TRUE(machine->start());
PIVector<PIStateBase *> expected;
expected << c1;
EXPECT_EQ(machine->activeAtomics(), expected);
EXPECT_EQ(p->activeAtomics(), expected);
}
TEST_F(PIStateMachineTest, GatherStates_ReturnsAllDescendants) {
auto * p = new PIStateLambda(nullptr, nullptr, "p");
auto * c1 = new PIStateLambda(nullptr, nullptr, "c1");
auto * c2 = new PIStateLambda(nullptr, nullptr, "c2");
auto * x = new PIStateLambda(nullptr, nullptr, "x");
machine = new PIStateMachine("Main");
machine->addState(p);
machine->addState(x);
p->addState(c1);
p->addState(c2);
p->setInitialState(c1);
machine->setInitialState(p);
auto states = machine->gatherStates();
EXPECT_EQ(states.size(), 5u);
EXPECT_TRUE(states.contains(machine));
EXPECT_TRUE(states.contains(p));
EXPECT_TRUE(states.contains(c1));
EXPECT_TRUE(states.contains(c2));
EXPECT_TRUE(states.contains(x));
}
TEST_F(PIStateMachineTest, NestedPostEvent_FromOnEnter) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda(
[&log, this]() {
log << "enter_b";
machine->postEvent(2);
},
[&log]() { log << "exit_b"; },
"b");
auto * c = new PIStateLambda([&log]() { log << "enter_c"; }, nullptr, "c");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->addState(c);
machine->setInitialState(a);
a->addTransition(b, 1);
b->addTransition(c, 2);
EXPECT_TRUE(machine->start());
// post from inside onEnter is queued and processed after the outer postEvent returns
EXPECT_TRUE(machine->postEvent(1));
EXPECT_FALSE(a->isActive());
EXPECT_FALSE(b->isActive());
EXPECT_TRUE(c->isActive());
Log expected;
expected << "enter_a" << "exit_a" << "enter_b" << "exit_b" << "enter_c";
EXPECT_EQ(log, expected);
}
TEST_F(PIStateMachineTest, NestedPostEvent_SequenceProcessedInOrder) {
Log log;
auto * a = new PIStateLambda([&log]() { log << "enter_a"; }, [&log]() { log << "exit_a"; }, "a");
auto * b = new PIStateLambda(
[&log, this]() {
log << "enter_b";
machine->postEvent(2);
machine->postEvent(3);
},
[&log]() { log << "exit_b"; },
"b");
auto * c = new PIStateLambda([&log]() { log << "enter_c"; }, [&log]() { log << "exit_c"; }, "c");
auto * d = new PIStateLambda([&log]() { log << "enter_d"; }, nullptr, "d");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->addState(c);
machine->addState(d);
machine->setInitialState(a);
a->addTransition(b, 1);
b->addTransition(c, 2);
c->addTransition(d, 3);
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
EXPECT_TRUE(d->isActive());
Log expected;
expected << "enter_a" << "exit_a" << "enter_b" << "exit_b" << "enter_c" << "exit_c" << "enter_d";
EXPECT_EQ(log, expected);
}
#ifdef PIP_HAS_THREADS
TEST_F(PIStateMachineTest, TimeoutTransition_Fires) {
std::atomic_bool entered_b{false};
auto * a = new PIStateLambda([]() {});
auto * b = new PIStateLambda([&entered_b]() { entered_b = true; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTimeoutTransition(b, PISystemTime::fromMilliseconds(100));
EXPECT_TRUE(machine->start());
EXPECT_FALSE(entered_b.load());
EXPECT_TRUE(waitFor([&entered_b]() { return entered_b.load(); }, 2000));
EXPECT_TRUE(b->isActive());
EXPECT_FALSE(a->isActive());
}
TEST_F(PIStateMachineTest, TimeoutTransition_CancelledWhenSourceExited) {
auto * a = new PIStateLambda([]() {});
auto * b = new PIStateLambda([]() {}, nullptr, "b");
auto * c = new PIStateLambda([]() {}, nullptr, "c");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->addState(c);
machine->setInitialState(a);
a->addTransition(b, 1);
a->addTimeoutTransition(c, PISystemTime::fromMilliseconds(100));
EXPECT_TRUE(machine->start());
EXPECT_TRUE(machine->postEvent(1));
PISystemTime::fromMilliseconds(300).sleep();
EXPECT_TRUE(b->isActive());
EXPECT_FALSE(c->isActive());
}
TEST_F(PIStateMachineTest, PostEvent_FromOtherThread) {
std::atomic_bool entered_b{false};
auto * a = new PIStateLambda([]() {});
auto * b = new PIStateLambda([&entered_b]() { entered_b = true; }, nullptr, "b");
machine = new PIStateMachine("Main");
machine->addState(a);
machine->addState(b);
machine->setInitialState(a);
a->addTransition(b, 1);
EXPECT_TRUE(machine->start());
PIThread worker([this]() {
PISystemTime::fromMilliseconds(50).sleep();
machine->postEvent(1);
});
worker.startOnce();
EXPECT_TRUE(waitFor([&entered_b]() { return entered_b.load(); }, 2000));
EXPECT_TRUE(b->isActive());
worker.waitForFinish(PISystemTime::fromSeconds(2));
}
#endif // PIP_HAS_THREADS