summaryrefslogtreecommitdiffstats
path: root/src/lib/util/tests/state_model_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/util/tests/state_model_unittest.cc')
-rw-r--r--src/lib/util/tests/state_model_unittest.cc916
1 files changed, 916 insertions, 0 deletions
diff --git a/src/lib/util/tests/state_model_unittest.cc b/src/lib/util/tests/state_model_unittest.cc
new file mode 100644
index 0000000..eaaba73
--- /dev/null
+++ b/src/lib/util/tests/state_model_unittest.cc
@@ -0,0 +1,916 @@
+// Copyright (C) 2013-2020 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <util/state_model.h>
+
+#include <gtest/gtest.h>
+
+using namespace std;
+using namespace isc;
+using namespace isc::util;
+
+namespace {
+
+/// @brief Test derivation of StateModel for exercising state model mechanics.
+///
+/// This class facilitates testing by making non-public methods accessible so
+/// they can be invoked directly in test routines. It implements a very
+/// rudimentary state model, sufficient to test the state model mechanics
+/// supplied by the base class.
+class StateModelTest : public StateModel, public testing::Test {
+public:
+
+ ///@brief StateModelTest states
+ ///@brief Fake state used for handler mapping tests.
+ static const int DUMMY_ST = SM_DERIVED_STATE_MIN + 1;
+
+ ///@brief Starting state for the test state model.
+ static const int READY_ST = SM_DERIVED_STATE_MIN + 2;
+
+ ///@brief State which simulates doing asynchronous work.
+ static const int DO_WORK_ST = SM_DERIVED_STATE_MIN + 3;
+
+ ///@brief State which finishes off processing.
+ static const int DONE_ST = SM_DERIVED_STATE_MIN + 4;
+
+ ///@brief State in which model is always paused.
+ static const int PAUSE_ALWAYS_ST = SM_DERIVED_STATE_MIN + 5;
+
+ ///@brief State in which model is paused at most once.
+ static const int PAUSE_ONCE_ST = SM_DERIVED_STATE_MIN + 6;
+
+ // StateModelTest events
+ ///@brief Event used to trigger initiation of asynchronous work.
+ static const int WORK_START_EVT = SM_DERIVED_EVENT_MIN + 1;
+
+ ///@brief Event issued when the asynchronous work "completes".
+ static const int WORK_DONE_EVT = SM_DERIVED_EVENT_MIN + 2;
+
+ ///@brief Event issued when all the work is done.
+ static const int ALL_DONE_EVT = SM_DERIVED_EVENT_MIN + 3;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int FORCE_UNDEFINED_ST_EVT = SM_DERIVED_EVENT_MIN + 4;
+
+ ///@brief Event used to trigger an attempt to transition to bad state
+ static const int SIMULATE_ERROR_EVT = SM_DERIVED_EVENT_MIN + 5;
+
+ ///@brief Event used to indicate that state machine is unpaused.
+ static const int UNPAUSED_EVT = SM_DERIVED_EVENT_MIN + 6;
+
+ /// @brief Constructor
+ ///
+ /// Parameters match those needed by StateModel.
+ StateModelTest() : dummy_called_(false), work_completed_(false),
+ failure_explanation_("") {
+ }
+ /// @brief Destructor
+ virtual ~StateModelTest() {
+ }
+
+ /// @brief Fetches the value of the dummy called flag.
+ bool getDummyCalled() {
+ return (dummy_called_);
+ }
+
+ /// @brief StateHandler for fake state, DummyState.
+ ///
+ /// It simply sets the dummy called flag to indicate that this method
+ /// was invoked.
+ void dummyHandler() {
+ dummy_called_ = true;
+ }
+
+ /// @brief Returns the failure explanation string.
+ ///
+ /// This value is set only via onModelFailure and it stores whatever
+ /// explanation that method was passed.
+ const std::string& getFailureExplanation() {
+ return (failure_explanation_);
+ }
+
+ /// @brief Returns indication of whether or not the model succeeded.
+ ///
+ /// If true, this indicates that the test model executed correctly through
+ /// to completion. The flag is only by the DONE_ST handler.
+ bool getWorkCompleted() {
+ return (work_completed_);
+ }
+
+ /// @brief State handler for the READY_ST.
+ ///
+ /// Serves as the starting state handler, it consumes the
+ /// START_EVT "transitioning" to the state, DO_WORK_ST and
+ /// sets the next event to WORK_START_EVT.
+ void readyHandler() {
+ switch(getNextEvent()) {
+ case START_EVT:
+ transition(DO_WORK_ST, WORK_START_EVT);
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "readyHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DO_WORK_ST.
+ ///
+ /// Simulates a state that starts some form of asynchronous work.
+ /// When next event is WORK_START_EVT it sets the status to pending
+ /// and signals the state model must "wait" for an event by setting
+ /// next event to NOP_EVT.
+ ///
+ /// When next event is IO_COMPLETED_EVT, it transitions to the state,
+ /// DONE_ST, and sets the next event to WORK_DONE_EVT.
+ void doWorkHandler() {
+ switch(getNextEvent()) {
+ case WORK_START_EVT:
+ postNextEvent(NOP_EVT);
+ break;
+ case WORK_DONE_EVT:
+ work_completed_ = true;
+ transition(DONE_ST, ALL_DONE_EVT);
+ break;
+ case FORCE_UNDEFINED_ST_EVT:
+ transition(9999, ALL_DONE_EVT);
+ break;
+ case SIMULATE_ERROR_EVT:
+ throw std::logic_error("Simulated Unexpected Error");
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for the DONE_ST.
+ ///
+ /// This is the last state in the model. Note that it sets the
+ /// status to completed and next event to NOP_EVT.
+ void doneWorkHandler() {
+ switch(getNextEvent()) {
+ case ALL_DONE_EVT:
+ endModel();
+ break;
+ default:
+ // its bogus
+ isc_throw(StateModelError, "doneWorkHandler:invalid event: "
+ << getContextStr());
+ }
+ }
+
+ /// @brief State handler for PAUSE_ALWAYS_ST and PAUSE_ONCE_ST.
+ void pauseHandler() {
+ postNextEvent(NOP_EVT);
+ }
+
+ /// @brief Construct the event dictionary.
+ virtual void defineEvents() {
+ // Invoke the base call implementation first.
+ StateModel::defineEvents();
+
+ // Define our events.
+ defineEvent(WORK_START_EVT, "WORK_START_EVT");
+ defineEvent(WORK_DONE_EVT , "WORK_DONE_EVT");
+ defineEvent(ALL_DONE_EVT, "ALL_DONE_EVT");
+ defineEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT");
+ defineEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT");
+ defineEvent(UNPAUSED_EVT, "UNPAUSED_EVT");
+ }
+
+ /// @brief Verify the event dictionary.
+ virtual void verifyEvents() {
+ // Invoke the base call implementation first.
+ StateModel::verifyEvents();
+
+ // Verify our events.
+ getEvent(WORK_START_EVT);
+ getEvent(WORK_DONE_EVT);
+ getEvent(ALL_DONE_EVT);
+ getEvent(FORCE_UNDEFINED_ST_EVT);
+ getEvent(SIMULATE_ERROR_EVT);
+ getEvent(UNPAUSED_EVT);
+ }
+
+ /// @brief Construct the state dictionary.
+ virtual void defineStates() {
+ // Invoke the base call implementation first.
+ StateModel::defineStates();
+
+ // Define our states.
+ defineState(DUMMY_ST, "DUMMY_ST",
+ std::bind(&StateModelTest::dummyHandler, this));
+
+ defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this));
+
+ defineState(DO_WORK_ST, "DO_WORK_ST",
+ std::bind(&StateModelTest::doWorkHandler, this));
+
+ defineState(DONE_ST, "DONE_ST",
+ std::bind(&StateModelTest::doneWorkHandler, this));
+
+ defineState(PAUSE_ALWAYS_ST, "PAUSE_ALWAYS_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ALWAYS);
+
+ defineState(PAUSE_ONCE_ST, "PAUSE_ONCE_ST",
+ std::bind(&StateModelTest::pauseHandler, this),
+ STATE_PAUSE_ONCE);
+ }
+
+ /// @brief Verify the state dictionary.
+ virtual void verifyStates() {
+ // Invoke the base call implementation first.
+ StateModel::verifyStates();
+
+ // Verify our states.
+ getStateInternal(DUMMY_ST);
+ getStateInternal(READY_ST);
+ getStateInternal(DO_WORK_ST);
+ getStateInternal(DONE_ST);
+ getStateInternal(PAUSE_ALWAYS_ST);
+ getStateInternal(PAUSE_ONCE_ST);
+ }
+
+ /// @brief Manually construct the event and state dictionaries.
+ /// This allows testing without running startModel.
+ void initDictionaries() {
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+ }
+
+ /// @brief Tests the event dictionary entry for the given event value.
+ bool checkEvent(const int value, const std::string& label) {
+ EventPtr event;
+ try {
+ event = getEvent(value);
+ EXPECT_TRUE(event);
+ EXPECT_EQ(value, event->getValue());
+ EXPECT_EQ(label, event->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /// @brief Tests the state dictionary entry for the given state value.
+ bool checkState(const int value, const std::string& label) {
+ EventPtr state;
+ try {
+ state = getState(value);
+ EXPECT_TRUE(state);
+ EXPECT_EQ(value, state->getValue());
+ EXPECT_EQ(label, state->getLabel());
+ } catch (const std::exception& ex) {
+ return false;
+ }
+
+ return true;
+ }
+
+
+ /// @brief Handler called when the model suffers an execution error.
+ virtual void onModelFailure(const std::string& explanation) {
+ failure_explanation_ = explanation;
+ }
+
+ /// @brief Indicator of whether or not the DUMMY_ST handler has been called.
+ bool dummy_called_;
+
+ /// @brief Indicator of whether or not DONE_ST handler has been called.
+ bool work_completed_;
+
+ /// @brief Stores the failure explanation
+ std::string failure_explanation_;
+};
+
+// Declare them so gtest can see them.
+const int StateModelTest::DUMMY_ST;
+const int StateModelTest::READY_ST;
+const int StateModelTest::DO_WORK_ST;
+const int StateModelTest::DONE_ST;
+const int StateModelTest::WORK_START_EVT;
+const int StateModelTest::WORK_DONE_EVT;
+const int StateModelTest::ALL_DONE_EVT;
+const int StateModelTest::PAUSE_ALWAYS_ST;
+const int StateModelTest::PAUSE_ONCE_ST;
+
+/// @brief Checks the fundamentals of defining and retrieving events.
+TEST_F(StateModelTest, eventDefinition) {
+ // After construction, the event dictionary should be empty. Verify that
+ // getEvent will throw when event is not defined.
+ EXPECT_THROW(getEvent(NOP_EVT), StateModelError);
+
+ // Verify that we can add a handler to the map.
+ ASSERT_NO_THROW(defineEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we can find the event by value and its content is correct.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+
+ // Verify that we cannot add a duplicate.
+ ASSERT_THROW(defineEvent(NOP_EVT, "NOP_EVT"), StateModelError);
+
+ // Verify that we can still find the event.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+}
+
+/// @brief Tests event dictionary construction and verification.
+TEST_F(StateModelTest, eventDictionary) {
+ // After construction, the event dictionary should be empty.
+ // Make sure that verifyEvents() throws.
+ EXPECT_THROW(verifyEvents(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ EXPECT_NO_THROW(defineEvents());
+ EXPECT_NO_THROW(verifyEvents());
+
+ // Verify base class events are defined.
+ EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+ EXPECT_TRUE(checkEvent(START_EVT, "START_EVT"));
+ EXPECT_TRUE(checkEvent(END_EVT, "END_EVT"));
+ EXPECT_TRUE(checkEvent(FAIL_EVT, "FAIL_EVT"));
+
+ // Verify stub class events are defined.
+ EXPECT_TRUE(checkEvent(WORK_START_EVT, "WORK_START_EVT"));
+ EXPECT_TRUE(checkEvent(WORK_DONE_EVT, "WORK_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(ALL_DONE_EVT, "ALL_DONE_EVT"));
+ EXPECT_TRUE(checkEvent(FORCE_UNDEFINED_ST_EVT, "FORCE_UNDEFINED_ST_EVT"));
+ EXPECT_TRUE(checkEvent(SIMULATE_ERROR_EVT, "SIMULATE_ERROR_EVT"));
+
+ // Verify that undefined events are handled correctly.
+ EXPECT_THROW(getEvent(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getEventLabel(9999));
+}
+
+/// @brief General testing of event context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, eventContextAccessors) {
+ // Construct the event definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+
+ // Verify the post-construction values.
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent which will update both next event and last event.
+ EXPECT_NO_THROW(postNextEvent(START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call setEvent again.
+ EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(WORK_START_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+
+ // Verify that posting an undefined event throws.
+ EXPECT_THROW(postNextEvent(9999), StateModelError);
+}
+
+/// @brief Tests the fundamental methods used for state handler mapping.
+/// Verifies the ability to search for and add entries in the state handler map.
+TEST_F(StateModelTest, stateDefinition) {
+ // After construction, the state dictionary should be empty. Verify that
+ // getState will throw when, state is not defined.
+ EXPECT_THROW(getState(READY_ST), StateModelError);
+
+ // Verify that we can add a state to the dictionary.
+ ASSERT_NO_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::dummyHandler,
+ this)));
+
+ // Verify that we can find the state by its value.
+ StatePtr state;
+ EXPECT_NO_THROW(state = getState(READY_ST));
+ EXPECT_TRUE(state);
+
+ // Verify the state's value and label.
+ EXPECT_EQ(READY_ST, state->getValue());
+ EXPECT_EQ("READY_ST", state->getLabel());
+
+ // Now verify that retrieved state's handler executes the correct method.
+ // Make sure the dummy called flag is false prior to invocation.
+ EXPECT_FALSE(getDummyCalled());
+
+ // Invoke the state's handler.
+ EXPECT_NO_THROW(state->run());
+
+ // Verify the dummy called flag is now true.
+ EXPECT_TRUE(getDummyCalled());
+
+ // Verify that we cannot add a duplicate.
+ EXPECT_THROW(defineState(READY_ST, "READY_ST",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // Verify that we can still find the state.
+ EXPECT_NO_THROW(getState(READY_ST));
+}
+
+/// @brief Tests state dictionary initialization and validation.
+/// This tests the basic concept of state dictionary initialization and
+/// verification by manually invoking the methods normally called by startModel.
+TEST_F(StateModelTest, stateDictionary) {
+ // Verify that the map validation throws prior to the dictionary being
+ // initialized.
+ EXPECT_THROW(verifyStates(), StateModelError);
+
+ // Construct the dictionary and verify it.
+ ASSERT_NO_THROW(defineStates());
+ EXPECT_NO_THROW(verifyStates());
+
+ // Verify the base class states.
+ EXPECT_TRUE(checkState(NEW_ST, "NEW_ST"));
+ EXPECT_TRUE(checkState(END_ST, "END_ST"));
+
+ // Verify stub class states.
+ EXPECT_TRUE(checkState(DUMMY_ST, "DUMMY_ST"));
+ EXPECT_TRUE(checkState(READY_ST, "READY_ST"));
+ EXPECT_TRUE(checkState(DO_WORK_ST, "DO_WORK_ST"));
+ EXPECT_TRUE(checkState(DONE_ST, "DONE_ST"));
+
+ // Verify that undefined states are handled correctly.
+ EXPECT_THROW(getState(9999), StateModelError);
+ EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getStateLabel(9999));
+}
+
+/// @brief General testing of state context accessors.
+/// Most if not all of these are also tested as a byproduct off larger tests.
+TEST_F(StateModelTest, stateContextAccessors) {
+ // setState will throw unless we initialize the handler map.
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // Verify post-construction state values.
+ EXPECT_EQ(NEW_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState which will update both state and previous state.
+ EXPECT_NO_THROW(setState(READY_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(READY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+
+ // Call setState again.
+ EXPECT_NO_THROW(setState(DO_WORK_ST));
+
+ // Verify the values are what we expect.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(READY_ST, getPrevState());
+
+ // Verify that calling setState with an state that has no handler
+ // will throw.
+ EXPECT_THROW(setState(-1), StateModelError);
+
+ // Verify that calling setState with NEW_ST is ok.
+ EXPECT_NO_THROW(setState(NEW_ST));
+
+ // Verify that calling setState with END_ST is ok.
+ EXPECT_NO_THROW(setState(END_ST));
+
+ // Verify that calling setState with an undefined state throws.
+ EXPECT_THROW(setState(9999), StateModelError);
+}
+
+/// @brief Checks that invoking runModel prior to startModel is not allowed.
+TEST_F(StateModelTest, runBeforeStart) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Attempt to call runModel before startModel. This should result in an
+ // orderly model failure.
+ ASSERT_NO_THROW(runModel(START_EVT));
+
+ // Check that state and event are correct.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+}
+
+/// @brief Tests that the endModel may be used to transition the model to
+/// a normal conclusion.
+TEST_F(StateModelTest, transitionWithEnd) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the abortModel may be used to transition the model to
+/// failed conclusion.
+TEST_F(StateModelTest, transitionWithAbort) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Verify that state and event members are as expected.
+ EXPECT_EQ(DUMMY_ST, getCurrState());
+ EXPECT_EQ(NEW_ST, getPrevState());
+ EXPECT_EQ(START_EVT, getNextEvent());
+ EXPECT_EQ(NOP_EVT, getLastEvent());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // Verify state and event members are correctly set.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(DUMMY_ST, getPrevState());
+ EXPECT_EQ(FAIL_EVT, getNextEvent());
+ EXPECT_EQ(START_EVT, getLastEvent());
+}
+
+/// @brief Tests that the boolean indicators for on state entry and exit
+/// work properly.
+TEST_F(StateModelTest, doFlags) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // Verify that "do" flags are false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // We are transitioning states, so "do" flags should be true.
+ EXPECT_TRUE(doOnEntry());
+ EXPECT_TRUE(doOnExit());
+
+ // "do" flags are one-shots, so they should now both be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+ // call transition to re-enter same state, "do" flags should be false.
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // "do" flags should be false.
+ EXPECT_FALSE(doOnEntry());
+ EXPECT_FALSE(doOnExit());
+
+}
+
+/// @brief Verifies that the model status methods accurately reflect the model
+/// status. It also verifies that the dictionaries can be modified before
+/// the model is running but not after.
+TEST_F(StateModelTest, statusMethods) {
+ // Init dictionaries manually, normally done by startModel.
+ initDictionaries();
+
+ // After construction, state model is "new", all others should be false.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Verify that events and states can be added before the model is started.
+ EXPECT_NO_THROW(defineEvent(9998, "9998"));
+ EXPECT_NO_THROW(defineState(9998, "9998",
+ std::bind(&StateModelTest::readyHandler,
+ this)));
+
+ // "START" the model.
+ // Fake out starting the model by calling transition to move from NEW_ST
+ // to DUMMY_ST with START_EVT. If we used startModel this would blow by
+ // the status of "running" but not "waiting".
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+
+ // Verify that events and states cannot be added after the model is started.
+ EXPECT_THROW(defineEvent(9999, "9999"), StateModelError);
+ EXPECT_THROW(defineState(9999, "9999",
+ std::bind(&StateModelTest::readyHandler, this)),
+ StateModelError);
+
+ // The state and event combos set above, should show the model as
+ // "running", all others should be false.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // call transition to submit NOP_EVT to current state, DUMMY_ST.
+ EXPECT_NO_THROW(transition(DUMMY_ST, NOP_EVT));
+
+ // Verify the status methods are correct: with next event set to NOP_EVT,
+ // model should be "running" and "waiting".
+ EXPECT_FALSE(isModelNew());
+ EXPECT_TRUE(isModelRunning());
+ EXPECT_TRUE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(endModel());
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_FALSE(didModelFail());
+}
+
+/// @brief Tests that the model status methods are correct after a model
+/// failure.
+TEST_F(StateModelTest, statusMethodsOnFailure) {
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+ EXPECT_NO_THROW(transition(DUMMY_ST, START_EVT));
+
+ // Call endModel to transition us to the end of the model.
+ EXPECT_NO_THROW(abortModel("test invocation"));
+
+ // With state set to END_ST, model should be done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+}
+
+/// @brief Checks that the context strings accurately reflect context and
+/// are safe to invoke.
+TEST_F(StateModelTest, contextStrs) {
+ // Verify context methods do not throw prior to dictionary init.
+ ASSERT_NO_THROW(getContextStr());
+ ASSERT_NO_THROW(getPrevContextStr());
+
+ // Construct the event and state definitions, normally done by startModel.
+ ASSERT_NO_THROW(defineEvents());
+ ASSERT_NO_THROW(verifyEvents());
+ ASSERT_NO_THROW(defineStates());
+ ASSERT_NO_THROW(verifyStates());
+
+ // transition uses setState and setEvent, testing it tests all three.
+ EXPECT_NO_THROW(transition(READY_ST, START_EVT));
+
+ // Verify the current context string depicts correct state and event.
+ std::string ctx_str;
+ ASSERT_NO_THROW(ctx_str = getContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(READY_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(START_EVT)));
+
+ // Verify the previous context string depicts correct state and event.
+ ASSERT_NO_THROW(ctx_str = getPrevContextStr());
+ EXPECT_NE(std::string::npos, ctx_str.find(getStateLabel(NEW_ST)));
+ EXPECT_NE(std::string::npos, ctx_str.find(getEventLabel(NOP_EVT)));
+}
+
+/// @brief Tests that undefined states are handled gracefully.
+/// This test verifies that attempting to transition to an undefined state,
+/// which constitutes a model violation, results in an orderly model failure.
+TEST_F(StateModelTest, undefinedState) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel. This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(FORCE_UNDEFINED_ST_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that an unexpected exception thrown by a state handler is
+/// handled gracefully. State models are supposed to account for and handle
+/// all errors that they actions (i.e. handlers) may cause. In the event they
+/// do not, this constitutes a model violation. This test verifies such
+/// violations are handled correctly and result in an orderly model failure.
+TEST_F(StateModelTest, unexpectedError) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Resume the model with next event set to cause the DO_WORK_ST handler
+ // to transition to an undefined state. This should cause it to return
+ // without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(SIMULATE_ERROR_EVT));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Tests that undefined events are handled gracefully.
+/// This test verifies that submitting an undefined event to the state machine
+/// results, which constitutes a model violation, results in an orderly model
+/// failure.
+TEST_F(StateModelTest, undefinedEvent) {
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // First, lets execute the state model to a known valid point, by
+ // calling startModel with a start state of READY_ST.
+ // This should run the model through to DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify we are in the state of DO_WORK_ST with event of NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Attempting to post an undefined event within runModel should cause it
+ // to return without throwing and yield a failed model.
+ EXPECT_NO_THROW(runModel(9999));
+
+ // Verify that status methods are correct: model is done but failed.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+ EXPECT_TRUE(didModelFail());
+
+ // Verify that failure explanation is not empty.
+ EXPECT_FALSE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is still false.
+ EXPECT_FALSE(getWorkCompleted());
+}
+
+/// @brief Test the basic mechanics of state model execution.
+/// This test exercises the ability to execute state model from start to
+/// finish, including the handling of a asynchronous IO operation.
+TEST_F(StateModelTest, stateModelTest) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Launch the transaction by calling startModel. The state model
+ // should run up until the simulated async work operation is initiated
+ // in DO_WORK_ST.
+ ASSERT_NO_THROW(startModel(READY_ST));
+
+ // Verify that we are now in state of DO_WORK_ST, the last event was
+ // WORK_START_EVT, the next event is NOP_EVT.
+ EXPECT_EQ(DO_WORK_ST, getCurrState());
+ EXPECT_EQ(WORK_START_EVT, getLastEvent());
+ EXPECT_EQ(NOP_EVT, getNextEvent());
+
+ // Simulate completion of async work completion by resuming runModel with
+ // an event of WORK_DONE_EVT.
+ ASSERT_NO_THROW(runModel(WORK_DONE_EVT));
+
+ // Verify that the state model has progressed through to completion:
+ // it is in the DONE_ST, the status is ST_COMPLETED, and the next event
+ // is NOP_EVT.
+ EXPECT_EQ(END_ST, getCurrState());
+ EXPECT_EQ(END_EVT, getNextEvent());
+
+ // Verify that status methods are correct: model done.
+ EXPECT_FALSE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_TRUE(isModelDone());
+
+ // Verify that failure explanation is empty.
+ EXPECT_TRUE(getFailureExplanation().empty());
+
+ // Verify that work completed flag is true.
+ EXPECT_TRUE(getWorkCompleted());
+}
+
+// This test verifies the pausing and un-pausing capabilities of the state
+// model.
+TEST_F(StateModelTest, stateModelPause) {
+ // Verify that status methods are correct: model is new.
+ EXPECT_TRUE(isModelNew());
+ EXPECT_FALSE(isModelRunning());
+ EXPECT_FALSE(isModelWaiting());
+ EXPECT_FALSE(isModelDone());
+ EXPECT_FALSE(isModelPaused());
+
+ // Verify that the failure explanation is empty and work is not done.
+ EXPECT_TRUE(getFailureExplanation().empty());
+ EXPECT_FALSE(getWorkCompleted());
+
+ // Transition straight to the state in which the model should always
+ // pause.
+ ASSERT_NO_THROW(startModel(PAUSE_ALWAYS_ST));
+
+ // Verify it was successful and that the model is paused.
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Run the model again. It should still be paused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should be paused at most once.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // The model should still be paused until explicitly unpaused.
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ unpauseModel();
+
+ // Transition back to the first state. The model should pause again.
+ transition(PAUSE_ALWAYS_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ ASSERT_NO_THROW(runModel(NOP_EVT));
+ EXPECT_EQ(PAUSE_ALWAYS_ST, getCurrState());
+ EXPECT_TRUE(isModelPaused());
+
+ // Unpause the model and transition to the state in which the model
+ // should pause only once. This time it should not pause.
+ unpauseModel();
+ transition(PAUSE_ONCE_ST, NOP_EVT);
+ EXPECT_EQ(PAUSE_ONCE_ST, getCurrState());
+ EXPECT_FALSE(isModelPaused());
+}
+
+}