#include "pistatemachine.h" #include "pisystemtime.h" #include "pithread.h" #include "gtest/gtest.h" #include #include typedef PIVector 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 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 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 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