From f356520259cff7006465aa09ad050b1cd591d496 Mon Sep 17 00:00:00 2001 From: erick Date: Tue, 27 Oct 2009 21:20:18 +0000 Subject: first complete version of tutorialADT with tests, ready for review --- diff --git a/tests/tutorialtests.py b/tests/tutorialtests.py index ca70e11..3be3415 100644 --- a/tests/tutorialtests.py +++ b/tests/tutorialtests.py @@ -210,14 +210,10 @@ class TutorialTest(unittest.TestCase): assert set(self.tutorial.get_state_dict().keys()) == set([Tutorial._INIT,Tutorial._END]) assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 1 - assert self.tutorial.get_previous_states_dict(Tutorial._INIT).keys() == None + assert self.tutorial.get_previous_states_dict(Tutorial._INIT) == {} assert self.tutorial.get_following_states_dict(Tutorial._INIT).keys() == [Tutorial._END] assert self.tutorial.get_previous_states_dict(Tutorial._END).keys() == [Tutorial._INIT] - assert self.tutorial.get_following_states_dict(Tutorial._END).keys() == None - - def test_initialize_tutorial_from_existing_dictionary(self): - # TODO: to be implemented - assert False + assert self.tutorial.get_following_states_dict(Tutorial._END) == {} #### State def test_add_default_state(self): @@ -237,11 +233,11 @@ class TutorialTest(unittest.TestCase): assert len(self.tutorial.get_transition_dict()) == 1 def test_add_state_with_transition(self): - state_name = self.tutorial.add_state(transition_list=[("event1",Tutorial._END]) + state_name = self.tutorial.add_state(transition_list=[("event1",Tutorial._END)]) assert state_name assert len(self.tutorial.get_state_dict()) == 3 assert set(self.tutorial.get_state_dict().keys()) == set([Tutorial._INIT,Tutorial._END, state_name]) - assert len(self.tutorial.get_action_dict()) == 1 + assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 2 def test_add_generate_unique_state_names(self): @@ -257,25 +253,25 @@ class TutorialTest(unittest.TestCase): assert set(self.tutorial.get_state_dict().keys()) == set([Tutorial._INIT,Tutorial._END]) assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 1 - assert self.tutorial.get_previous_states_dict(Tutorial._INIT).keys() == None + assert self.tutorial.get_previous_states_dict(Tutorial._INIT) == {} assert self.tutorial.get_following_states_dict(Tutorial._INIT).keys() == [Tutorial._END] assert self.tutorial.get_previous_states_dict(Tutorial._END).keys() == [Tutorial._INIT] - assert self.tutorial.get_following_states_dict(Tutorial._END).keys() == None + assert self.tutorial.get_following_states_dict(Tutorial._END) == {} def test_delete_linked_state(self): state_name1 = self.tutorial.add_state() self.tutorial.update_transition(Tutorial._INITIAL_TRANSITION_NAME, \ (Tutorial._AUTOMATIC_TRANSITION_EVENT, state_name1)) - transition_name1 = self.tutorial.add_transition(("event1", Tutorial._END)) + transition_name1 = self.tutorial.add_transition(state_name1,("event1", Tutorial._END)) self.tutorial.delete_state(state_name1) assert len(self.tutorial.get_state_dict()) == 2 assert set(self.tutorial.get_state_dict().keys()) == set([Tutorial._INIT,Tutorial._END]) assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 1 - assert self.tutorial.get_previous_states_dict(Tutorial._INIT).keys() == None + assert self.tutorial.get_previous_states_dict(Tutorial._INIT) == {} assert self.tutorial.get_following_states_dict(Tutorial._INIT).keys() == [Tutorial._END] assert self.tutorial.get_previous_states_dict(Tutorial._END).keys() == [Tutorial._INIT] - assert self.tutorial.get_following_states_dict(Tutorial._END).keys() == None + assert self.tutorial.get_following_states_dict(Tutorial._END) == {} #### Action def test_add_dummy_action(self): @@ -288,73 +284,61 @@ class TutorialTest(unittest.TestCase): def test_add_generate_unique_action_names(self): state_name = self.tutorial.add_state() - action_name1 = self.state.add_action(state_name,"action1") - action_name2 = self.state.add_action(state_name,"action2") + action_name1 = self.tutorial.add_action(state_name,"action1") + action_name2 = self.tutorial.add_action(state_name,"action2") assert action_name1 and action_name2 assert action_name1 != action_name2 def test_update_dummy_action(self): - action_name = self.state.add_action("action1") - self.state.update_action(action_name, "action2") - assert len(self.state.get_action_dict()) == 1 - assert self.state.get_action_dict().has_key(action_name) - assert self.state.get_action_dict()[action_name] == "action2" + state_name = self.tutorial.add_state() + action_name = self.tutorial.add_action(state_name,"action1") + self.tutorial.update_action(action_name, "action2") + assert len(self.tutorial.get_action_dict()) == 1 + assert self.tutorial.get_action_dict().has_key(action_name) + assert self.tutorial.get_action_dict()[action_name] == "action2" def test_delete_dummy_action(self): - action_name = self.state.add_action("action1") - assert len(self.state.get_action_dict()) == 1 - assert self.state.get_action_dict().has_key(action_name) - assert self.state.get_action_dict()[action_name] == "action1" - - self.state.delete_action(action_name) - assert len(self.state.get_action_dict()) == 0 - - def test_delete_all_dummy_actions(self): - action_name = self.state.add_action("action1") - assert len(self.state.get_action_dict()) == 1 - assert self.state.get_action_dict().has_key(action_name) - assert self.state.get_action_dict()[action_name] == "action1" + state_name = self.tutorial.add_state() + action_name = self.tutorial.add_action(state_name,"action1") + assert len(self.tutorial.get_action_dict()) == 1 + assert self.tutorial.get_action_dict().has_key(action_name) + assert self.tutorial.get_action_dict()[action_name] == "action1" - self.state.delete_actions() - assert len(self.state.get_action_dict()) == 0 + self.tutorial.delete_action(action_name) + assert len(self.tutorial.get_action_dict()) == 0 #### Transition def test_add_dummy_transition(self): - transition_name = self.state.add_transition("transition1") - assert len(self.state.get_transition_dict()) == 1 - assert self.state.get_transition_dict().has_key(transition_name) - assert self.state.get_transition_dict()[transition_name] == "transition1" + state_name = self.tutorial.add_state() + transition_name = self.tutorial.add_transition(state_name,"transition1") + assert len(self.tutorial.get_transition_dict()) == 2 + assert self.tutorial.get_transition_dict().has_key(transition_name) + assert self.tutorial.get_transition_dict()[transition_name] == "transition1" def test_add_generate_unique_transition_names(self): - transition_name1 = self.state.add_transition("transition1") - transition_name2 = self.state.add_transition("transition2") + state_name = self.tutorial.add_state() + transition_name1 = self.tutorial.add_transition(state_name,"transition1") + transition_name2 = self.tutorial.add_transition(state_name,"transition2") + assert transition_name1 and transition_name2 assert transition_name1 != transition_name2 def test_update_dummy_transition(self): - transition_name = self.state.add_transition("transition1") - self.state.update_transition(transition_name, "transition2") - assert len(self.state.get_transition_dict()) == 1 - assert self.state.get_transition_dict().has_key(transition_name) - assert self.state.get_transition_dict()[transition_name] == "transition2" + state_name = self.tutorial.add_state() + transition_name = self.tutorial.add_transition(state_name,"transition1") + self.tutorial.update_transition(transition_name, "transition2") + assert len(self.tutorial.get_transition_dict()) == 2 + assert self.tutorial.get_transition_dict().has_key(transition_name) + assert self.tutorial.get_transition_dict()[transition_name] == "transition2" def test_delete_dummy_transition(self): - transition_name = self.state.add_transition("transition1") - assert len(self.state.get_transition_dict()) == 1 - assert self.state.get_transition_dict().has_key(transition_name) - assert self.state.get_transition_dict()[transition_name] == "transition1" - - self.state.delete_transition(transition_name) - assert len(self.state.get_transition_dict()) == 0 - - def test_delete_all_dummy_transitions(self): - transition_name = self.state.add_transition("transition1") - assert len(self.state.get_transition_dict()) == 1 - assert self.state.get_transition_dict().has_key(transition_name) - assert self.state.get_transition_dict()[transition_name] == "transition1" - - self.state.delete_transitions() - assert len(self.state.get_transition_dict()) == 0 + state_name = self.tutorial.add_state() + transition_name = self.tutorial.add_transition(state_name,"transition1") + assert len(self.tutorial.get_transition_dict()) == 2 + assert self.tutorial.get_transition_dict().has_key(transition_name) + assert self.tutorial.get_transition_dict()[transition_name] == "transition1" + self.tutorial.delete_transition(transition_name) + assert len(self.tutorial.get_transition_dict()) == 1 ######################### Limit cases ################################### @@ -368,17 +352,14 @@ class TutorialTest(unittest.TestCase): ######################### Error cases ################################### #### Tutorial - def test_initialize_tutorial_from_wrong_dictionary(self): - # TODO: to be implemented - assert False - + #### State #### Action def test_update_unknown_action(self): name_error = None try: - self.state.update_action("unknown_name", "action") + self.tutorial.update_action("unknown_name", "action") except NameError, e: name_error = e @@ -388,7 +369,7 @@ class TutorialTest(unittest.TestCase): def test_delete_unknown_action(self): name_error = None try: - self.state.delete_action("unknown_name") + self.tutorial.delete_action("unknown_name") except NameError, e: name_error = e @@ -396,10 +377,10 @@ class TutorialTest(unittest.TestCase): #### Transition def test_add_existing_transition(self): - self.state.add_transition("transition") + self.tutorial.add_transition(Tutorial._INIT,("event","transition")) transition_exists_error = None try: - self.state.add_transition("transition") + self.tutorial.add_transition(Tutorial._INIT,("event","transition")) except TransitionAlreadyExists, e: transition_exists_error = e diff --git a/tutorius/tutorial.py b/tutorius/tutorial.py index a902201..8970d4f 100644 --- a/tutorius/tutorial.py +++ b/tutorius/tutorial.py @@ -7,7 +7,7 @@ class Tutorial(object): _INIT = "INIT" _END = "END" - _INITIAL_TRANSITION_NAME = "Transition1" + _INITIAL_TRANSITION_NAME = _INIT + "/transition0" _AUTOMATIC_TRANSITION_EVENT = "automatic" def __init__(self, name, state_dict=None): @@ -15,8 +15,10 @@ class Tutorial(object): The constructor for the Tutorial. By default, the tutorial contains only an initial state and an end state. The initial state doesn't contain any action but it contains - a single automatic transition between the initial state and the - end state, named _INITIAL_TRANSITION. + a single automatic transition + between the initial state and the end state + . + The end state doesn't contain any action nor transition. If state_dict is provided, a valid initial state and an end state @@ -27,16 +29,23 @@ class Tutorial(object): @raise InvalidStateDictionary """ self.name = name + # We will use an adjacency list representation through the # usage of state objects because our graph representation # is really sparse and mostly linear, for a brief # example of graph programming in python see: # http://www.python.org/doc/essays/graphs - self._state_dict = state_dict or \ - {Tutorial._INIT:State(name=Tutorial._INIT),\ - Tutorial._END:State(name=Tutorial._END)} + if not state_dict: + self._state_dict = \ + {Tutorial._INIT:State(name=Tutorial._INIT),\ + Tutorial._END:State(name=Tutorial._END)} + self.add_transition(Tutorial._INIT, \ + (Tutorial._AUTOMATIC_TRANSITION_EVENT, Tutorial._END)) + else: + raise NotImplementedError("Tutorial: Initilization from a dictionary is not supported yet") + # Minimally check for the presence of an INIT and an END # state if not self._state_dict.has_key(Tutorial._INIT): @@ -44,8 +53,9 @@ class Tutorial(object): if not self._state_dict.has_key(Tutorial._END): raise Exception("No END state found in state_dict") - - self.validate() + + # TODO: Validate once validation is working + #self.validate() # Initialize variables for generating unique names # TODO: We should take the max number from the @@ -77,10 +87,6 @@ class Tutorial(object): state = State(name, action_list, transition_list) - if self._state_dict.has_key(name): - raise Exception("Name: " + name + " already exists, could not\ - add a new state.") - self._state_dict[name] = state return name @@ -88,7 +94,7 @@ class Tutorial(object): def add_action(self, state_name, action): """ - Add an action to a specific state. A unique name for this + Add an action to a specific state. A name unique throughout the tutorial is generated to refer precisely to this action and is returned. @@ -97,12 +103,19 @@ class Tutorial(object): @param state_name The name of the state to add an action to @param action The action to be added @return unique name for this action + @raise NameError if state_name doesn't exist """ - return "State/Action" + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + + self._validate_action(action) + + return self._state_dict[state_name].add_action(action) def add_transition(self, state_name, transition): """ - Add a transition to a specific state. A unique name for this + Add a transition to a specific state. A name unique throughout the tutorial is generated to refer precisely to this transition and is returned. Inserting a duplicate transition will raise an exception. @@ -112,35 +125,59 @@ class Tutorial(object): @param state_name The name of the state to add a transition to @param transition The transition to be added @return unique name for this action + @raise NameError if state_name doesn't exist @raise TransitionAlreadyExists """ - return "State/Transition" + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + + self._validate_transition(transition) - def update_action(self, action_name, action): + # The unicity of the transition is validated by the state + return self._state_dict[state_name].add_transition(transition) + + def update_action(self, action_name, new_action): """ - Update the properties of a specific action with a copy of the - properties of the action passed in. + Replace the action with action_name by new_action The action is validated. - @param action_name The name of the action to update - @param action An action with the properties to copy from - @return action_name if the update was successful, False otherwise + @param action_name The name of the action to replace + @param new_action The action that will replace the old one + @return The replaced action + @raise NameError if action_name doesn't exist """ - return action_name + state_name = action_name[:action_name.find("/")] + + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: action <" + action_name +\ + "> is not defined") + + self._validate_action(new_action) + + return self._state_dict[state_name].update_action(action_name, new_action) - def update_transition(self, transition_name, transition): + def update_transition(self, transition_name, new_transition): """ - Update the properties of a specific transition with a copy of the - properties of the transition passed in. + Replace the transition with transition_name by new_transition The transition is validated. - @param transition_name The name of the transition to update - @param transition An transition with the properties to copy from - @return transition_name if the update was successful, False otherwise + @param transition_name The name of the transition to replace + @param new_transition The transition that will replace the old one + @return The replaced transition + @raise NameError if transition_name doesn't exist """ - return transition_name + state_name = transition_name[:transition_name.find("/")] + + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: transition <" + transition_name +\ + "> is not defined") + + self._validate_transition(new_transition) + + return self._state_dict[state_name].update_transition(transition_name, new_transition) def delete_action(self, action_name): """ @@ -148,8 +185,15 @@ class Tutorial(object): @param action_name The name of the action to be deleted @return the action that has been deleted + @raise NameError if transition_name doesn't exist """ - return None + state_name = action_name[:action_name.find("/")] + + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: action <" + action_name +\ + "> is not defined") + + return self._state_dict[state_name].delete_action(action_name) def delete_transition(self, transition_name): """ @@ -157,22 +201,61 @@ class Tutorial(object): @param transition_name The name of the transition to be deleted @return the transition that has been deleted + @raise NameError if transition_name doesn't exist """ - return None + state_name = transition_name[:transition_name.find("/")] + + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: transition <" + transition_name +\ + "> is not defined") + + return self._state_dict[state_name].delete_transition(transition_name) def delete_state(self, state_name): """ Delete the state, delete all the actions and transitions in this state, update the transitions from the state that - pointed to this one to the next state and remove all the + pointed to this one to point to the next state and remove all the unreachable states recursively. All but the INIT and END states can be deleted. @param state_name The name of the state to remove + @return The deleted state @raise StateDeletionError when trying to delete the INIT or the END state + @raise NameError if state_name doesn't exist """ - pass + if state_name == Tutorial._INIT or state_name == Tutorial._END: + raise StateDeletionError("<" + state_name + "> cannot be deleted") + + + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + transition_name +\ + "> is not defined") + + next_states = set(self.get_following_states_dict(state_name).values()) + previous_states = set(self.get_previous_states_dict(state_name).values()) + + # For now tutorials should be completely linear, + # let's make sure they are + assert len(next_states) <= 1 and len(previous_states) <= 1 + + # Update transitions only if they existed + if len(next_states) == 1 and len(previous_states) == 1: + next_state = next_states.pop() + previous_state = previous_states.pop() + + transitions = previous_state.get_transition_dict() + for transition_name, (event, state_to_delete) in \ + transitions.iteritems(): + self.update_transition(transition_name, (event, next_state.name)) + + # Since we assume tutorials are linear for now, we do not need + # to search for unreachable states + + return self._state_dict.pop(state_name) + + def get_action_dict(self, state_name=None): """ @@ -184,7 +267,16 @@ class Tutorial(object): @return A dictionary of actions with action_name as key and action as value for state_name @raise NameError if state_name doesn't exist """ - pass + if state_name and not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + elif state_name: + return self._state_dict.get_action_dict() + else: + action_dict = {} + for state in self._state_dict.itervalues(): + action_dict.update(state.get_action_dict()) + return action_dict def get_transition_dict(self, state_name=None): """ @@ -196,13 +288,23 @@ class Tutorial(object): @return A dictionary of transitions with transition_name as key and transition as value for state_name @raise NameError if state_name doesn't exist """ - pass + if state_name and not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + elif state_name: + return self._state_dict.get_transition_dict() + else: + transition_dict = {} + for state in self._state_dict.itervalues(): + transition_dict.update(state.get_transition_dict()) + return transition_dict + def get_state_dict(self): """ @return A dictionary of all the states in the tutorial with state_name as key and state as value """ - pass + return self._state_dict def get_following_states_dict(self, state_name): """ @@ -212,8 +314,16 @@ class Tutorial(object): @param state_name The name of the state @raise NameError if state_name doesn't exist """ - pass + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + + following_states_dict = {} + for (event, next_state) in \ + self._state_dict[state_name].get_transition_dict().itervalues(): + following_states_dict[next_state] = self._state_dict[next_state] + return following_states_dict def get_previous_states_dict(self, state_name): """ @@ -223,7 +333,27 @@ class Tutorial(object): @param state_name The name of the state @raise NameError if state_name doesn't exist """ - pass + if not self._state_dict.has_key(state_name): + raise NameError("Tutorial: state <" + state_name +\ + "> is not defined") + + + previous_states_dict = {} + for iter_state_name, state in \ + self._state_dict.iteritems(): + + for (event, next_state) in \ + self._state_dict[iter_state_name].get_transition_dict().itervalues(): + + if next_state != state_name: + continue + + previous_states_dict[iter_state_name] = state + # if we have found one, do not look for other transitions + # from this state + break + + return previous_states_dict # Convenience methods for common tutorial manipulations def add_state_before(self, state_name, action_list=[], event_list=[]): @@ -242,7 +372,7 @@ class Tutorial(object): @return unique name for this state @raise NameError if state_name doesn't exist """ - pass + raise NotImplementedError # Callback mecanism to allow automatic change notification when # the tutorial is modified @@ -261,7 +391,7 @@ class Tutorial(object): @raise InvalidCallbackFunction if the callback has less or more than 2 arguments """ - pass + raise NotImplementedError def register_action_updated_cb(self, cb): """ @@ -278,7 +408,7 @@ class Tutorial(object): @raise InvalidCallbackFunction if the callback has less or more than 2 arguments """ - pass + raise NotImplementedError def register_action_deleted_cb(self, cb): """ @@ -295,7 +425,7 @@ class Tutorial(object): @raise InvalidCallbackFunction if the callback has less or more than 2 arguments """ - pass + raise NotImplementedError def register_transition_updated_cb(self, cb): """ @@ -313,7 +443,7 @@ class Tutorial(object): @raise InvalidCallbackFunction if the callback has less or more than 2 arguments """ - pass + raise NotImplementedError # Validation to assert precondition def _validate_action(self, action): @@ -326,15 +456,6 @@ class Tutorial(object): """ pass - def _validate_action_name(self, action_name): - """ - Check if action_name exists. - - @param action_name The name to check - @except UnknownName if the name is not present in the Tutorial - """ - pass - def _validate_transition(self, transition): """ Validate that a transition conforms to what we expect, @@ -345,15 +466,6 @@ class Tutorial(object): """ pass - def _validate_transition_name(self, transition_name): - """ - Check if transition_name exists. - - @param transition_name The name to check - @except UnknownName if the name is not present in the Tutorial - """ - pass - def validate(self): """ Validate the state machine for a serie of properties: @@ -365,7 +477,7 @@ class Tutorial(object): Throw an exception for the first condition that is not met. """ - pass + raise NotImplementedError def _generate_unique_state_name(self): name = "State" + str(self._state_name_nb) @@ -376,7 +488,7 @@ class Tutorial(object): """ Return a string representation of the tutorial """ - return "" + return str(self._state_dict) class State(object): """ @@ -388,7 +500,7 @@ class State(object): inputs, the validation should be done by the containing class. """ - def __init__(self, name="", action_list=[], transition_list=[]): + def __init__(self, name, action_list=[], transition_list=[]): """ Initializes the content of the state, such as loading the actions that are required and building the correct transitions. @@ -402,6 +514,10 @@ class State(object): object.__init__(self) self.name = name + + # Initialize internal variables for name generation + self.action_name_nb = 0 + self.transition_name_nb = 0 self._actions = {} for action in action_list: @@ -411,9 +527,6 @@ class State(object): for transition in transition_list: self._transitions[self._generate_unique_transition_name(transition)] = transition - # Initialize internal variables for name generation - self.action_name_nb = 0 - self.transition_name_nb = 0 # Action manipulations def add_action(self, new_action): @@ -438,7 +551,7 @@ class State(object): if self._actions.has_key(action_name): return self._actions.pop(action_name) else: - raise NameError("Tutorial.State: action '" + action_name + "' is not defined") + raise NameError("Tutorial.State: action <" + action_name + "> is not defined") def update_action(self, action_name, new_action): """ @@ -457,7 +570,7 @@ class State(object): self._actions[action_name] = new_action return old_action else: - raise NameError("Tutorial.State: action '" + action_name + "' is not defined") + raise NameError("Tutorial.State: action <" + action_name + "> is not defined") def get_action_dict(self): """ @@ -508,7 +621,7 @@ class State(object): self._transitions[transition_name] = new_transition return old_transition else: - raise NameError("Tutorial.State: transition '" + transition_name + "' is not defined") + raise NameError("Tutorial.State: transition <" + transition_name + "> is not defined") def delete_transition(self, transition_name): """ @@ -521,7 +634,7 @@ class State(object): if self._transitions.has_key(transition_name): return self._transitions.pop(transition_name) else: - raise NameError("Tutorial.State: transition '" + transition_name + "' is not defined") + raise NameError("Tutorial.State: transition <" + transition_name + "> is not defined") def get_transition_dict(self): """ @@ -547,7 +660,7 @@ class State(object): #TODO use the action class name to generate a name # to make it easier to debug and know what we are # manipulating - name = "action" + str(self.action_name_nb) + name = self.name + "/" + "action" + str(self.action_name_nb) self.action_name_nb += 1 return name @@ -563,7 +676,7 @@ class State(object): #TODO use the event class name from the transition to # generate a name to make it easier to debug and know # what we are manipulating - name = "transition" + str(self.transition_name_nb) + name = self.name + "/" + "transition" + str(self.transition_name_nb) self.transition_name_nb += 1 return name -- cgit v0.9.1