Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormike <michael.jmontcalm@gmail.com>2009-03-18 19:04:33 (GMT)
committer mike <michael.jmontcalm@gmail.com>2009-03-18 19:04:33 (GMT)
commit79ffdf4c4c38e0064f25db06685af9156b39e679 (patch)
treecb1f2934acd54e984c65cab7523fc1db4a71cbcc
parentf5d13236595810710aaab6c9622a12dc86045166 (diff)
TutoriusV2 : Correcting constructors for States and FSMs, exploration
tests
-rw-r--r--src/sugar/tutorius/core.py97
-rw-r--r--src/sugar/tutorius/tests/coretests.py56
2 files changed, 119 insertions, 34 deletions
diff --git a/src/sugar/tutorius/core.py b/src/sugar/tutorius/core.py
index 4b9e985..d0fc3cb 100644
--- a/src/sugar/tutorius/core.py
+++ b/src/sugar/tutorius/core.py
@@ -23,6 +23,7 @@ This module contains the core classes for tutorius
import gtk
import logging
+import copy
from sugar.tutorius.dialog import TutoriusDialog
from sugar.tutorius.gtkutils import find_widget
@@ -101,7 +102,7 @@ class State(object):
with associated actions that point to a possible next state.
"""
- def __init__(self, name, action_list=[], event_filter_list=[], tutorial=None):
+ def __init__(self, name, action_list=None, event_filter_list=None, tutorial=None):
"""
Initializes the content of the state, like loading the actions
that are required and building the correct tests.
@@ -117,12 +118,12 @@ class State(object):
self.name = name
- self._actions = action_list
+ self._actions = action_list or []
# Unused for now
#self.tests = []
- self._event_filters = event_filter_list
+ self._event_filters = event_filter_list or []
self.tutorial = tutorial
@@ -188,8 +189,8 @@ class State(object):
@param new_action The new action to execute when in this state
@return True if added, False otherwise
"""
- if new_action not in self.action_list:
- self.action_list.append(new_action)
+ if new_action not in self._actions:
+ self._actions.append(new_action)
return True
return False
@@ -200,14 +201,14 @@ class State(object):
"""
@return A list of actions that the state will execute
"""
- return self.action_list
+ return self._actions
def clear_actions(self):
"""
Removes all the action associated with this state. A cleared state will
not do anything when entered or exited.
"""
- self.action_list.clear()
+ self._actions.clear()
def add_event_filter(self, event_filter):
"""
@@ -218,8 +219,8 @@ class State(object):
@param event_filter The new event filter that will trigger a transition
@return True if added, False otherwise
"""
- if event_filter not in self.event_filter_list:
- self.event_filter_list.append(event_filter)
+ if event_filter not in self._event_filters:
+ self._event_filters.append(event_filter)
return True
return False
@@ -227,7 +228,7 @@ class State(object):
"""
@return The list of event filters associated with this state.
"""
- return self.event_filter_list
+ return self._event_filters
def clear_event_filters(self):
"""
@@ -235,7 +236,7 @@ class State(object):
was just cleared will become a sink and will be the end of the
tutorial.
"""
- self.event_filter_list.clear()
+ self._event_filters.clear()
class FiniteStateMachine(State):
"""
@@ -248,7 +249,7 @@ class FiniteStateMachine(State):
inserted in the FSM, and that there are no nested FSM inside.
"""
- def __init__(self, name, tutorial=None, state_dict={}, start_state_name="INIT", action_list=[]):
+ def __init__(self, name, tutorial=None, state_dict=None, start_state_name="INIT", action_list=None):
"""
The constructor for a FSM. Pass in the start state and the setup
actions that need to be taken when the FSM itself start (which may be
@@ -270,18 +271,20 @@ class FiniteStateMachine(State):
self.tutorial = tutorial
# Dictionnary of states contained in the FSM
- self._states = state_dict
+ self._states = state_dict or {}
- # Remember the initial state - we might want to reset
- # or rewind the FSM at a later moment
- self.start_state = state_dict[start_state_name]
- self.current_state = self.start_state
+ self.start_state_name = start_state_name
+ # If we have a filled input dictionary
+ if len(self._states) > 0:
+ self.current_state = self._states[self.start_state_name]
+ else:
+ self.current_state = None
# Register the actions for the FSM - They will be processed at the
# FSM level, meaning that when the FSM will start, it will first
# execute those actions. When the FSM closes, it will tear down the
# inner actions of the state, then close its own actions
- self.actions = action_list
+ self.actions = action_list or []
# Flag to mention that the FSM was initialized
self._fsm_setup_done = False
@@ -320,6 +323,10 @@ class FiniteStateMachine(State):
# If we never initialized the FSM itself, then we need to run all the
# actions associated with the FSM.
if self._fsm_setup_done == False:
+ # Remember the initial state - we might want to reset
+ # or rewind the FSM at a later moment
+ self.start_state = self._states[self.start_state_name]
+ self.current_state = self.start_state
# Flag the FSM level setup as done
self._fsm_setup_done = True
# Execute all the FSM level actions
@@ -372,7 +379,8 @@ class FiniteStateMachine(State):
Revert any changes done by setup()
"""
# Teardown the current state
- self.current_state.teardown()
+ if self.current_state is not None:
+ self.current_state.teardown()
# If we just finished the whole FSM, we need to also call the teardown
# on the FSM level actions
@@ -395,10 +403,23 @@ class FiniteStateMachine(State):
@param new_state The State object that will now be part of the FSM
@raise KeyError In the case where a state with this name already exists
"""
- if self.state_dict.has_key(new_state.name):
+ if self._states.has_key(new_state.name):
raise KeyError("There is already a state by this name in the FSM")
- self.state_dict[new_state.name] = new_state
+ self._states[new_state.name] = new_state
+
+ # Not such a great name for the state accessor... We already have a
+ # set_state name, so get_state would conflict with the notion of current
+ # state - I would recommend having a set_current_state instead.
+ def get_state_by_name(self, state_name):
+ """
+ Fetches a state from the FSM, based on its name. If there is no
+ such state, the method will throw a KeyError.
+
+ @param state_name The name of the desired state
+ @return The State object having the given name
+ """
+ return self._states[state_name]
def remove_state(self, state_name):
"""
@@ -415,18 +436,21 @@ class FiniteStateMachine(State):
stored in the dictionary
"""
- state_to_remove = self.state_dict[state_name]
+ state_to_remove = self._states[state_name]
# Remove the state from the states' dictionnary
- for st in self.state_dict.itervalues():
+ for st in self._states.itervalues():
# Iterate through the list of event filters and remove those
# that point to the state that will be removed
- for event_filter in st.event_filter_list:
+
+ #TODO : Move this code inside the State itself - we're breaking
+ # encap :P
+ for event_filter in st._event_filters:
if event_filter.get_next_state() == state_name:
- st.event_filter_list.remove(event_filter)
+ st._event_filters.remove(event_filter)
# Remove the state from the dictionary
- del self.state_dict[state_name]
+ del self._states[state_name]
# Exploration methods - used to know more about a given state
def get_following_states(self, state_name):
@@ -437,12 +461,12 @@ class FiniteStateMachine(State):
@param state_name The name of the state to analyse
@raise KeyError When there is no state by this name in the FSM
"""
- state = self.state_dict[state_name]
+ state = self._states[state_name]
- next_states = Set()
+ next_states = set()
- for event_filter in state.event_filter_list:
- next_states.insert(event_filter.get_next_state())
+ for event_filter in state._event_filters:
+ next_states.add(event_filter.get_next_state())
return tuple(next_states)
@@ -463,10 +487,17 @@ class FiniteStateMachine(State):
states = []
# Walk through the list of states
- for st in self.state_dict.itervalues():
- for event_filter in st.event_filter_list:
+ for st in self._states.itervalues():
+ for event_filter in st._event_filters:
if event_filter.get_next_state() == state_name:
states.append(event_filter.get_next_state())
continue
- return tuple(states) \ No newline at end of file
+ return tuple(states)
+
+ # Convenience methods to see the content of a FSM
+ def __str__(self):
+ out_string = ""
+ for st in self._states.itervalues():
+ out_string += st.name + ", "
+ return out_string \ No newline at end of file
diff --git a/src/sugar/tutorius/tests/coretests.py b/src/sugar/tutorius/tests/coretests.py
index 086c833..297a7c3 100644
--- a/src/sugar/tutorius/tests/coretests.py
+++ b/src/sugar/tutorius/tests/coretests.py
@@ -100,7 +100,7 @@ class FakeEventFilter(TriggerEventFilter):
The difference between this one and the TriggerEventFilter is that the
tutorial's set_state will be called on the callback.
- Do not forget to add the do_callback() function.
+ Do not forget to add the do_callback() after creating the object.
"""
def set_tutorial(self, tutorial):
self.tutorial = tutorial
@@ -251,5 +251,59 @@ class FSMTest(unittest.TestCase):
assert act_second.active == False, "FSM did not teardown SECOND properly"
+class FSMExplorationTests(unittest.TestCase):
+ def buildFSM(self):
+ """
+ Create a sample FSM to play with in the rest of the tests.
+ """
+ st1 = State("INIT")
+ st1.add_action(CountAction())
+ st1.add_event_filter(TriggerEventFilter("Second"))
+ st1.add_event_filter(TriggerEventFilter("Third"))
+
+ st2 = State("Second")
+ st2.add_action(TrueWhileActiveAction())
+ st2.add_event_filter(TriggerEventFilter("Third"))
+ st2.add_event_filter(TriggerEventFilter("Fourth"))
+
+ st3 = State("Third")
+ st3.add_action(CountAction())
+ st3.add_action(TrueWhileActiveAction())
+
+ self.fsm = FiniteStateMachine("ExplorationTestingMachine")
+ self.fsm.add_state(st1)
+ self.fsm.add_state(st2)
+ self.fsm.add_state(st3)
+
+ def validate_following_states(self, in_name, out_name_list):
+ nextStates = self.fsm.get_following_states(in_name)
+ assert list(nextStates).sort() == list(out_name_list).sort(), \
+ "The following states for %s are wrong : got %s"%\
+ (in_name, str(nextStates))
+
+ def validate_previous_states(self, in_name, out_name_list):
+ prevStates = self.fsm.get_previous_states(in_name)
+ assert list(prevStates).sort() == list(out_name_list).sort(), \
+ "The following states for %s are wrong : got %s"%\
+ (in_name, str(prevStates))
+
+ def test_get_following_states(self):
+ self.buildFSM()
+ self.validate_following_states("INIT", ('Second', 'Third'))
+
+ self.validate_following_states("Second", ("Third", "Fourth"))
+
+ self.validate_following_states("Third", ())
+
+ def test_get_previous_states(self):
+ self.buildFSM()
+ self.validate_previous_states("INIT", ())
+
+ self.validate_previous_states("Second", ("INIT"))
+
+ self.validate_previous_states("Third", ("INIT", "Second"))
+
+ self.validate_previous_states("Fourth", ("Second"))
+
if __name__ == "__main__":
unittest.main()