Parcourir la source

[3156] Review comments addressed: part II - d2::StateMode revisions.

This commit replaces the state specification mechanism in d2::StateModel
with one derived from LabeledValue and LabeledValueSet.
Thomas Markwalder il y a 11 ans
Parent
commit
86050220a6

+ 1 - 1
src/bin/d2/d2_messages.mes

@@ -253,7 +253,7 @@ in event loop.
 This is informational message issued when the application has been instructed
 to shut down by the controller.
 
-% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1 , %2
+% DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR application encountered an unexpected error while carrying out a NameChangeRequest: %1
 This is error message issued when the application fails to process a
 NameChangeRequest correctly. Some or all of the DNS updates requested as part
 of this update did not succeed. This is a programmatic error and should be

+ 27 - 34
src/bin/d2/nc_trans.cc

@@ -83,7 +83,10 @@ NameChangeTransaction::operator()(DNSClient::Status status) {
 
 void
 NameChangeTransaction::defineEvents() {
+    // Call superclass impl first.
     StateModel::defineEvents();
+
+    // Define NCT events.
     defineEvent(SELECT_SERVER_EVT, "SELECT_SERVER_EVT");
     defineEvent(SERVER_SELECTED_EVT, "SERVER_SELECTED_EVT");
     defineEvent(SERVER_IO_ERROR_EVT, "SERVER_IO_ERROR_EVT");
@@ -95,7 +98,10 @@ NameChangeTransaction::defineEvents() {
 
 void
 NameChangeTransaction::verifyEvents() {
+    // Call superclass impl first.
     StateModel::verifyEvents();
+
+    // Verify NCT events.
     getEvent(SELECT_SERVER_EVT);
     getEvent(SERVER_SELECTED_EVT);
     getEvent(SERVER_IO_ERROR_EVT);
@@ -106,17 +112,31 @@ NameChangeTransaction::verifyEvents() {
 }
 
 void
-NameChangeTransaction::verifyStateHandlerMap() {
-    getStateHandler(READY_ST);
-    getStateHandler(SELECTING_FWD_SERVER_ST);
-    getStateHandler(SELECTING_REV_SERVER_ST);
-    getStateHandler(PROCESS_TRANS_OK_ST);
-    getStateHandler(PROCESS_TRANS_FAILED_ST);
+NameChangeTransaction::defineStates() {
+    // Call superclass impl first.
+    StateModel::defineStates();
+    // This class is "abstract" in that it does not supply handlers for its
+    // states, derivations must do that therefore they must define them.
+}
+
+void
+NameChangeTransaction::verifyStates() {
+    // Call superclass impl first.
+    StateModel::verifyStates();
+
+    // Verify NCT states. This ensures that derivations provide the handlers.
+    getState(READY_ST);
+    getState(SELECTING_FWD_SERVER_ST);
+    getState(SELECTING_REV_SERVER_ST);
+    getState(PROCESS_TRANS_OK_ST);
+    getState(PROCESS_TRANS_FAILED_ST);
 }
 
 void
-NameChangeTransaction::onModelFailure() {
+NameChangeTransaction::onModelFailure(const std::string& explanation) {
     setNcrStatus(dhcp_ddns::ST_FAILED);
+    LOG_ERROR(dctl_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
+                  .arg(explanation);
 }
 
 void
@@ -226,32 +246,5 @@ NameChangeTransaction::getReverseChangeCompleted() const {
     return (reverse_change_completed_);
 }
 
-const char*
-NameChangeTransaction::getStateLabel(const int state) const {
-    const char* str = "Unknown";
-    switch(state) {
-    case READY_ST:
-        str = "NameChangeTransaction::READY_ST";
-        break;
-    case SELECTING_FWD_SERVER_ST:
-        str = "NameChangeTransaction::SELECTING_FWD_SERVER_ST";
-        break;
-    case SELECTING_REV_SERVER_ST:
-        str = "NameChangeTransaction::SELECTING_REV_SERVER_ST";
-        break;
-    case PROCESS_TRANS_OK_ST:
-        str = "NameChangeTransaction::PROCESS_TRANS_OK_ST";
-        break;
-    case PROCESS_TRANS_FAILED_ST:
-        str = "NameChangeTransaction::PROCESS_TRANS_FAILED_ST";
-        break;
-    default:
-        str = StateModel::getStateLabel(state);
-        break;
-    }
-
-    return (str);
-}
-
 } // namespace isc::d2
 } // namespace isc

+ 23 - 84
src/bin/d2/nc_trans.h

@@ -182,12 +182,12 @@ public:
     virtual void operator()(DNSClient::Status status);
 
 protected:
-    /// @brief Adds events defined by NameChangeTransaction to the event set. 
+    /// @brief Adds events defined by NameChangeTransaction to the event set.
     ///
     /// This method adds the events common to NCR transaction processing to
     /// the set of define events.  It invokes the superclass's implementation
     /// first to maitain the hierarchical chain of event defintion.
-    /// Derivations of NameChangeTransaction must invoke its implementation 
+    /// Derivations of NameChangeTransaction must invoke its implementation
     /// in like fashion.
     ///
     /// @throw StateModelError if an event definition is invalid or a duplicate.
@@ -198,58 +198,33 @@ protected:
     /// This method verifies that the events defined by both the superclass and
     /// this class are defined.  As with defineEvents, this method calls the
     /// superclass's implementation first, to verify events defined by it and
-    /// then this implementation to verify events defined by 
+    /// then this implementation to verify events defined by
     /// NameChangeTransaction.
     ///
-    /// @throw StateModelError if an event value is undefined. 
+    /// @throw StateModelError if an event value is undefined.
     virtual void verifyEvents();
 
-    /// @brief Populates the map of state handlers.
+    /// @brief Adds states defined by NameChangeTransaction to the state set.
     ///
-    /// This method is used by derivations to construct a map of states to
-    /// their appropriate state handlers (bound method pointers).  It is
-    /// invoked at the beginning of startTransaction().
+    /// This method adds the states common to NCR transaction processing to
+    /// the dictionary of states.  It invokes the superclass's implementation
+    /// first to maitain the hierarchical chain of state defintion.
+    /// Derivations of NameChangeTransaction must invoke its implementation
+    /// in like fashion.
     ///
-    /// Implementations should use the addToMap() method add entries to
-    /// the map.
-    /// @todo This method should be pure virtual but until there are
-    /// derivations for the update manager to use, we will provide a
-    /// temporary empty, implementation.  If we make it pure virtual now
-    /// D2UpdateManager will not compile.
-    virtual void initStateHandlerMap() {};
+    /// @throw StateModelError if an state definition is invalid or a duplicate.
+    virtual void defineStates();
 
-    /// @brief Validates the contents of the state handler map.
-    ///
-    /// This method is invoked immediately after initStateHandlerMap and
-    /// verifies that the state map includes handlers for all of the states
-    /// defined by NameChangeTransaction.  If the map is determined to be
-    /// invalid this method will throw a NameChangeTransactionError.
-    ///
-    /// Derivations should ALSO provide an implementation of this method. That
-    /// implementation should invoke this method, as well as verifying that all
-    /// of the derivation's states have handlers.
-    ///
-    /// A derivation's implementation of this function might look as follows:
-    ///
-    /// @code
+    /// @brief Validates the contents of the set of states.
     ///
-    ///     class DerivedTrans : public NameChangeTransaction {
-    ///         :
-    ///         void verifyStateHandlerMap() {
-    ///             // Verify derivations' states:
-    ///             getStateHandler(SOME_DERIVED_STATE_1);
-    ///             getStateHandler(SOME_DERIVED_STATE_2);
-    ///             :
-    ///             getStateHandler(SOME_DERIVED_STATE_N);
-    ///
-    ///             // Verify handlers for NameChangeTransaction states:
-    ///             NameChangeTransaction::verifyStateHandlerMap();
-    ///         }
-    ///
-    /// @endcode
+    /// This method verifies that the states defined by both the superclass and
+    /// this class are defined.  As with defineStates, this method calls the
+    /// superclass's implementation first, to verify states defined by it and
+    /// then this implementation to verify states defined by
+    /// NameChangeTransaction.
     ///
-    /// @throw NameChangeTransactionError if the map is invalid.
-    virtual void verifyStateHandlerMap();
+    /// @throw StateModelError if an event value is undefined.
+    virtual void verifyStates();
 
     /// @brief Handler for fatal model execution errors.
     ///
@@ -260,7 +235,9 @@ protected:
     /// error occurs the transaction is deemed inoperable, and futher model
     /// execution cannot be performed.  It marks the transaction as failed by
     /// setting the NCR status to dhcp_ddns::ST_FAILED
-    virtual void onModelFailure();
+    ///
+    /// @param explanation is text detailing the error
+    virtual void onModelFailure(const std::string& explanation);
 
     /// @brief Sets the update status to the given status value.
     ///
@@ -388,44 +365,6 @@ public:
     /// @return True if the reverse change has been completed, false otherwise.
     bool getReverseChangeCompleted() const;
 
-    /// @brief Converts a state value into a text label.
-    ///
-    /// This method supplies labels for NameChangeTransaction's predefined
-    /// states. It is declared virtual to allow derivations to embed a call to
-    /// this method within their own implementation which would define labels
-    /// for its states.  An example implementation might look like the
-    /// following:
-    /// @code
-    ///
-    /// class DerivedTrans : public NameChangeTransaction {
-    ///     :
-    ///     static const int EXAMPLE_1_ST = NCT_STATE_MAX + 1;
-    ///     :
-    ///     const char* getStateLabel(const int state) const {
-    ///         const char* str = "Unknown";
-    ///         switch(state) {
-    ///         case EXAMPLE_1_ST:
-    ///             str = "DerivedTrans::EXAMPLE_1_ST";
-    ///             break;
-    ///         :
-    ///         default:
-    ///             // Not a derived state, pass it to NameChangeTransaction's
-    ///             // method.
-    ///             str = NameChangeTransaction::getStateLabel(state);
-    ///             break;
-    ///         }
-    ///
-    ///         return (str);
-    ///      }
-    ///
-    /// @endcode
-    ///
-    /// @param state is the state for which a label is desired.
-    ///
-    /// @return Returns a const char* containing the state label or
-    /// "Unknown" if the value cannot be mapped.
-    virtual const char* getStateLabel(const int state) const;
-
 private:
     /// @brief The IOService which should be used to for IO processing.
     isc::asiolink::IOService& io_service_;

+ 136 - 82
src/bin/d2/state_model.cc

@@ -20,6 +20,53 @@
 namespace isc {
 namespace d2 {
 
+/********************************** State *******************************/
+
+State::State(const int value, const char* label, StateHandler handler)
+        : LabeledValue(value, label), handler_(handler) {
+}
+
+State::~State() {
+}
+
+void
+State::run() {
+        (handler_)();
+}
+
+/********************************** StateSet *******************************/
+
+StateSet::StateSet() {
+}
+
+StateSet::~StateSet() {
+}
+
+void
+StateSet::add(const int value, const char* label, StateHandler handler) {
+    try {
+        LabeledValueSet::add(LabeledValuePtr(new State(value, label, handler)));
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "StateSet: cannot add state :" << ex.what());
+    }
+
+}
+
+const StatePtr
+StateSet::getState(int value) {
+    if (!isDefined(value)) {
+        isc_throw(StateModelError," StateSet: state is undefined");
+    }
+
+    // Since we have to use dynamic casting, to get a state pointer
+    // we can't return a reference.
+    StatePtr state = boost::dynamic_pointer_cast<State>(get(value));
+    return (state);
+}
+
+/********************************** StateModel *******************************/
+
+
 // Common state model states
 const int StateModel::NEW_ST;
 const int StateModel::END_ST;
@@ -34,10 +81,10 @@ const int StateModel::FAIL_EVT;
 
 const int StateModel::SM_EVENT_MAX;
 
-StateModel::StateModel() : state_handlers_(), state_(NEW_ST),
-                          prev_state_(NEW_ST), last_event_(NOP_EVT),
-                          next_event_(NOP_EVT), on_entry_flag_(false),
-                          on_exit_flag_(false) {
+StateModel::StateModel() : events_(), states_(), dictionaries_initted_(false),
+                          curr_state_(NEW_ST), prev_state_(NEW_ST),
+                          last_event_(NOP_EVT), next_event_(NOP_EVT),
+                          on_entry_flag_(false), on_exit_flag_(false) {
 }
 
 StateModel::~StateModel(){
@@ -45,6 +92,7 @@ StateModel::~StateModel(){
 
 void
 StateModel::startModel(const int start_state) {
+    // First let's build and verify the dictionary of events.
     try {
         defineEvents();
         verifyEvents();
@@ -52,20 +100,29 @@ StateModel::startModel(const int start_state) {
         isc_throw(StateModelError, "Event set is invalid: " << ex.what());
     }
 
-    // Initialize the state handler map first.
-    initStateHandlerMap();
+    // Next let's build and verify the dictionary of states.
+    try {
+        defineStates();
+        verifyStates();
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "State set is invalid: " << ex.what());
+    }
 
-    // Test validity of the handler map. This provides an opportunity to
-    // sanity check the map prior to attempting to execute the model.
-    verifyStateHandlerMap();
+    // Record that we are good to go.
+    dictionaries_initted_ = true;
 
-    // Set the current state to startng state and enter the run loop.
+    // Set the current state to starting state and enter the run loop.
     setState(start_state);
     runModel(START_EVT);
 }
 
 void
 StateModel::runModel(unsigned int run_event) {
+    /// If the dictionaries aren't built bail out.
+    if (!dictionaries_initted_) {
+       abortModel("runModel invoked before model has been initialized");
+    }
+
     try {
         // Seed the loop with the given event as the next to process.
         postNextEvent(run_event);
@@ -73,32 +130,35 @@ StateModel::runModel(unsigned int run_event) {
             // Invoke the current state's handler.  It should consume the
             // next event, then determine what happens next by setting
             // current state and/or the next event.
-            (getStateHandler(state_))();
+            getState(curr_state_)->run();
 
             // Keep going until a handler sets next event to a NOP_EVT.
         } while (!isModelDone() && getNextEvent() != NOP_EVT);
     }
     catch (const std::exception& ex) {
-        // The model has suffered an unexpected exception. This constitutes a
+        // The model has suffered an unexpected exception. This constitutes
         // a model violation and indicates a programmatic shortcoming.
         // In theory, the model should account for all error scenarios and
-        // deal with them accordingly.  Log it and transition to END_ST with
-        // FAILED_EVT via abortModel.
-        LOG_ERROR(dctl_logger, DHCP_DDNS_STATE_MODEL_UNEXPECTED_ERROR)
-                  .arg(ex.what()).arg(getContextStr());
-        abortModel();
+        // deal with them accordingly.  Transition to END_ST with FAILED_EVT
+        // via abortModel.
+        abortModel(ex.what());
     }
 }
 
 void
+StateModel::nopStateHandler() {
+}
+
+
+void
 StateModel::defineEvent(unsigned int event_value, const char* label) {
     if (!isModelNew()) {
-        // Don't allow for self-modifying maps.
+        // Don't allow for self-modifying models.
         isc_throw(StateModelError, "Events may only be added to a new model."
                    << event_value << " - " << label);
     }
 
-    // add method may throw on duplicate or empty label.  
+    // Attempt to add the event to the set.
     try {
         events_.add(event_value, label);
     } catch (const std::exception& ex) {
@@ -109,52 +169,41 @@ StateModel::defineEvent(unsigned int event_value, const char* label) {
 const EventPtr&
 StateModel::getEvent(unsigned int event_value) {
     if (!events_.isDefined(event_value)) {
-        isc_throw(StateModelError, 
+        isc_throw(StateModelError,
                   "Event value is not defined:" << event_value);
     }
 
     return (events_.get(event_value));
 }
 
-StateHandler
-StateModel::getStateHandler(unsigned int state) {
-    StateHandlerMap::iterator it = state_handlers_.find(state);
-    // It is wrong to try to find a state that isn't mapped.
-    if (it == state_handlers_.end()) {
-        isc_throw(StateModelError, "No handler for state: "
-                  << state << " - " << getStateLabel(state));
-    }
-
-    return ((*it).second);
-}
-
 void
-StateModel::addToStateHandlerMap(unsigned int state, StateHandler handler) {
+StateModel::defineState(unsigned int state_value, const char* label,
+    StateHandler handler) {
     if (!isModelNew()) {
         // Don't allow for self-modifying maps.
-        isc_throw(StateModelError,
-                  "State handler mappings can only be added to a new model."
-                   << state << " - " << getStateLabel(state));
+        isc_throw(StateModelError, "States may only be added to a new model."
+                   << state_value << " - " << label);
     }
 
-    StateHandlerMap::iterator it = state_handlers_.find(state);
-    // Check for a duplicate attempt.  State's can't have more than one.
-    if (it != state_handlers_.end()) {
-        isc_throw(StateModelError,
-                  "Attempted duplicate entry in state handler map, state: "
-                   << state << " - " << getStateLabel(state));
+    // Attempt to add the state to the set.
+    try {
+        states_.add(state_value, label, handler);
+    } catch (const std::exception& ex) {
+        isc_throw(StateModelError, "Error adding state: " << ex.what());
     }
+}
 
-    // Do not allow handlers for special states fo NEW_ST and END_ST.
-    if (state == NEW_ST || state == END_ST) {
-        isc_throw(StateModelError, "A handler is not supported for state: "
-                  << state << " - " << getStateLabel(state));
+const StatePtr
+StateModel::getState(unsigned int state_value) {
+    if (!states_.isDefined(state_value)) {
+        isc_throw(StateModelError,
+                  "State value is not defined:" << state_value);
     }
 
-    state_handlers_[state] = handler;
+    return (states_.getState(state_value));
 }
 
-void 
+void
 StateModel::defineEvents() {
     defineEvent(NOP_EVT, "NOP_EVT");
     defineEvent(START_EVT, "START_EVT");
@@ -170,6 +219,19 @@ StateModel::verifyEvents() {
     getEvent(FAIL_EVT);
 }
 
+void
+StateModel::defineStates() {
+    defineState(NEW_ST, "NEW_ST",
+                boost::bind(&StateModel::nopStateHandler, this));
+    defineState(END_ST, "END_ST",
+                boost::bind(&StateModel::nopStateHandler, this));
+}
+
+void
+StateModel::verifyStates() {
+    getState(NEW_ST);
+    getState(END_ST);
+}
 
 void
 StateModel::transition(unsigned int state, unsigned int event) {
@@ -183,33 +245,37 @@ StateModel::endModel() {
 }
 
 void
-StateModel::abortModel() {
+StateModel::abortModel(const std::string& explanation) {
     transition(END_ST, FAIL_EVT);
-    onModelFailure();
+
+    std::ostringstream stream ;
+    stream << explanation << " : " << getContextStr();
+    onModelFailure(stream.str());
 }
 
 void
 StateModel::setState(unsigned int state) {
-    // If the new state isn't NEW_ST or END_ST, make sure it has a handler.
-    if ((state && state != NEW_ST && state != END_ST)
-        && (state_handlers_.end() == state_handlers_.find(state))) {
-        isc_throw(StateModelError, "Attempt to set state to invalid stat :"
-                  << state << "=" << getStateLabel(state));
+    if (state != END_ST && !states_.isDefined(state)) {
+        isc_throw(StateModelError,
+                  "Attempt to set state to an undefined value: " << state );
     }
 
-    prev_state_ = state_;
-    state_ = state;
+    prev_state_ = curr_state_;
+    curr_state_ = state;
 
     // Set the "do" flags if we are transitioning.
-    on_entry_flag_ = ((state != END_ST) && (prev_state_ != state_));
+    on_entry_flag_ = ((state != END_ST) && (prev_state_ != curr_state_));
+
     // At this time they are calculated the same way.
     on_exit_flag_ = on_entry_flag_;
 }
 
 void
 StateModel::postNextEvent(unsigned int event_value) {
+    // Check for FAIL_EVT as special case of model error before events are
+    // defined.
     if (event_value != FAIL_EVT && !events_.isDefined(event_value)) {
-        isc_throw(StateModelError, 
+        isc_throw(StateModelError,
                   "Attempt to post an undefined event, value: " << event_value);
     }
 
@@ -232,8 +298,8 @@ StateModel::doOnExit() {
 }
 
 unsigned int
-StateModel::getState() const {
-    return (state_);
+StateModel::getCurrState() const {
+    return (curr_state_);
 }
 
 unsigned int
@@ -252,12 +318,12 @@ StateModel::getNextEvent() const {
 }
 bool
 StateModel::isModelNew() const {
-    return (state_ == NEW_ST);
+    return (curr_state_ == NEW_ST);
 }
 
 bool
 StateModel::isModelRunning() const {
-    return ((state_ != NEW_ST) && (state_ != END_ST));
+    return ((curr_state_ != NEW_ST) && (curr_state_ != END_ST));
 }
 
 bool
@@ -267,7 +333,7 @@ StateModel::isModelWaiting() const {
 
 bool
 StateModel::isModelDone() const {
-    return (state_ == END_ST);
+    return (curr_state_ == END_ST);
 }
 
 bool
@@ -277,26 +343,19 @@ StateModel::didModelFail() const {
 
 const char*
 StateModel::getStateLabel(const int state) const {
-    const char* str = "Unknown";
-    switch(state) {
-    case NEW_ST:
-        str = "StateModel::NEW_ST";
-        break;
-    case END_ST:
-        str = "StateModel::END_ST";
-        break;
-    default:
-        break;
-    }
+    return (states_.getLabel(state));
+}
 
-    return (str);
+const char*
+StateModel::getEventLabel(const int event) const {
+    return (events_.getLabel(event));
 }
 
 std::string
 StateModel::getContextStr() const {
     std::ostringstream stream;
     stream << "current state: [ "
-            << state_ << " " << getStateLabel(state_)
+            << curr_state_ << " " << getStateLabel(curr_state_)
             << " ] next event: [ "
             << next_event_ << " " << getEventLabel(next_event_) << " ]";
     return(stream.str());
@@ -312,10 +371,5 @@ StateModel::getPrevContextStr() const {
     return(stream.str());
 }
 
-const char*
-StateModel::getEventLabel(const int event) const {
-    return (events_.getLabel(event));
-}
-
 } // namespace isc::d2
 } // namespace isc

+ 206 - 141
src/bin/d2/state_model.h

@@ -39,7 +39,7 @@ public:
         isc::Exception(file, line, what) { };
 };
 
-/// @brief Define an Event. 
+/// @brief Define an Event.
 typedef LabeledValue Event;
 
 /// @brief Define Event pointer.
@@ -48,22 +48,96 @@ typedef LabeledValuePtr EventPtr;
 /// @brief Defines a pointer to an instance method for handling a state.
 typedef boost::function<void()> StateHandler;
 
-/// @brief Defines a map of states to their handler methods.
-typedef std::map<unsigned int, StateHandler> StateHandlerMap;
+/// @brief Defines a State within the State Model.
+///
+/// This class provides the means to define a state within a set or dictionary
+/// of states, and assign the state an handler method to execute the state's
+/// actions.  It derives from LabeledValue which allows a set of states to be
+/// keyed by integer constants.
+class State : public LabeledValue {
+public:
+    /// @brief Constructor
+    ///
+    /// @param value is the numeric value of the state
+    /// @param label is the text label to assign to the state
+    /// @param handler is the bound instance method which handles the state's
+    /// action.
+    ///
+    /// A typical invocation might look this:
+    ///
+    /// @code
+    ///     State(SOME_INT_VAL, "SOME_INT_VAL",
+    ///            boost::bind(&StateModelDerivation::someHandler, this));
+    /// @endcode
+    ///
+    /// @throw StateModelError if label is null or blank.
+    State(const int value, const char* label, StateHandler handler);
+
+    /// @brief Destructor
+    virtual ~State();
+
+    /// @brief Invokes the State's handler.
+    void run();
+
+private:
+    /// @brief Bound instance method pointer to the state's handler method.
+    StateHandler handler_;
+};
+
+/// @brief Defines a shared pointer to a State.
+typedef boost::shared_ptr<State> StatePtr;
+
+/// @brief Implements a unique set or dictionary of states.
+///
+/// This class provides the means to construct and access a unique set of
+/// states.  This provide the ability to validate state values, look up their
+/// text labels, and their handlers.
+class StateSet : public LabeledValueSet {
+public:
+    /// @brief Constructor
+    StateSet();
+
+    /// @brief Destructor
+    virtual ~StateSet();
+
+    /// @brief Adds a state definition to the set of states.
+    ///
+    /// @param value is the numeric value of the state
+    /// @param label is the text label to assig to the state
+    /// @param handler is the bound instance method which handles the state's
+    ///
+    /// @throw StateModelError if the value is already defined in the set, or
+    /// if the label is null or blank.
+    void add(const int value, const char* label, StateHandler handler);
+
+    /// @brief Fetches a state for the given value.
+    ///
+    /// @param value the numeric value of the state desired
+    ///
+    /// @return A constant pointer the State found.
+    /// Note, this relies on dynamic cast and cannot return a pointer reference.
+    ///
+    /// @throw StateModelError if the value is undefined.
+    const StatePtr getState(int value);
+};
 
 /// @brief Implements a finite state machine.
 ///
 /// StateModel is an abstract class that provides the structure and mechanics
 /// of a basic finite state machine.
 ///
-/// The state model implementation used is a very basic approach. States
-/// and events described by simple integer constants. Each state must have a 
-/// state handler. State handlers are void methods which require no parameters.
-/// Each model instance contains a map of states to instance method pointers
-/// to their respective state handlers.  The model tracks the following
-/// context values:
+/// The state model implementation used is a very basic approach. The model
+/// uses numeric constants to identify events and states, and maintains
+/// dictionaries of defined events and states.  Event and state definitions
+/// include a text label for logging purposes.  Additionally, each state
+/// definition includes a state handler. State handlers are methods which
+/// implement the actions that need to occur when the model is "in" a given
+/// state.  The implementation provides methods to add entries to and verify
+/// the contents of both dictionaries.
 ///
-/// * state - The current state of the model
+/// During model execution, the following context is tracked:
+///
+/// * current state - The current state of the model
 /// * previous state -  The state the model was in prior to the current state
 /// * next event - The next event to be consumed
 /// * last event - The event most recently consumed
@@ -126,12 +200,23 @@ typedef std::map<unsigned int, StateHandler> StateHandlerMap;
 /// specifies the "start" state and it becomes the current state.
 
 /// The first task undertaken by startModel is to initialize and verify the
-/// state handler map.  Two virtual methods, initializeStateHandlerMap and
-/// verifyStateHandlerMap, are provided for the express purpose of allowing
-/// derivations to populate the state handler map and then verify that map is
-/// contents are correct.
+/// the event and state dictionaries.  The following virtual methods are
+/// provided for this:
+///
+/// * defineEvents - define events
+/// * verifyEvents - verifies that the expected events are defined
+/// * defineStates - defines states
+/// * verifyStates - verifies that the expected states are defined
+///
+/// The concept behind the verify methods is to provide an initial sanity
+/// check of the dictionaries.  This should help avoid using undefined event
+/// or state values accidentally.
 ///
-/// Once the handler map has been properly initialized, the startModel method
+/// These methods are intended to be implemented by each "layer" in a StateModel
+/// derivation hierarchy.  This allows each layer to define additional events
+/// and states.
+///
+/// Once the dictionaries have been properly initialized, the startModel method
 /// invokes runModel with an event of START_EVT.  From this point forward and
 /// until the model reaches the END_ST or fails, it is considered to be
 /// "running".  If the model encounters a NOP_EVT then it is "running" and
@@ -192,20 +277,21 @@ public:
 
     /// @brief Begins execution of the model.
     ///
-    /// This method invokes initStateHandlerMap() to initialize the map of state
-    /// handlers, followed by verifyStateHandlerMap which validates the map
-    /// contents. It then starts the  model execution setting the current
-    /// state to the given start state, and the event to START_EVT.
+    /// This method invokes the define and verify methods for both events and
+    /// states to initialize their respective dictionaries. It then starts
+    /// the model execution setting the current state to the given start state,
+    /// and the event to START_EVT.
     ///
     /// @param start_state is the state in which to begin execution.
+    ///
     /// @throw StateModelError or others indirectly, as this method calls
-    /// initializeStateHandlerMap and verifyStateHandlerMap.
+    /// dictionary define and verify methods.
     void startModel(const int start_state);
 
     /// @brief Processes events through the state model
     ///
     /// This method implements the state model "execution loop".  It uses
-    /// the given event as the next event to process and begins invoking the
+    /// the given event as the next event to process and begins invoking
     /// the state handler for the current state.   As described above, the
     /// invoked state handler consumes the next event and then determines the
     /// next event and the current state as required to implement the business
@@ -231,15 +317,22 @@ public:
     /// handler should call endModel.
     void endModel();
 
+    /// @brief An empty state handler.
+    ///
+    /// This method is primarily used to permit special states, NEW_ST and
+    /// END_ST to be included in the state dictionary.  Currently it is an
+    /// empty method.
+    void nopStateHandler();
+
 protected:
-    /// @brief Populates the set of events. 
+    /// @brief Populates the set of events.
     ///
     /// This method is used to construct the set of valid events. Each class
-    /// within a StateModel derivation heirarchy uses this method to add any
+    /// within a StateModel derivation hierarchy uses this method to add any
     /// events it defines to the set.  Each derivation's implementation must
-    /// also call it's superclass's implementation.  This allows each class 
-    /// within the heirarchy to make contributions to the set of defined 
-    /// events. Implementations use the method, defineEvent(), to add event 
+    /// also call it's superclass's implementation.  This allows each class
+    /// within the hierarchy to make contributions to the set of defined
+    /// events. Implementations use the method, defineEvent(), to add event
     /// definitions.  An example of the derivation's implementation follows:
     ///
     /// @code
@@ -247,7 +340,7 @@ protected:
     ///     // Call the superclass implementation.
     ///     StateModelDerivation::defineEvents();
     ///
-    ///     // Add the events defined by the derivation. 
+    ///     // Add the events defined by the derivation.
     ///     defineEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
     ///     defineEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
     ///     :
@@ -268,7 +361,7 @@ protected:
     /// @brief Fetches the event referred to by value.
     ///
     /// @param value is the numeric value of the event desired.
-    /// 
+    ///
     /// @return returns a constant pointer reference to the event if found
     ///
     /// @throw StateModelError if the event is not defined.
@@ -276,89 +369,95 @@ protected:
 
     /// @brief Validates the contents of the set of events.
     ///
-    /// This method is invoked immediately after the defineEvents method and 
-    /// is used to verify that all the requred events are defined.  If the 
-    /// event set is determined to be invalid this method should throw a 
+    /// This method is invoked immediately after the defineEvents method and
+    /// is used to verify that all the required events are defined.  If the
+    /// event set is determined to be invalid this method should throw a
     /// StateModelError.  As with the defineEvents method, each class within
-    /// the a StateModel derivation heirarchy must supply an implementation
-    /// which calls it's superclass's implemenation as well as verifying any
+    /// a StateModel derivation hierarchy must supply an implementation
+    /// which calls it's superclass's implementation as well as verifying any
     /// events added by the derivation.  Validating an event is accomplished
-    /// by simply attempting to fetch en event by its value from the the 
-    /// event set.  An example of the derivation's implementation follows:
+    /// by simply attempting to fetch an event by its value from the event set.
+    /// An example of the derivation's implementation follows:
     ///
     /// @code
     /// void StateModelDerivation::verifyEvents() {
     ///     // Call the superclass implementation.
     ///     StateModelDerivation::verifyEvents();
     ///
-    ///     // Verify the events defined by the derivation. 
-    ///     events_.get(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
-    ///     events_.get(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
+    ///     // Verify the events defined by the derivation.
+    ///     getEvent(SOME_CUSTOM_EVT_1, "CUSTOM_EVT_1");
+    ///     getEvent(SOME_CUSTOM_EVT_2, "CUSTOM_EVT_2");
     ///     :
     /// }
     /// @endcode
     virtual void verifyEvents();
 
-    /// @brief Populates the map of state handlers.
+    /// @brief Populates the set of states.
+    ///
+    /// This method is used to construct the set of valid states. Each class
+    /// within a StateModel derivation hierarchy uses this method to add any
+    /// states it defines to the set.  Each derivation's implementation must
+    /// also call it's superclass's implementation.  This allows each class
+    /// within the hierarchy to make contributions to the set of defined
+    /// states. Implementations use the method, defineState(), to add state
+    /// definitions.  An example of the derivation's implementation follows:
     ///
-    /// This method is used by derivations to construct a map of states to
-    /// their appropriate state handlers (bound method pointers).  It is
-    /// invoked at the beginning of startModel().
+    /// @code
+    /// void StateModelDerivation::defineStates() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::defineStates();
     ///
-    /// Implementations should use the addToStateHandlerMap() method add
-    /// entries to the map.
-    virtual void initStateHandlerMap() = 0;
+    ///     // Add the states defined by the derivation.
+    ///     defineState(SOME_ST, "SOME_ST",
+    ///                 boost::bind(&StateModelDerivation::someHandler, this));
+    ///     :
+    /// }
+    /// @endcode
+    virtual void defineStates();
 
-    /// @brief Validates the contents of the state handler map.
+    /// @brief Adds an state value and associated label to the set of states.
     ///
-    /// This method is invoked immediately after initStateHandlerMap and
-    /// provides an opportunity for derivations to verify that the map
-    /// is correct.  If the map is determined to be invalid this method
-    /// should throw a StateModelError.
+    /// @param value is the numeric value of the state
+    /// @param label is the text label of the state used in log messages and
+    /// exceptions.
     ///
-    /// The simplest implementation would include a call to getStateHandler,
-    /// for each state the derivation supports.  For example, an implementation
-    /// which included three states, READY_ST, DO_WORK_ST, and DONE_ST could
-    /// implement this function as follows:
+    /// @throw StateModelError if the model has already been started, if
+    /// the value is already defined, or if the label is null or empty.
+    void defineState(unsigned int value, const char* label,
+                     StateHandler handler);
+
+    /// @brief Fetches the state referred to by value.
     ///
-    /// @code
-    ///    void verifyStateHandlerMap() {
-    ///        getStateHandler(READY_ST);
-    ///        getStateHandler(DO_WORK_ST);
-    ///        getStateHandler(DONE_ST);
-    ///    }
-    /// @endcode
+    /// @param value is the numeric value of the state desired.
+    ///
+    /// @return returns a constant pointer to the state if found
     ///
-    virtual void verifyStateHandlerMap() = 0;
+    /// @throw StateModelError if the state is not defined.
+    const StatePtr getState(unsigned int value);
 
-    /// @brief Adds an entry to the state handler map.
+    /// @brief Validates the contents of the set of states.
     ///
-    /// This method attempts to add an entry to the handler map which maps
-    /// the given handler to the given state.  The state handler must be
-    /// a bound member pointer to a handler method of derivation instance.
-    /// The following code snippet shows an example derivation and call to
-    /// addToStateHandlerMap() within its initStateHandlerMap() method.
+    /// This method is invoked immediately after the defineStates method and
+    /// is used to verify that all the required states are defined.  If the
+    /// state set is determined to be invalid this method should throw a
+    /// StateModelError.  As with the defineStates method, each class within
+    /// a StateModel derivation hierarchy must supply an implementation
+    /// which calls it's superclass's implementation as well as verifying any
+    /// states added by the derivation.  Validating an state is accomplished
+    /// by simply attempting to fetch the state by its value from the state set.
+    /// An example of the derivation's implementation follows:
     ///
     /// @code
-    /// class ExampleModel : public StateModel {
-    /// public:
-    /// :
-    /// void readyHandler() {
-    /// }
+    /// void StateModelDerivation::verifyStates() {
+    ///     // Call the superclass implementation.
+    ///     StateModelDerivation::verifyStates();
     ///
-    /// void initStateHandlerMap() {
-    ///     addToStateHandlerMap(READY_ST,
-    ///        boost::bind(&ExampleModel::readyHandler, this));
+    ///     // Verify the states defined by the derivation.
+    ///     getState(SOME_CUSTOM_EVT_2);
     ///     :
-    ///
+    /// }
     /// @endcode
-    ///
-    /// @param state the value of the state to which to map
-    /// @param handler the bound method pointer to the handler for the state
-    ///
-    /// @throw StateModelError if the map already contains an entry
-    /// for the given state, or if the model is beyond the NEW_ST.
-    void addToStateHandlerMap(unsigned int state, StateHandler handler);
+    virtual void verifyStates();
 
     /// @brief Handler for fatal model execution errors.
     ///
@@ -367,20 +466,9 @@ protected:
     /// It provides derivations an opportunity to act accordingly by setting
     /// the appropriate status or taking other remedial action.   This allows
     /// the model execution loop to remain exception safe.
-    virtual void onModelFailure() = 0;
-
-    /// @brief Return the state handler for a given state.
-    ///
-    /// This method looks up the state handler for the given state from within
-    /// the state handler map.
     ///
-    /// @param state is the state constant of the desired handler.
-    ///
-    /// @return A StateHandler (bound member pointer) for the method that
-    /// handles the given state for this model.
-    ///
-    /// @throw StateModelError
-    StateHandler getStateHandler(unsigned int state);
+    /// @param explanation text detailing the error and state machine context
+    virtual void onModelFailure(const std::string& explanation) = 0;
 
     /// @brief Sets up the model to transition into given state with a given
     /// event.
@@ -402,7 +490,9 @@ protected:
     /// any sort of inconsistency such as attempting to reference an invalid
     /// state, or if the next event is not valid for the current state, or a
     /// state handler throws an uncaught exception.
-    void abortModel();
+    ///
+    /// @param explanation is text detailing the reason for aborting.
+    void abortModel(const std::string& explanation);
 
     /// @brief Sets the current state to the given state value.
     ///
@@ -425,7 +515,7 @@ protected:
     ///
     /// @param the numeric event value to post as the next event.
     ///
-    /// @throw StateModelError if the event is undefined 
+    /// @throw StateModelError if the event is undefined
     void postNextEvent(unsigned int event);
 
     /// @brief Checks if on entry flag is true.
@@ -458,7 +548,7 @@ public:
     /// loop.
     ///
     /// @return An unsigned int representing the current state.
-    unsigned int getState() const;
+    unsigned int getCurrState() const;
 
     /// @brief Fetches the model's previous state.
     ///
@@ -507,49 +597,21 @@ public:
     /// event indicates a model violation, FAILED_EVT.
     bool didModelFail() const;
 
-    /// @brief Converts a state value into a text label.
-    ///
-    /// This method supplies labels for StateModel's predefined states. It is
-    /// declared virtual to allow derivations to embed a call to this method
-    /// within their own implementation which would define labels for its
-    /// states.  An example implementation might look like the following:
-    /// @code
-    ///
-    /// class DerivedModel : public StateModel {
-    ///     :
-    ///     static const int EXAMPLE_1_ST = SM_STATE_MAX + 1;
-    ///     :
-    ///     const char* getStateLabel(const int state) const {
-    ///         const char* str = "Unknown";
-    ///         switch(state) {
-    ///         case EXAMPLE_1_ST:
-    ///             str = "DerivedModel::EXAMPLE_1_ST";
-    ///             break;
-    ///         :
-    ///         default:
-    ///             // Not a derived state, pass it down to StateModel's method.
-    ///             str = StateModel::getStateLabel(state);
-    ///             break;
-    ///         }
-    ///
-    ///         return (str);
-    ///      }
-    ///
-    /// @endcode
-    ///
-    /// @param state is the state for which a label is desired.
-    ///
-    /// @return Returns a const char* containing the state label or
-    /// "Unknown" if the value cannot be mapped.
-    virtual const char* getStateLabel(const int state) const;
-
     /// @brief Fetches the label associated with an event value.
     ///
     /// @param event is the numeric event value for which the label is desired.
     ///
     /// @return Returns a const char* containing the event label or
     /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
-    virtual const char* getEventLabel(const int event) const;
+    const char* getEventLabel(const int event) const;
+
+    /// @brief Fetches the label associated with an state value.
+    ///
+    /// @param state is the numeric state value for which the label is desired.
+    ///
+    /// @return Returns a const char* containing the state label or
+    /// LabeledValueSet::UNDEFINED_LABEL if the value is undefined.
+    const char* getStateLabel(const int state) const;
 
     /// @brief Convenience method which returns a string rendition of the
     /// current state and next event.
@@ -572,14 +634,17 @@ public:
     std::string getPrevContextStr() const;
 
 private:
-    /// @brief Contains the set of defined Events.
+    /// @brief The dictionary of valid events.
     LabeledValueSet events_;
 
-    /// @brief Contains a map of states to their state handlers.
-    StateHandlerMap state_handlers_;
+    /// @brief The dictionary of valid states.
+    StateSet states_;
+
+    /// @brief Indicates if the event and state dictionaries have been initted.
+    bool dictionaries_initted_;
 
     /// @brief The current state within the model's state model.
-    unsigned int state_;
+    unsigned int curr_state_;
 
     /// @brief The previous state within the model's state model.
     unsigned int prev_state_;

+ 41 - 135
src/bin/d2/tests/nc_trans_unittests.cc

@@ -131,65 +131,63 @@ public:
         }
     }
 
+    /// @brief Construct the event dictionary.
     virtual void defineEvents() {
+        // Invoke the base call implementation first.
         NameChangeTransaction::defineEvents();
+
+        // Define our events.
         defineEvent(SEND_UPDATE_EVT, "SEND_UPDATE_EVT");
     }
 
+    /// @brief Verify the event dictionary.
     virtual void verifyEvents() {
+        // Invoke the base call implementation first.
         NameChangeTransaction::verifyEvents();
+
+        // Define our events.
         getEvent(SEND_UPDATE_EVT);
     }
 
-    /// @brief Initializes the state handler map.
-    void initStateHandlerMap() {
-        addToStateHandlerMap(READY_ST,
+    /// @brief Construct the state dictionary.
+    virtual void defineStates() {
+        // Invoke the base call implementation first.
+        NameChangeTransaction::defineStates();
+
+        // Define our states.
+        defineState(READY_ST, "READY_ST",
                              boost::bind(&NameChangeStub::readyHandler, this));
 
-        addToStateHandlerMap(SELECTING_FWD_SERVER_ST,
+        defineState(SELECTING_FWD_SERVER_ST, "SELECTING_FWD_SERVER_ST",
                              boost::bind(&NameChangeStub::dummyHandler, this));
 
-        addToStateHandlerMap(SELECTING_REV_SERVER_ST,
+        defineState(SELECTING_REV_SERVER_ST, "SELECTING_REV_SERVER_ST",
                              boost::bind(&NameChangeStub::dummyHandler, this));
 
-        addToStateHandlerMap(DOING_UPDATE_ST,
+        defineState(DOING_UPDATE_ST, "DOING_UPDATE_ST",
                              boost::bind(&NameChangeStub::doingUpdateHandler,
                                          this));
 
-        addToStateHandlerMap(PROCESS_TRANS_OK_ST,
+        defineState(PROCESS_TRANS_OK_ST, "PROCESS_TRANS_OK_ST",
                              boost::bind(&NameChangeStub::
                                          processTransDoneHandler, this));
 
-        addToStateHandlerMap(PROCESS_TRANS_FAILED_ST,
+        defineState(PROCESS_TRANS_FAILED_ST, "PROCESS_TRANS_FAILED_ST",
                              boost::bind(&NameChangeStub::
                                          processTransDoneHandler, this));
     }
 
-    void verifyStateHandlerMap() {
-        getStateHandler(READY_ST);
-        getStateHandler(DOING_UPDATE_ST);
-        // Call base class verification.
-        NameChangeTransaction::verifyStateHandlerMap();
-    }
-
-    const char* getStateLabel(const int state) const {
-        const char* str = "Unknown";
-        switch(state) {
-        case NameChangeStub::DOING_UPDATE_ST:
-            str = "NameChangeStub::DOING_UPDATE_ST";
-            break;
-        default:
-            str = NameChangeTransaction::getStateLabel(state);
-            break;
-        }
+    /// @brief Verify the event dictionary.
+    virtual void verifyStates() {
+        // Invoke the base call implementation first.
+        NameChangeTransaction::verifyStates();
 
-        return (str);
+        // Define our states.
+        getState(DOING_UPDATE_ST);
     }
 
     // Expose the protected methods to be tested.
     using StateModel::runModel;
-    using StateModel::getStateHandler;
-
     using NameChangeTransaction::initServerSelection;
     using NameChangeTransaction::selectNextServer;
     using NameChangeTransaction::getCurrentServer;
@@ -377,22 +375,23 @@ TEST_F(NameChangeTransactionTest, accessors) {
     EXPECT_TRUE(name_change->getReverseChangeCompleted());
 }
 
-/// @brief Tests state map initialization and validation.
-/// This tests the basic concept of state map initialization and verification
-/// by manually invoking the map methods normally by StateModel::startModel.
-TEST_F(NameChangeTransactionTest, stubStateMapInit) {
+/// @brief Tests event and state dictionary construction and verification.
+TEST_F(NameChangeTransactionTest, dictionaryCheck) {
     NameChangeStubPtr name_change;
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
 
-    // Verify that the map validation throws prior to the map being
-    // initialized.
-    ASSERT_THROW(name_change->verifyStateHandlerMap(), StateModelError);
+    // Verify that the event and state dictionary validation fails prior
+    // dictionary construction.
+    ASSERT_THROW(name_change->verifyEvents(), StateModelError);
+    ASSERT_THROW(name_change->verifyStates(), StateModelError);
 
-    // Call initStateHandlerMap to initialize the state map.
-    ASSERT_NO_THROW(name_change->initStateHandlerMap());
+    // Construct both dictionaries.
+    ASSERT_NO_THROW(name_change->defineEvents());
+    ASSERT_NO_THROW(name_change->defineStates());
 
-    // Verify that the map validation succeeds now that the map is initialized.
-    ASSERT_NO_THROW(name_change->verifyStateHandlerMap());
+    // Verify both event and state dictionaries now pass validation.
+    ASSERT_NO_THROW(name_change->verifyEvents());
+    ASSERT_NO_THROW(name_change->verifyStates());
 }
 
 /// @brief Tests server selection methods.
@@ -511,106 +510,14 @@ TEST_F(NameChangeTransactionTest, serverSelectionTest) {
     EXPECT_EQ (passes, num_servers);
 }
 
-/// @brief Tests the ability to decode state values into text labels.
-TEST_F(NameChangeTransactionTest, stateLabels) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Verify StateModel labels.
-    EXPECT_EQ("StateModel::NEW_ST",
-              name_change->getStateLabel(StateModel::NEW_ST));
-    EXPECT_EQ("StateModel::END_ST",
-              name_change->getStateLabel(StateModel::END_ST));
-
-    // Verify NameChangeTransaction labels
-    EXPECT_EQ("NameChangeTransaction::READY_ST",
-              name_change->getStateLabel(NameChangeTransaction::READY_ST));
-    EXPECT_EQ("NameChangeTransaction::SELECTING_FWD_SERVER_ST",
-              name_change->getStateLabel(NameChangeTransaction::
-                                        SELECTING_FWD_SERVER_ST));
-    EXPECT_EQ("NameChangeTransaction::SELECTING_REV_SERVER_ST",
-              name_change->getStateLabel(NameChangeTransaction::
-                                         SELECTING_REV_SERVER_ST));
-    EXPECT_EQ("NameChangeTransaction::PROCESS_TRANS_OK_ST",
-              name_change->getStateLabel(NameChangeTransaction::
-                                         PROCESS_TRANS_OK_ST));
-    EXPECT_EQ("NameChangeTransaction::PROCESS_TRANS_FAILED_ST",
-              name_change->getStateLabel(NameChangeTransaction::
-                                         PROCESS_TRANS_FAILED_ST));
-
-    // Verify Stub states
-    EXPECT_EQ("NameChangeStub::DOING_UPDATE_ST",
-              name_change->getStateLabel(NameChangeStub::DOING_UPDATE_ST));
-
-    // Verify unknown state.
-    EXPECT_EQ("Unknown", name_change->getStateLabel(-1));
-}
-
-/// @brief Tests the ability to decode event values into text labels.
-TEST_F(NameChangeTransactionTest, eventLabels) {
-    NameChangeStubPtr name_change;
-    ASSERT_NO_THROW(name_change = makeCannedTransaction());
-
-    // Manually invoke event definition.
-    ASSERT_NO_THROW(name_change->defineEvents());
-    ASSERT_NO_THROW(name_change->verifyEvents());
-
-    // Verify StateModel labels.
-    EXPECT_EQ("NOP_EVT",
-              std::string(name_change->getEventLabel(StateModel::NOP_EVT)));
-    EXPECT_EQ("START_EVT",
-              std::string(name_change->getEventLabel(StateModel::START_EVT)));
-    EXPECT_EQ("END_EVT",
-              std::string(name_change->getEventLabel(StateModel::END_EVT)));
-    EXPECT_EQ("FAIL_EVT",
-              std::string(name_change->getEventLabel(StateModel::FAIL_EVT)));
-
-    // Verify NameChangeTransactionLabels
-    EXPECT_EQ("SELECT_SERVER_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         SELECT_SERVER_EVT)));
-    EXPECT_EQ("SERVER_SELECTED_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         SERVER_SELECTED_EVT)));
-    EXPECT_EQ("SERVER_IO_ERROR_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         SERVER_IO_ERROR_EVT)));
-    EXPECT_EQ("NO_MORE_SERVERS_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         NO_MORE_SERVERS_EVT)));
-    EXPECT_EQ("IO_COMPLETED_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         IO_COMPLETED_EVT)));
-    EXPECT_EQ("UPDATE_OK_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         UPDATE_OK_EVT)));
-    EXPECT_EQ("UPDATE_FAILED_EVT",
-              std::string(name_change->getEventLabel(NameChangeTransaction::
-                                         UPDATE_FAILED_EVT)));
-
-    // Verify stub class labels.
-    EXPECT_EQ("SEND_UPDATE_EVT",
-              std::string(name_change->getEventLabel(NameChangeStub::
-                                                     SEND_UPDATE_EVT)));
-
-    // Verify undefined event label.
-    EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, name_change->getEventLabel(-1));
-}
-
 /// @brief Tests that the transaction will be "failed" upon model errors.
 TEST_F(NameChangeTransactionTest, modelFailure) {
     NameChangeStubPtr name_change;
     ASSERT_NO_THROW(name_change = makeCannedTransaction());
 
-    // We will cause a model failure by attempting to submit an event to
-    // NEW_ST.  Let's make sure that state is NEW_ST and that NEW_ST has no
-    // handler.
-    ASSERT_EQ(NameChangeTransaction::NEW_ST, name_change->getState());
-    ASSERT_THROW(name_change->getStateHandler(NameChangeTransaction::NEW_ST),
-                 StateModelError);
-
-    // Now call runStateModel() which should not throw.
-    EXPECT_NO_THROW(name_change->runModel(NameChangeTransaction::START_EVT));
+    // Now call runModel() with an undefined event which should not throw,
+    // but should result in a failed model and failed transaction.
+    EXPECT_NO_THROW(name_change->runModel(9999));
 
     // Verify that the model reports are done but failed.
     EXPECT_TRUE(name_change->isModelDone());
@@ -689,5 +596,4 @@ TEST_F(NameChangeTransactionTest, failedUpdateTest) {
     EXPECT_FALSE(name_change->getForwardChangeCompleted());
 }
 
-
 }

+ 371 - 241
src/bin/d2/tests/state_model_unittests.cc

@@ -56,11 +56,17 @@ public:
     ///@brief Event issued when all the work is done.
     static const int ALL_DONE_EVT = SM_EVENT_MAX + 3;
 
+    ///@brief Event used to trigger an attempt to transition to bad state
+    static const int FORCE_UNDEFINED_ST_EVT = SM_EVENT_MAX + 4;
+
+    ///@brief Event used to trigger an attempt to transition to bad state
+    static const int SIMULATE_ERROR_EVT = SM_EVENT_MAX + 5;
+
     /// @brief Constructor
     ///
     /// Parameters match those needed by StateModel.
-    StateModelTest() : dummy_called_(false), model_failure_called_(false),
-                      work_completed_(false) {
+    StateModelTest() : dummy_called_(false), work_completed_(false),
+                       failure_explanation_("") {
     }
     /// @brief Destructor
     virtual ~StateModelTest() {
@@ -79,13 +85,12 @@ public:
         dummy_called_ = true;
     }
 
-    /// @brief Returns indication of whether or not the model failed to execute.
+    /// @brief Returns the failure explanation string.
     ///
-    /// If true, this indicates that the test model suffered an error
-    /// during execution such as an invalid state, event, or exception thrown by
-    /// a state handler. The flag is only set true by onModelFailure() method.
-    bool getModelFailureCalled() {
-        return (model_failure_called_);
+    /// 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.
@@ -131,6 +136,12 @@ public:
             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: "
@@ -154,79 +165,116 @@ public:
         }
     }
 
+    /// @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");
     }
 
+    /// @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);
     }
 
-    /// @brief Initializes the state handler map.
-    virtual void initStateHandlerMap() {
-        addToStateHandlerMap(DUMMY_ST,
-            boost::bind(&StateModelTest::dummyHandler, this));
+    /// @brief Construct the state dictionary.
+    virtual void defineStates() {
+        // Invoke the base call implementation first.
+        StateModel::defineStates();
+
+        // Define our states.
+        defineState(DUMMY_ST, "DUMMY_ST",
+                    boost::bind(&StateModelTest::dummyHandler, this));
 
-        addToStateHandlerMap(READY_ST,
+        defineState(READY_ST, "READY_ST",
             boost::bind(&StateModelTest::readyHandler, this));
 
-        addToStateHandlerMap(DO_WORK_ST,
+        defineState(DO_WORK_ST, "DO_WORK_ST",
             boost::bind(&StateModelTest::doWorkHandler, this));
 
-        addToStateHandlerMap(DONE_ST,
+        defineState(DONE_ST, "DONE_ST",
             boost::bind(&StateModelTest::doneWorkHandler, this));
     }
 
-    /// @brief Validates the contents of the state handler map.
-    virtual void verifyStateHandlerMap() {
-        getStateHandler(DUMMY_ST);
-        getStateHandler(READY_ST);
-        getStateHandler(DO_WORK_ST);
-        getStateHandler(DONE_ST);
+    /// @brief Verify the state dictionary.
+    virtual void verifyStates() {
+        // Invoke the base call implementation first.
+        StateModel::verifyStates();
+
+        // Verify our states.
+        getState(DUMMY_ST);
+        getState(READY_ST);
+        getState(DO_WORK_ST);
+        getState(DONE_ST);
     }
 
-    /// @brief  Handler called when the model suffers an execution error.
-    virtual void onModelFailure() {
-        model_failure_called_ = true;
+    /// @brief  Manually construct the event and state dictionaries.
+    /// This allows testing without running startModel.
+    void initDictionaires() {
+        ASSERT_NO_THROW(defineEvents());
+        ASSERT_NO_THROW(verifyEvents());
+        ASSERT_NO_THROW(defineStates());
+        ASSERT_NO_THROW(verifyStates());
     }
 
-    const char* getStateLabel(const int state) const {
-        const char* str = "Unknown";
-        switch(state) {
-        case DUMMY_ST:
-            str = "StateModelTest::DUMMY_ST";
-            break;
-        case READY_ST:
-            str = "StateModelTest::READY_ST";
-            break;
-        case DO_WORK_ST:
-            str = "StateModelTest::DO_WORK_ST";
-            break;
-        case DONE_ST:
-            str = "StateModelTest::DONE_ST";
-            break;
-        default:
-            str = StateModel::getStateLabel(state);
-            break;
+    /// @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, std::string(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, std::string(state->getLabel()));
+        } catch (const std::exception& ex) {
+            return false;
         }
 
-        return (str);
+        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 onModelError has been called.
-    bool model_failure_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.
@@ -238,129 +286,173 @@ const int StateModelTest::WORK_START_EVT;
 const int StateModelTest::WORK_DONE_EVT;
 const int StateModelTest::ALL_DONE_EVT;
 
-/// @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, basicStateMapping) {
-    // After construction, the state map should be empty. Verify that
-    // getStateHandler will throw when, state cannot be found.
-    EXPECT_THROW(getStateHandler(READY_ST), StateModelError);
+/// @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(addToStateHandlerMap(READY_ST,
-                                         boost::bind(&StateModelTest::
-                                                     dummyHandler, this)));
+    ASSERT_NO_THROW(defineEvent(NOP_EVT, "NOP_EVT"));
 
-    // Verify that we can find the handler by its state.
-    StateHandler retreived_handler;
-    EXPECT_NO_THROW(retreived_handler = getStateHandler(READY_ST));
+    // Verify that we can find the event by value and its content is correct.
+    EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
 
-    // Now we will Verify that retrieved handler executes the correct method.
-    // Make sure the dummy called flag is false prior to invocation.
-    EXPECT_FALSE(getDummyCalled());
-
-    // Invoke the retreived handler.
-    ASSERT_NO_THROW((retreived_handler)());
+    // Verify that we cannot add a duplicate.
+    ASSERT_THROW(defineEvent(NOP_EVT, "NOP_EVT"), StateModelError);
 
-    // Verify the dummy called flag is now true.
-    EXPECT_TRUE(getDummyCalled());
+    // Verify that we can still find the event.
+    EXPECT_TRUE(checkEvent(NOP_EVT, "NOP_EVT"));
+}
 
-    // Verify that we cannot add a duplicate.
-    EXPECT_THROW(addToStateHandlerMap(READY_ST,
-                                      boost::bind(&StateModelTest::readyHandler,
-                                                  this)),
-                 StateModelError);
+/// @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));
+}
 
-    // Verify that we can still find the handler by its state.
-    EXPECT_NO_THROW(getStateHandler(READY_ST));
+/// @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());
 
-    // Verify that we cannot add a handler for NEW_ST.
-    EXPECT_THROW(addToStateHandlerMap(NEW_ST,
-                                      boost::bind(&StateModelTest::dummyHandler,
-                                                  this)),
-                 StateModelError);
+    // Call setEvent which will update both next event and last event.
+    EXPECT_NO_THROW(postNextEvent(START_EVT));
 
-    // Verify that we cannot add a handler for END_ST.
-    EXPECT_THROW(addToStateHandlerMap(END_ST,
-                                      boost::bind(&StateModelTest::dummyHandler,
-                                                  this)),
-                 StateModelError);
-}
+    // Verify the values are what we expect.
+    EXPECT_EQ(START_EVT, getNextEvent());
+    EXPECT_EQ(NOP_EVT, getLastEvent());
 
-/// @brief Tests state map initialization and validation.
-/// This tests the basic concept of state map initialization and verification
-/// by manually invoking the map methods normally called by startModel.
-TEST_F(StateModelTest, stateMapInit) {
-    // Verify that the map validation throws prior to the map being
-    // initialized.
-    EXPECT_THROW(verifyStateHandlerMap(), StateModelError);
+    // Call setEvent again.
+    EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
 
-    // Call initStateHandlerMap to initialize the state map.
-    ASSERT_NO_THROW(initStateHandlerMap());
+    // Verify the values are what we expect.
+    EXPECT_EQ(WORK_START_EVT, getNextEvent());
+    EXPECT_EQ(START_EVT, getLastEvent());
 
-    // Verify that the map validation succeeds now that the map is initialized.
-    EXPECT_NO_THROW(verifyStateHandlerMap());
+    // Verify that posting an undefined event throws.
+    EXPECT_THROW(postNextEvent(9999), StateModelError);
 }
 
-/// @brief Tests the ability to decode state values into text labels.
-TEST_F(StateModelTest, stateLabels) {
-    // Verify base class labels.
-    EXPECT_EQ("StateModel::NEW_ST", getStateLabel(NEW_ST));
-    EXPECT_EQ("StateModel::END_ST", getStateLabel(END_ST));
-
-    // Verify stub class labels.
-    EXPECT_EQ("StateModelTest::DUMMY_ST", getStateLabel(DUMMY_ST));
-    EXPECT_EQ("StateModelTest::READY_ST", getStateLabel(READY_ST));
-    EXPECT_EQ("StateModelTest::DO_WORK_ST", getStateLabel(DO_WORK_ST));
-    EXPECT_EQ("StateModelTest::DONE_ST", getStateLabel(DONE_ST));
+/// @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",
+                                boost::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", std::string(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());
 
-    // Verify unknown state.
-    EXPECT_EQ("Unknown", getStateLabel(-1));
-}
+    // Invoke the state's handler.
+    EXPECT_NO_THROW(state->run());
 
-/// @brief Tests the ability to decode event values into text labels.
-TEST_F(StateModelTest, eventLabels) {
-    // Verify base class labels.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
+    // Verify the dummy called flag is now true.
+    EXPECT_TRUE(getDummyCalled());
 
-    EXPECT_EQ("NOP_EVT", std::string(getEventLabel(NOP_EVT)));
-    EXPECT_EQ("START_EVT", std::string(getEventLabel(START_EVT)));
-    EXPECT_EQ("END_EVT", std::string(getEventLabel(END_EVT)));
-    EXPECT_EQ("FAIL_EVT", std::string(getEventLabel(FAIL_EVT)));
+    // Verify that we cannot add a duplicate.
+    EXPECT_THROW(defineState(READY_ST, "READY_ST",
+                             boost::bind(&StateModelTest::readyHandler, this)),
+                 StateModelError);
 
-    // Verify stub class labels.
-    EXPECT_EQ("WORK_START_EVT", std::string(getEventLabel(WORK_START_EVT)));
-    EXPECT_EQ("WORK_DONE_EVT", std::string(getEventLabel(WORK_DONE_EVT)));
-    EXPECT_EQ("ALL_DONE_EVT", std::string(getEventLabel(ALL_DONE_EVT)));
+    // Verify that we can still find the state.
+    EXPECT_NO_THROW(getState(READY_ST));
+}
 
-    // Verify unknown state.
-    EXPECT_EQ(LabeledValueSet::UNDEFINED_LABEL, getEventLabel(-1));
+/// @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,
+              std::string(getStateLabel(9999)));
 }
 
-/// @brief General testing of member accessors.
+/// @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, stateAccessors) {
+TEST_F(StateModelTest, stateContextAccessors) {
     // setState will throw unless we initialize the handler map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    ASSERT_NO_THROW(defineStates());
+    ASSERT_NO_THROW(verifyStates());
 
     // Verify post-construction state values.
-    EXPECT_EQ(NEW_ST, getState());
+    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, getState());
+    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, getState());
+    EXPECT_EQ(DO_WORK_ST, getCurrState());
     EXPECT_EQ(READY_ST, getPrevState());
 
     // Verify that calling setState with an state that has no handler
@@ -372,45 +464,39 @@ TEST_F(StateModelTest, stateAccessors) {
 
     // Verify that calling setState with END_ST is ok.
     EXPECT_NO_THROW(setState(END_ST));
-}
-
-TEST_F(StateModelTest, eventAccessors) {
-    // Construct the event definitions, normally done by startModel.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
 
-    EXPECT_EQ(NOP_EVT, getNextEvent());
-    EXPECT_EQ(NOP_EVT, getLastEvent());
+    // Verify that calling setState with an undefined state throws.
+    EXPECT_THROW(setState(9999), StateModelError);
+}
 
-    // Call setEvent which will update both next event and last event.
-    EXPECT_NO_THROW(postNextEvent(START_EVT));
+/// @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());
 
-    // Verify the values are what we expect.
-    EXPECT_EQ(START_EVT, getNextEvent());
-    EXPECT_EQ(NOP_EVT, getLastEvent());
+    // Attempt to call runModel before startModel.  This should result in an
+    // orderly model failure.
+    ASSERT_NO_THROW(runModel(START_EVT));
 
-    // Call setEvent again.
-    EXPECT_NO_THROW(postNextEvent(WORK_START_EVT));
-    EXPECT_EQ(WORK_START_EVT, getNextEvent());
-    EXPECT_EQ(START_EVT, getLastEvent());
+    // Check that state and event are correct.
+    EXPECT_EQ(END_ST, getCurrState());
+    EXPECT_EQ(FAIL_EVT, getNextEvent());
 
-    // Verify the values are what we expect.
+    // 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) {
-    // Construct the event definitions, normally done by startModel.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
 
     // 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, getState());
+    EXPECT_EQ(DUMMY_ST, getCurrState());
     EXPECT_EQ(NEW_ST, getPrevState());
     EXPECT_EQ(START_EVT, getNextEvent());
     EXPECT_EQ(NOP_EVT, getLastEvent());
@@ -419,48 +505,42 @@ TEST_F(StateModelTest, transitionWithEnd) {
     EXPECT_NO_THROW(endModel());
 
     // Verify state and event members are correctly set.
-    EXPECT_EQ(END_ST, getState());
+    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) {
-    // Construct the event definitions, normally done by startModel.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
 
     // 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, getState());
+    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());
+    EXPECT_NO_THROW(abortModel("test invocation"));
 
     // Verify state and event members are correctly set.
-    EXPECT_EQ(END_ST, getState());
+    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) {
-    // Construct the event definitions, normally done by startModel.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
 
     // Verify that "do" flags are false.
     EXPECT_FALSE(doOnEntry());
@@ -486,14 +566,12 @@ TEST_F(StateModelTest, doFlags) {
 
 }
 
+/// @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) {
-    // Construct the event definitions, normally done by startModel.
-    ASSERT_NO_THROW(defineEvents());
-    ASSERT_NO_THROW(verifyEvents());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    // Init dictionaries manually, normally done by startModel.
+    initDictionaires();
 
     // After construction, state model is "new", all others should be false.
     EXPECT_TRUE(isModelNew());
@@ -502,19 +580,23 @@ TEST_F(StateModelTest, statusMethods) {
     EXPECT_FALSE(isModelDone());
     EXPECT_FALSE(didModelFail());
 
-    // verify that you can add to the before the model has started.
-    EXPECT_NO_THROW(addToStateHandlerMap(9998,
-                                         boost::bind(&StateModelTest::
-                                                     readyHandler, this)));
+    // 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",
+                                boost::bind(&StateModelTest::readyHandler,
+                                            this)));
 
-    // call transition to move from NEW_ST to DUMMY_ST with START_EVT
+    // "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 you cannot add to the map once the model has started.
-    EXPECT_THROW(addToStateHandlerMap(9999,
-                                      boost::bind(&StateModelTest::readyHandler,
-                                                  this)),
+    // Verify that events and states cannot be added after the model is started.
+    EXPECT_THROW(defineEvent(9999, "9999"), StateModelError);
+    EXPECT_THROW(defineState(9999, "9999",
+                             boost::bind(&StateModelTest::readyHandler, this)),
                  StateModelError);
 
     // The state and event combos set above, should show the model as
@@ -547,20 +629,20 @@ TEST_F(StateModelTest, statusMethods) {
     EXPECT_FALSE(didModelFail());
 }
 
+/// @brief Tests that the model status methods are correct after a model
+/// failure.
 TEST_F(StateModelTest, statusMethodsOnFailure) {
-    // Construct the event definitions, normally done by startModel.
+    // Construct the event and state definitions, normally done by startModel.
     ASSERT_NO_THROW(defineEvents());
     ASSERT_NO_THROW(verifyEvents());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    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());
+    EXPECT_NO_THROW(abortModel("test invocation"));
 
     // With state set to END_ST, model should be done.
     EXPECT_FALSE(isModelNew());
@@ -570,44 +652,92 @@ TEST_F(StateModelTest, statusMethodsOnFailure) {
     EXPECT_TRUE(didModelFail());
 }
 
+/// @brief Checks that the context strings accurately reflect context and
+/// are safe to invoke.
 TEST_F(StateModelTest, contextStrs) {
-    // Construct the event definitions, normally done by startModel.
+    // 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());
-
-    // Manually init the handlers map.
-    ASSERT_NO_THROW(initStateHandlerMap());
-    ASSERT_NO_THROW(verifyStateHandlerMap());
+    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));
 
-    std::string ctx_str = getContextStr();
+    // 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)));
 
-    ctx_str = getPrevContextStr();
+    // 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 invalid states are handled gracefully.
-/// This test verifies that attempting to execute a state which has no handler
-/// results in a failed model.
-TEST_F(StateModelTest, invalidState) {
-    // First, verify state is NEW_ST and that NEW_ST has no handler.
-    // This is the state that runModel will attempt to execute.
-    ASSERT_EQ(NEW_ST, getState());
-    ASSERT_THROW(getStateHandler(NEW_ST), StateModelError);
-
-    // Verify that the StateModelTest's outcome flags are both false.
-    EXPECT_FALSE(getModelFailureCalled());
+/// @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());
 
-    // Now call runModel() which should not throw, but should result
-    // in a failed model and a call to onModelFailure().
-    //EXPECT_NO_THROW(runModel(START_EVT));
-    (runModel(START_EVT));
+    // 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());
@@ -616,19 +746,20 @@ TEST_F(StateModelTest, invalidState) {
     EXPECT_TRUE(isModelDone());
     EXPECT_TRUE(didModelFail());
 
-    // Verify that model failure flag is true.
-    EXPECT_TRUE(getModelFailureCalled());
+    // 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 invalid events are handled gracefully.
-/// This test verifies that submitting an invalid event to the state machine
-/// results in a failed transaction.
-TEST_F(StateModelTest, invalidEvent) {
-    // Verify that the StateModelTest's outcome flags are both false.
-    EXPECT_FALSE(getModelFailureCalled());
+/// @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
@@ -637,13 +768,12 @@ TEST_F(StateModelTest, invalidEvent) {
     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, getState());
+    EXPECT_EQ(DO_WORK_ST, getCurrState());
     EXPECT_EQ(NOP_EVT, getNextEvent());
 
-    // Submitting an invalid event to a valid state, should cause runModel to
-    // return without throwing and yield a failed model. (Current state is
-    // DO_WORK_ST, during which START_EVT, is invalid).
-    EXPECT_NO_THROW(runModel(START_EVT));
+    // 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());
@@ -652,15 +782,15 @@ TEST_F(StateModelTest, invalidEvent) {
     EXPECT_TRUE(isModelDone());
     EXPECT_TRUE(didModelFail());
 
-    // Verify that model failure flag is true.
-    EXPECT_TRUE(getModelFailureCalled());
+    // 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 state to
+/// 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.
@@ -669,8 +799,8 @@ TEST_F(StateModelTest, stateModelTest) {
     EXPECT_FALSE(isModelWaiting());
     EXPECT_FALSE(isModelDone());
 
-    // Verify that the StateModelTest's outcome flags are both false.
-    EXPECT_FALSE(getModelFailureCalled());
+    // 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
@@ -680,7 +810,7 @@ TEST_F(StateModelTest, stateModelTest) {
 
     // 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, getState());
+    EXPECT_EQ(DO_WORK_ST, getCurrState());
     EXPECT_EQ(WORK_START_EVT, getLastEvent());
     EXPECT_EQ(NOP_EVT, getNextEvent());
 
@@ -691,7 +821,7 @@ TEST_F(StateModelTest, stateModelTest) {
     // 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, getState());
+    EXPECT_EQ(END_ST, getCurrState());
     EXPECT_EQ(END_EVT, getNextEvent());
 
     // Verify that status methods are correct: model done.
@@ -700,8 +830,8 @@ TEST_F(StateModelTest, stateModelTest) {
     EXPECT_FALSE(isModelWaiting());
     EXPECT_TRUE(isModelDone());
 
-    // Verify that model failure flag is false.
-    EXPECT_FALSE(getModelFailureCalled());
+    // Verify that failure explanation is empty.
+    EXPECT_TRUE(getFailureExplanation().empty());
 
     // Verify that work completed flag is true.
     EXPECT_TRUE(getWorkCompleted());