From 60109dea12383f8ea2f6d4251952ccc3859feec5 Mon Sep 17 00:00:00 2001 From: erick Date: Wed, 28 Oct 2009 02:03:51 +0000 Subject: TutorialADT: First pass for modifications following Simon review, still has some changes to do --- diff --git a/tests/tutorialtests.py b/tests/tutorialtests.py index 3be3415..c1f591f 100644 --- a/tests/tutorialtests.py +++ b/tests/tutorialtests.py @@ -1,5 +1,5 @@ # Copyright (C) 2009, Tutorius.org -# Copyright (C) 2009, Michael Janelle-Montcalm +# Copyright (C) 2009, Erick Lavoie # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -15,21 +15,16 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA """ -Core Tests - -This module contains all the tests that pertain to the usage of the Tutorius -Core. This means that the Event Filters, the Finite State Machine and all the -related elements and interfaces are tested here. +Tutorial Tests +""" -Usage of actions and event filters is tested, but not the concrete actions -and event filters. Those are in their separate test module +# TODO: Add tests for 47, 52, 55, 109, 132, 175, 209, 229, 233, 271, 274, 292, 295, 318, 337, 375, 394, 411, 428, 446, 480, 491, 624, 637, 698 -""" +# TODO: Use doc strings for test names that are too long +# TODO: Use helper functions for common checks import unittest -import copy -import logging from sugar.tutorius.tutorial import * # The following tests are organized around 4 classes: @@ -162,36 +157,27 @@ class StateTest(unittest.TestCase): ######################### Error cases ################################### #### Action def test_update_unknown_action(self): - name_error = None try: self.state.update_action("unknown_name", "action") - except NameError, e: - name_error = e - - assert name_error - + assert False + except LookupError: + pass def test_delete_unknown_action(self): - name_error = None try: self.state.delete_action("unknown_name") - except NameError, e: - name_error = e - - assert name_error + assert False + except LookupError: + pass #### Transition def test_add_existing_transition(self): self.state.add_transition("transition") - transition_exists_error = None try: self.state.add_transition("transition") - except TransitionAlreadyExists, e: - transition_exists_error = e - - assert transition_exists_error - - + assert False + except TransitionAlreadyExists: + pass class TutorialTest(unittest.TestCase): """Test tutorial functionality""" @@ -207,20 +193,20 @@ class TutorialTest(unittest.TestCase): #### Tutorial def test_default_initial_value_in_tutorial(self): assert len(self.tutorial.get_state_dict()) == 2 - assert set(self.tutorial.get_state_dict().keys()) == set([Tutorial._INIT,Tutorial._END]) + 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) == {} - 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) == {} + 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) == {} #### State def test_add_default_state(self): state_name = self.tutorial.add_state() 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 set(self.tutorial.get_state_dict().keys()) == set([Tutorial.INIT,Tutorial.END, state_name]) assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 1 @@ -228,15 +214,15 @@ class TutorialTest(unittest.TestCase): state_name = self.tutorial.add_state(action_list=["action1"]) 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 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_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 set(self.tutorial.get_state_dict().keys()) == set([Tutorial.INIT,Tutorial.END, state_name]) assert len(self.tutorial.get_action_dict()) == 0 assert len(self.tutorial.get_transition_dict()) == 2 @@ -250,28 +236,28 @@ class TutorialTest(unittest.TestCase): state_name1 = self.tutorial.add_state() 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 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) == {} - 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) == {} + 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) == {} 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(state_name1,("event1", Tutorial._END)) + self.tutorial.update_transition(Tutorial.INITIAL_TRANSITION_NAME, \ + (Tutorial.AUTOMATIC_TRANSITION_EVENT, state_name1)) + 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 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) == {} - 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) == {} + 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) == {} #### Action def test_add_dummy_action(self): @@ -357,30 +343,30 @@ class TutorialTest(unittest.TestCase): #### Action def test_update_unknown_action(self): - name_error = None + lookup_error = None try: self.tutorial.update_action("unknown_name", "action") - except NameError, e: - name_error = e + except LookupError, e: + lookup_error = e - assert name_error + assert lookup_error def test_delete_unknown_action(self): - name_error = None + lookup_error = None try: self.tutorial.delete_action("unknown_name") - except NameError, e: - name_error = e + except LookupError, e: + lookup_error = e - assert name_error + assert lookup_error #### Transition def test_add_existing_transition(self): - self.tutorial.add_transition(Tutorial._INIT,("event","transition")) + self.tutorial.add_transition(Tutorial.INIT,("event","transition")) transition_exists_error = None try: - self.tutorial.add_transition(Tutorial._INIT,("event","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 8970d4f..6a6e089 100644 --- a/tutorius/tutorial.py +++ b/tutorius/tutorial.py @@ -1,3 +1,21 @@ +# Copyright (C) 2009, Tutorius.org +# Copyright (C) 2009, Erick Lavoie +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +#TODO: On update, only properties should be passed as arguments for State.update and Tutorial.update class Tutorial(object): """ This class replaces the previous Tutorial class and @@ -5,19 +23,21 @@ class Tutorial(object): of a tutorial as a state machine """ - _INIT = "INIT" - _END = "END" - _INITIAL_TRANSITION_NAME = _INIT + "/transition0" - _AUTOMATIC_TRANSITION_EVENT = "automatic" + INIT = "INIT" + END = "END" + INITIAL_TRANSITION_NAME = INIT + "/transition0" + AUTOMATIC_TRANSITION_EVENT = "automatic" + + _NAME_SEPARATOR = "/" def __init__(self, name, state_dict=None): """ 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 - . + a single automatic transition + between the initial state and the end state + . The end state doesn't contain any action nor transition. @@ -38,20 +58,20 @@ class Tutorial(object): # http://www.python.org/doc/essays/graphs if not state_dict: self._state_dict = \ - {Tutorial._INIT:State(name=Tutorial._INIT),\ - Tutorial._END:State(name=Tutorial._END)} + {Tutorial.INIT:State(name=Tutorial.INIT),\ + Tutorial.END:State(name=Tutorial.END)} - self.add_transition(Tutorial._INIT, \ - (Tutorial._AUTOMATIC_TRANSITION_EVENT, 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): + if not self._state_dict.has_key(Tutorial.INIT): raise Exception("No INIT state found in state_dict") - if not self._state_dict.has_key(Tutorial._END): + if not self._state_dict.has_key(Tutorial.END): raise Exception("No END state found in state_dict") # TODO: Validate once validation is working @@ -103,10 +123,10 @@ 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 + @raise LookupError if state_name doesn't exist """ if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") self._validate_action(action) @@ -125,11 +145,11 @@ 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 LookupError if state_name doesn't exist @raise TransitionAlreadyExists """ if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") self._validate_transition(transition) @@ -146,12 +166,12 @@ class Tutorial(object): @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 + @raise LookupError if action_name doesn't exist """ - state_name = action_name[:action_name.find("/")] + state_name = action_name[:action_name.find(Tutorial._NAME_SEPARATOR)] if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: action <" + action_name +\ + raise LookupError("Tutorial: action <" + action_name +\ "> is not defined") self._validate_action(new_action) @@ -167,12 +187,12 @@ class Tutorial(object): @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 + @raise LookupError if transition_name doesn't exist """ - state_name = transition_name[:transition_name.find("/")] + state_name = transition_name[:transition_name.find(Tutorial._NAME_SEPARATOR)] if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: transition <" + transition_name +\ + raise LookupError("Tutorial: transition <" + transition_name +\ "> is not defined") self._validate_transition(new_transition) @@ -185,12 +205,12 @@ 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 + @raise LookupError if transition_name doesn't exist """ - state_name = action_name[:action_name.find("/")] + state_name = action_name[:action_name.find(Tutorial._NAME_SEPARATOR)] if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: action <" + action_name +\ + raise LookupError("Tutorial: action <" + action_name +\ "> is not defined") return self._state_dict[state_name].delete_action(action_name) @@ -201,12 +221,12 @@ 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 + @raise LookupError if transition_name doesn't exist """ - state_name = transition_name[:transition_name.find("/")] + state_name = transition_name[:transition_name.find(Tutorial._NAME_SEPARATOR)] if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: transition <" + transition_name +\ + raise LookupError("Tutorial: transition <" + transition_name +\ "> is not defined") return self._state_dict[state_name].delete_transition(transition_name) @@ -223,14 +243,13 @@ class Tutorial(object): @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 + @raise LookupError if state_name doesn't exist """ - if state_name == Tutorial._INIT or state_name == Tutorial._END: + 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 +\ + raise LookupError("Tutorial: state <" + transition_name +\ "> is not defined") next_states = set(self.get_following_states_dict(state_name).values()) @@ -259,16 +278,18 @@ class Tutorial(object): def get_action_dict(self, state_name=None): """ - Returns a dictionary of all actions for a specific state. + Returns a reference to the dictionary of all actions for a specific + state. If no state_name is provided, returns an action dictionary containing actions for all states. @param state_name The name of the state to list actions from - @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 + @return A dictionary of actions with action_name as key and action + as value for state_name + @raise LookupError if state_name doesn't exist """ if state_name and not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") elif state_name: return self._state_dict.get_action_dict() @@ -286,10 +307,10 @@ class Tutorial(object): @param state_name The name of the state to list actions from @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 + @raise LookupError if state_name doesn't exist """ if state_name and not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") elif state_name: return self._state_dict.get_transition_dict() @@ -302,7 +323,10 @@ class Tutorial(object): def get_state_dict(self): """ - @return A dictionary of all the states in the tutorial with state_name as key and state as value + Returns a reference to the internal state dictionary used by + the Tutorial. + + @return A reference to the dictionary of all the states in the tutorial with state_name as key and state as value """ return self._state_dict @@ -312,10 +336,10 @@ class Tutorial(object): a specific state. @param state_name The name of the state - @raise NameError if state_name doesn't exist + @raise LookupError if state_name doesn't exist """ if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") following_states_dict = {} @@ -331,10 +355,10 @@ class Tutorial(object): specific state. @param state_name The name of the state - @raise NameError if state_name doesn't exist + @raise LookupError if state_name doesn't exist """ if not self._state_dict.has_key(state_name): - raise NameError("Tutorial: state <" + state_name +\ + raise LookupError("Tutorial: state <" + state_name +\ "> is not defined") @@ -370,7 +394,7 @@ class Tutorial(object): @param action_list The list of valid actions for this state @param event_list The list of events that will be converted to transitions to state_name @return unique name for this state - @raise NameError if state_name doesn't exist + @raise LookupError if state_name doesn't exist """ raise NotImplementedError @@ -521,11 +545,11 @@ class State(object): self._actions = {} for action in action_list: - self._actions[self._generate_unique_action_name(action)] = action + self.add_action(action) self._transitions = {} for transition in transition_list: - self._transitions[self._generate_unique_transition_name(transition)] = transition + self.add_transition(transition) # Action manipulations @@ -546,12 +570,12 @@ class State(object): @param action_name The name of the action to delete @return The action deleted - @raise NameError if action_name doesn't exist + @raise LookupError if action_name doesn't exist """ 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 LookupError("Tutorial.State: action <" + action_name + "> is not defined") def update_action(self, action_name, new_action): """ @@ -560,7 +584,7 @@ class State(object): @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 + @raise LookupError if action_name doesn't exist """ # TODO: For now let's just replace the action with a new one, # we should check to see if we need a replace or an update @@ -570,10 +594,12 @@ class State(object): self._actions[action_name] = new_action return old_action else: - raise NameError("Tutorial.State: action <" + action_name + "> is not defined") + raise LookupError("Tutorial.State: action <" + action_name + "> is not defined") def get_action_dict(self): """ + Return the reference to the internal action dictionary. + @return A dictionary of actions that the state will execute """ return self._actions @@ -611,7 +637,7 @@ class State(object): @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 + @raise LookupError if transition_name doesn't exist """ # TODO: For now let's just replace the transition with a new one, # we should check to see if we need a replace or an update @@ -621,7 +647,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 LookupError("Tutorial.State: transition <" + transition_name + "> is not defined") def delete_transition(self, transition_name): """ @@ -629,15 +655,17 @@ class State(object): @param transition_name The name of the transition to delete @return The transition deleted - @raise NameError if transition_name doesn't exist + @raise LookupError if transition_name doesn't exist """ 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 LookupError("Tutorial.State: transition <" + transition_name + "> is not defined") def get_transition_dict(self): """ + Return the reference to the internal transition dictionary. + @return The dictionary of transitions associated with this state. """ return self._transitions @@ -660,7 +688,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 = self.name + "/" + "action" + str(self.action_name_nb) + name = self.name + Tutorial._NAME_SEPARATOR + "action" + str(self.action_name_nb) self.action_name_nb += 1 return name @@ -676,10 +704,28 @@ 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 = self.name + "/" + "transition" + str(self.transition_name_nb) + name = self.name + Tutorial._NAME_SEPARATOR + "transition" + str(self.transition_name_nb) self.transition_name_nb += 1 return name + def __eq__(self, otherState): + """ + Compare current state to otherState. + + Two states are considered equal if and only if: + -every action in this state has a matching action in the + other state with the same properties and values + -every event filters in this state has a matching filter in the + other state having the same properties and values + -both states have the same name. + + + @param otherState The state that will be compared to this one + @return True if the states are the same, False otherwise +` """ + raise NotImplemented + + ################## Error Handling and Exceptions ############################## -- cgit v0.9.1