blob: 86888c06c82ca23d4926684f12174d3d1321cd47 [file] [log] [blame]
// Copyright (c) 2014 Google Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "starboard/common/state_machine.h"
#include <iostream>
#include <list>
#include "starboard/log.h"
#include "testing/gtest/include/gtest/gtest.h"
// Enables some verbose logging for debugging the state machine test and
// implementation.
#define STATE_MACHINE_TEST_DEBUG 0
namespace starboard {
namespace {
namespace hsm {
// Constant to represent no version for TestHsm::Expect.
static const uint64_t kNoVersion = static_cast<uint64_t>(-1);
// States for the test HSM
enum TestState {
kStateS0,
kStateS1,
kStateS11,
kStateS2,
kStateS21,
kStateS211,
kStateT0,
};
// Events for the test HSM
enum TestEvent {
kEventA,
kEventB,
kEventC,
kEventD,
kEventE,
kEventF,
kEventG,
kEventH,
kEventI,
kEventJ,
};
// An enumeration of things that the HSM does that we can sense and then
// assert about.
enum HsmEvent { kHsmEnter, kHsmExit, kHsmHandled };
} // namespace hsm
// --- Test Subclass ---
// StateMachine is an abstract class, so we must subclass it to test it.
// This class uses the sample test state machine specified by Miro Samek in his
// Practical Statecharts book. It covers the interesting transitions and
// topologies, so if it's fully exercised, it should represent a
// close-to-canonical test of the state machine's facilities.
//
// The diagram of this statechart is reproduced here:
// http://www.state-machine.com/resources/Heinzmann04.pdf
//
// This version has:
// - A new event, I, in state S11, to test reentrant event handling.
// - A new event, J, and new state T0, to test the no top state case.
class TestHsm : public StateMachine<hsm::TestState, hsm::TestEvent> {
public:
// --- Some types to aid sensing ---
struct ResultEvent {
// The state the HSM was in when it handled the event.
const optional<hsm::TestState> state;
// The data passed into the event, if any.
const void* data;
// The state that actually handled the event (could be an ancestor of the
// current state.
const optional<hsm::TestState> event_state;
// The event that was handled.
const optional<hsm::TestEvent> event;
// The "HSM Event" that occurred causing this to be recorded.
const hsm::HsmEvent hsm_event;
// The state version at the time the HsmEvent occured.
const uint64_t version;
};
// --- Extra state to aid sensing ---
// Foo is used as extended state and in guard conditions in the test HSM (see
// link above).
bool foo_;
// A counter for how many times the S11 I handler is invoked.
int event_i_count_;
// A collection of interesting things that has happened to this state
// machine. Used to validate that the HSM behaved as expected.
std::list<ResultEvent> results;
TestHsm()
: StateMachine<hsm::TestState, hsm::TestEvent>("TestHsm"),
foo_(true),
event_i_count_(0) {
#if STATE_MACHINE_TEST_DEBUG
EnableLogging();
#endif
}
virtual ~TestHsm() {}
// Clears the results list, so nothing bleeds between test cases.
void ClearResults() { results.clear(); }
// Consumes and validates a ResultEvent from the results list.
void Expect(hsm::HsmEvent hsm_event,
optional<hsm::TestState> event_state = nullopt,
optional<hsm::TestEvent> event = nullopt,
optional<hsm::TestState> current_state = nullopt,
void* data = NULL,
uint64_t version = hsm::kNoVersion) {
#if STATE_MACHINE_TEST_DEBUG
SB_DLOG(INFO) << __FUNCTION__ << ": hsm_event=" << hsm_event
<< ", event_state=" << GetStateString(event_state)
<< ", event=" << GetEventString(event)
<< ", current_state=" << GetStateString(current_state)
<< ", data=0x" << std::hex << data << ", version=" << version;
#endif // STATE_MACHINE_TEST_DEBUG
EXPECT_FALSE(results.empty());
TestHsm::ResultEvent result = results.front();
results.pop_front();
EXPECT_EQ(hsm_event, result.hsm_event);
if (event_state) {
EXPECT_EQ(event_state, result.event_state);
if (!current_state) {
EXPECT_EQ(event_state, result.state);
}
}
if (current_state) {
EXPECT_EQ(current_state, result.state);
}
if (event) {
EXPECT_EQ(event, result.event);
}
EXPECT_EQ(data, result.data);
if (version != hsm::kNoVersion) {
EXPECT_EQ(version, result.version);
}
}
// --- StateMachine Implementation ---
protected:
virtual optional<hsm::TestState> GetUserParentState(
hsm::TestState state) const SB_OVERRIDE {
switch (state) {
case hsm::kStateS1:
case hsm::kStateS2:
return hsm::kStateS0;
case hsm::kStateS11:
return hsm::kStateS1;
case hsm::kStateS21:
return hsm::kStateS2;
case hsm::kStateS211:
return hsm::kStateS21;
default:
return nullopt;
}
}
virtual optional<hsm::TestState> GetUserInitialSubstate(
hsm::TestState state) const SB_OVERRIDE {
switch (state) {
case hsm::kStateS0:
return hsm::kStateS1;
case hsm::kStateS1:
return hsm::kStateS11;
case hsm::kStateS2:
return hsm::kStateS21;
case hsm::kStateS21:
return hsm::kStateS211;
default:
return nullopt;
}
}
virtual hsm::TestState GetUserInitialState() const SB_OVERRIDE {
return hsm::kStateS0;
}
virtual const char* GetUserStateString(hsm::TestState state) const
SB_OVERRIDE {
switch (state) {
case hsm::kStateS0:
return "S0";
case hsm::kStateS1:
return "S1";
case hsm::kStateS11:
return "S11";
case hsm::kStateS2:
return "S2";
case hsm::kStateS21:
return "S21";
case hsm::kStateS211:
return "S211";
case hsm::kStateT0:
return "T0";
default:
return NULL;
}
}
virtual const char* GetUserEventString(hsm::TestEvent event) const
SB_OVERRIDE {
switch (event) {
case hsm::kEventA:
return "A";
case hsm::kEventB:
return "B";
case hsm::kEventC:
return "C";
case hsm::kEventD:
return "D";
case hsm::kEventE:
return "E";
case hsm::kEventF:
return "F";
case hsm::kEventG:
return "G";
case hsm::kEventH:
return "H";
case hsm::kEventI:
return "I";
case hsm::kEventJ:
return "J";
default:
return NULL;
}
}
virtual Result HandleUserStateEvent(hsm::TestState state,
hsm::TestEvent event,
void* data) SB_OVERRIDE {
#if STATE_MACHINE_TEST_DEBUG
SB_DLOG(INFO) << __FUNCTION__ << "(" << GetStateString(state) << ", "
<< GetEventString(event) << ", 0x" << std::hex << data
<< ");";
#endif // STATE_MACHINE_TEST_DEBUG
Result result(kNotHandled);
switch (state) {
case hsm::kStateS0:
switch (event) {
case hsm::kEventE:
result = hsm::kStateS211;
break;
case hsm::kEventJ:
result = hsm::kStateT0;
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateS1:
switch (event) {
case hsm::kEventA:
result = Result(hsm::kStateS1, true);
break;
case hsm::kEventB:
result = hsm::kStateS11;
break;
case hsm::kEventC:
result = hsm::kStateS2;
break;
case hsm::kEventD:
result = hsm::kStateS0;
break;
case hsm::kEventF:
result = hsm::kStateS211;
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateS11:
switch (event) {
case hsm::kEventG:
result = hsm::kStateS211;
break;
case hsm::kEventH:
if (foo_) {
foo_ = false;
result = kHandled;
break;
}
break;
case hsm::kEventI:
// Inject another I every other time I is handled so every I should
// ultimately increase event_i_count_ by 2.
++event_i_count_;
if (event_i_count_ % 2 == 1) {
// This should queue and be run before Handle() returns.
Handle(hsm::kEventI);
result = hsm::kStateS1;
break;
} else {
result = kHandled;
break;
}
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateS2:
switch (event) {
case hsm::kEventC:
result = hsm::kStateS1;
break;
case hsm::kEventF:
result = hsm::kStateS11;
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateS21:
switch (event) {
case hsm::kEventB:
result = hsm::kStateS211;
break;
case hsm::kEventH:
if (!foo_) {
foo_ = true;
result = Result(hsm::kStateS21, true);
break;
}
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateS211:
switch (event) {
case hsm::kEventD:
result = hsm::kStateS21;
break;
case hsm::kEventG:
result = hsm::kStateS0;
break;
default:
// Not Handled
break;
}
break;
case hsm::kStateT0:
switch (event) {
case hsm::kEventJ:
result = hsm::kStateS0;
break;
default:
// Not Handled
break;
}
break;
default:
// Not Handled
break;
}
if (result.is_handled) {
AddEvent(state, event, data, hsm::kHsmHandled);
}
return result;
}
virtual void HandleUserStateEnter(hsm::TestState state) SB_OVERRIDE {
#if STATE_MACHINE_TEST_DEBUG
SB_DLOG(INFO) << __FUNCTION__ << "(" << GetStateString(state)
<< ", ENTER);";
#endif // STATE_MACHINE_TEST_DEBUG
AddEvent(state, nullopt, NULL, hsm::kHsmEnter);
}
virtual void HandleUserStateExit(hsm::TestState state) SB_OVERRIDE {
#if STATE_MACHINE_TEST_DEBUG
SB_DLOG(INFO) << __FUNCTION__ << "(" << GetStateString(state) << ", EXIT);";
#endif // STATE_MACHINE_TEST_DEBUG
AddEvent(state, nullopt, NULL, hsm::kHsmExit);
}
private:
// Adds a new record to the result list.
void AddEvent(hsm::TestState state,
optional<hsm::TestEvent> event,
void* data,
hsm::HsmEvent hsm_event) {
ResultEvent result_event = {this->state(), data, state,
event, hsm_event, this->version()};
results.push_back(result_event);
}
};
// --- Test Definitions ---
// This test validates that a state machine will initialize itself when it
// handles its first event, even if the user has not explicitly called
// initialize.
TEST(StateMachineTest, AutoInit) {
TestHsm hsm;
hsm.ClearResults();
hsm.Handle(hsm::kEventA);
// The HSM should Auto-Initialize
hsm.Expect(hsm::kHsmEnter, hsm::kStateS0);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
// Then it should handle the event
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventA, hsm::kStateS11, 0);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(1, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
}
// This test validates that if Handle() is called from within an event handler,
// it queues the event rather than trying to Handle the next event
// reentrantly. This behavior, or something like it, is required to make the
// state machine truly run-to-completion.
TEST(StateMachineTest, ReentrantHandle) {
TestHsm hsm;
hsm.Initialize();
EXPECT_EQ(0, hsm.version());
hsm.ClearResults();
// Test a Handle() inside Handle()
EXPECT_EQ(0, hsm.event_i_count_);
hsm.Handle(hsm::kEventI);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventI, hsm::kStateS11,
NULL, 0);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventI, hsm::kStateS11,
NULL, 1);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(1, hsm.version());
EXPECT_EQ(2, hsm.event_i_count_);
}
// This test validates that every meaningful event in every state in the test
// state machine behaves as expected. This should cover all normal operation of
// the state machine framework, except what is extracted into their own test
// cases above.
TEST(StateMachineTest, KitNKaboodle) {
TestHsm hsm;
// Test the initial state
EXPECT_EQ(0, hsm.version());
hsm.Initialize();
EXPECT_EQ(0, hsm.version());
hsm.Expect(hsm::kHsmEnter, hsm::kStateS0);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// Test IsIn
EXPECT_TRUE(hsm.IsIn(hsm::kStateS11));
EXPECT_TRUE(hsm.IsIn(hsm::kStateS1));
EXPECT_TRUE(hsm.IsIn(hsm::kStateS0));
EXPECT_FALSE(hsm.IsIn(hsm::kStateS2));
EXPECT_FALSE(hsm.IsIn(hsm::kStateS21));
EXPECT_FALSE(hsm.IsIn(hsm::kStateS211));
// State: S11, Event: A
hsm.ClearResults();
hsm.Handle(hsm::kEventA);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventA, hsm::kStateS11, 0);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(1, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: B
hsm.ClearResults();
hsm.Handle(hsm::kEventB);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventB, hsm::kStateS11,
NULL, 1);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(2, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: D
hsm.ClearResults();
hsm.Handle(hsm::kEventD);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventD, hsm::kStateS11,
NULL, 2);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(3, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: H (foo == true)
hsm.ClearResults();
EXPECT_TRUE(hsm.foo_);
hsm.Handle(hsm::kEventH);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventH, hsm::kStateS11,
NULL, 3);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(3, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
EXPECT_FALSE(hsm.foo_);
// State: S11, Event: H (foo == false)
hsm.ClearResults();
hsm.Handle(hsm::kEventH);
EXPECT_FALSE(hsm.foo_);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(3, hsm.version());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: C
hsm.ClearResults();
hsm.Handle(hsm::kEventC);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventC, hsm::kStateS11,
NULL, 3);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(4, hsm.version());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: A
hsm.ClearResults();
hsm.Handle(hsm::kEventA);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: B
hsm.ClearResults();
hsm.Handle(hsm::kEventB);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS21, hsm::kEventB, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: D
hsm.ClearResults();
hsm.Handle(hsm::kEventD);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS211, hsm::kEventD, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: E
hsm.ClearResults();
hsm.Handle(hsm::kEventE);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventE, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmExit, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: F
hsm.ClearResults();
hsm.Handle(hsm::kEventF);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS2, hsm::kEventF, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmExit, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: E
hsm.ClearResults();
hsm.Handle(hsm::kEventE);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventE, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: G
hsm.ClearResults();
hsm.Handle(hsm::kEventG);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS211, hsm::kEventG, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmExit, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: F
hsm.ClearResults();
hsm.Handle(hsm::kEventF);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventF, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: H (foo == false)
EXPECT_FALSE(hsm.foo_);
hsm.ClearResults();
hsm.Handle(hsm::kEventH);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS21, hsm::kEventH, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_TRUE(hsm.foo_);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: H (foo == true)
hsm.ClearResults();
hsm.Handle(hsm::kEventH);
EXPECT_TRUE(hsm.foo_);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: C
hsm.ClearResults();
hsm.Handle(hsm::kEventC);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS2, hsm::kEventC, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmExit, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// State: S11, Event: G
hsm.ClearResults();
hsm.Handle(hsm::kEventG);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS11, hsm::kEventG, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
// State: S211, Event: J
hsm.ClearResults();
hsm.Handle(hsm::kEventJ);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS0, hsm::kEventJ, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS211);
hsm.Expect(hsm::kHsmExit, hsm::kStateS21);
hsm.Expect(hsm::kHsmExit, hsm::kStateS2);
hsm.Expect(hsm::kHsmExit, hsm::kStateS0);
hsm.Expect(hsm::kHsmEnter, hsm::kStateT0);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateT0, hsm.state());
// State: T0, Event: J
hsm.ClearResults();
hsm.Handle(hsm::kEventJ);
hsm.Expect(hsm::kHsmHandled, hsm::kStateT0, hsm::kEventJ);
hsm.Expect(hsm::kHsmExit, hsm::kStateT0);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS0);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS11);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS11, hsm.state());
// Test that event data gets passed through to the handler
hsm.ClearResults();
void* data = reinterpret_cast<void*>(7);
hsm.Handle(hsm::kEventC, data);
hsm.Expect(hsm::kHsmHandled, hsm::kStateS1, hsm::kEventC, hsm::kStateS11,
data);
hsm.Expect(hsm::kHsmExit, hsm::kStateS11);
hsm.Expect(hsm::kHsmExit, hsm::kStateS1);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS2);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS21);
hsm.Expect(hsm::kHsmEnter, hsm::kStateS211);
EXPECT_EQ(0, hsm.results.size());
EXPECT_EQ(hsm::kStateS211, hsm.state());
}
} // namespace
} // namespace starboard