Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/sugar/tutorius/actions.py70
-rw-r--r--src/sugar/tutorius/bundler.py2
-rw-r--r--src/sugar/tutorius/constraints.py26
-rw-r--r--src/sugar/tutorius/core.py14
-rw-r--r--src/sugar/tutorius/properties.py12
-rw-r--r--src/sugar/tutorius/tests/actiontests.py79
-rw-r--r--src/sugar/tutorius/tests/constraintstests.py38
-rw-r--r--src/sugar/tutorius/tests/coretests.py41
-rw-r--r--src/sugar/tutorius/tests/propertiestests.py40
-rwxr-xr-xsrc/sugar/tutorius/tests/run-tests.py27
10 files changed, 249 insertions, 100 deletions
diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py
index ad91fb4..d81e3c2 100644
--- a/src/sugar/tutorius/actions.py
+++ b/src/sugar/tutorius/actions.py
@@ -23,12 +23,13 @@ from dialog import TutoriusDialog
import overlayer
from sugar.tutorius.editor import WidgetIdentifier
from sugar.tutorius.services import ObjectStore
-
+from sugar.tutorius.properties import *
class Action(object):
"""Base class for Actions"""
def __init__(self):
object.__init__(self)
+ self.properties = {}
def do(self, **kwargs):
"""
@@ -43,12 +44,12 @@ class Action(object):
pass #Should raise NotImplemented?
def get_properties(self):
- if not hasattr(self, "_props") or self._props is None:
- self._props = []
- for i in dir(self.__class__):
- if type(getattr(self.__class__,i)) is property:
- self._props.append(i)
- return self._props
+ if self.properties is None or len(self.properties) == 0:
+ self.properties = {}
+ for i in dir(self):
+ if isinstance(getattr(self,i), TutoriusProperty):
+ self.properties[i] = getattr(self,i)
+ return self.properties.keys()
class OnceWrapper(object):
"""
@@ -89,36 +90,21 @@ class DialogMessage(Action):
@param message A string to display to the user
@param pos A list of the form [x, y]
"""
- def __init__(self, message, pos=[0,0]):
+ def __init__(self, message, pos=None):
super(DialogMessage, self).__init__()
- self._message = message
- self._position = pos
self._dialog = None
-
- def set_message(self, msg):
- self._message = msg
-
- def get_message(self):
- return self._message
-
- message = property(fget=get_message, fset=set_message)
-
- def set_pos(self, x, y):
- self._position = [x, y]
- def get_pos(self):
- return self._position
-
- position = property(fget=get_pos, fset=set_pos)
+ self.message = TStringProperty(message)
+ self.position = TArrayProperty(pos or [0, 0], 2, 2)
def do(self):
"""
Show the dialog
"""
- self._dialog = TutoriusDialog(self._message)
+ self._dialog = TutoriusDialog(self.message.value)
self._dialog.set_button_clicked_cb(self._dialog.close_self)
self._dialog.set_modal(False)
- self._dialog.move(self.position[0], self.position[1])
+ self._dialog.move(self.position.value[0], self.position.value[1])
self._dialog.show()
def undo(self):
@@ -137,34 +123,20 @@ class BubbleMessage(Action):
@param message A string to display to the user
@param pos A list of the form [x, y]
@param speaker treeish representation of the speaking widget
+ @param tailpos The position of the tail of the bubble; useful to point to
+ specific elements of the interface
"""
def __init__(self, message, pos=[0,0], speaker=None, tailpos=None):
Action.__init__(self)
- self._message = message
- self._position = pos
-
+ self.message = TStringProperty(message)
+ # Create the position as an array of fixed-size 2
+ self.position = TArrayProperty(pos, 2, 2)
+ # Do the same for the tail position
+ self.tail_pos = TArrayProperty(tailpos, 2, 2)
+
self.overlay = None
self._bubble = None
self._speaker = None
- self._tailpos = tailpos
-
- def set_message(self, msg):
- self._message = msg
- def get_message(self):
- return self._message
- message = property(fget=get_message, fset=set_message, doc="Message displayed to the user")
-
- def set_pos(self, x, y):
- self._position = [x, y]
- def get_pos(self):
- return self.position
- position = property(fget=get_pos, fset=set_pos, doc="Position in [x, y] on the screen")
-
- def set_tail_pos(self, x, y):
- self._tailpos = [x, y]
- def get_tail_pos(self):
- return self._tailpos
- tail_pos = property(fget=get_tail_pos, fset=set_tail_pos, doc="Position the tail of the bubble must point to")
def do(self):
"""
diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py
index 00ab3b6..a28d6ef 100644
--- a/src/sugar/tutorius/bundler.py
+++ b/src/sugar/tutorius/bundler.py
@@ -322,6 +322,8 @@ class XMLSerializer(Serializer):
events = self._load_xml_event_filters(fsm_elem.getElementsByName("EventFiltersList"))
for event in events:
fsm.add_event_filter(event)
+
+ return fsm
def load_fsm(self, guid):
diff --git a/src/sugar/tutorius/constraints.py b/src/sugar/tutorius/constraints.py
index a666ecb..0e09664 100644
--- a/src/sugar/tutorius/constraints.py
+++ b/src/sugar/tutorius/constraints.py
@@ -81,10 +81,10 @@ class LowerLimitConstraint(ValueConstraint):
raise LowerLimitConstraintError()
return
-class SizeConstraintError(Exception):
+class MaxSizeConstraintError(Exception):
pass
-class SizeConstraint(ValueConstraint):
+class MaxSizeConstraint(ValueConstraint):
def validate(self, value):
"""
Evaluate whether a given object is smaller than the given size when
@@ -94,9 +94,27 @@ class SizeConstraint(ValueConstraint):
bigger than the limit.
"""
if self.limit is not None:
- if self.limit > len(value):
+ if self.limit >= len(value):
return
- raise SizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit))
+ raise MaxSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit))
+ return
+
+class MinSizeConstraintError(Exception):
+ pass
+
+class MinSizeConstraint(ValueConstraint):
+ def validate(self, value):
+ """
+ Evaluate whether a given object is smaller than the given size when
+ run through len(). Great for string, lists and the like. ;)
+
+ @raise SizeConstraintError If the length of the value is strictly
+ bigger than the limit.
+ """
+ if self.limit is not None:
+ if self.limit <= len(value):
+ return
+ raise MinSizeConstraintError("Setter : trying to set value of length %d while limit is %d"%(len(value), self.limit))
return
class ColorConstraintError(Exception):
diff --git a/src/sugar/tutorius/core.py b/src/sugar/tutorius/core.py
index f290f1e..602947f 100644
--- a/src/sugar/tutorius/core.py
+++ b/src/sugar/tutorius/core.py
@@ -295,11 +295,8 @@ class FiniteStateMachine(State):
self._states = state_dict or {}
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
+ # Set the current state to None - we are not executing anything yet
+ 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
@@ -375,6 +372,13 @@ class FiniteStateMachine(State):
# state by that name - we must ignore this state change request as
# it will be done elsewhere in the hierarchy (or it's just bogus).
return
+
+ if self.current_state != None:
+ if new_state_name == self.current_state.name:
+ # If we already are in this state, we do not need to change
+ # anything in the current state - By design, a state may not point
+ # to itself
+ return
new_state = self._states[new_state_name]
diff --git a/src/sugar/tutorius/properties.py b/src/sugar/tutorius/properties.py
index 5be7e1c..a0bfa03 100644
--- a/src/sugar/tutorius/properties.py
+++ b/src/sugar/tutorius/properties.py
@@ -87,7 +87,7 @@ class TIntProperty(TutoriusProperty):
class TFloatProperty(TutoriusProperty):
"""
- Represents a floting point number. Can have an upper value limit and/or
+ Represents a floating point number. Can have an upper value limit and/or
a lower value limit.
"""
def __init__(self, value, lower_limit=None, upper_limit=None):
@@ -106,7 +106,7 @@ class TStringProperty(TutoriusProperty):
def __init__(self, value, size_limit=None):
TutoriusProperty.__init__(self)
self._type = "string"
- self.size_limit = SizeConstraint(size_limit)
+ self.size_limit = MaxSizeConstraint(size_limit)
self.set(value)
@@ -115,11 +115,11 @@ class TArrayProperty(TutoriusProperty):
Represents an array of properties. Can have a maximum number of element
limit, but there are no constraints on the content of the array.
"""
- def __init__(self, value, size_limit=None):
+ def __init__(self, value, min_size_limit=None, max_size_limit=None):
TutoriusProperty.__init__(self)
self._type = "array"
- self.size_limit = SizeConstraint(size_limit)
-
+ self.max_size_limit = MaxSizeConstraint(max_size_limit)
+ self.min_size_limit = MinSizeConstraint(min_size_limit)
self.set(value)
class TColorProperty(TutoriusProperty):
@@ -185,7 +185,7 @@ class TEnumProperty(TutoriusProperty):
class TBooleanProperty(TutoriusProperty):
"""
- Represents a True of False value.
+ Represents a True or False value.
"""
def __init__(self, value=False):
TutoriusProperty.__init__(self)
diff --git a/src/sugar/tutorius/tests/actiontests.py b/src/sugar/tutorius/tests/actiontests.py
index ab9cdba..6001ec7 100644
--- a/src/sugar/tutorius/tests/actiontests.py
+++ b/src/sugar/tutorius/tests/actiontests.py
@@ -27,24 +27,83 @@ import gtk
from sugar.tutorius.actions import *
from sugar.tutorius.services import ObjectStore
+test_props = {"prop_a":8, "prop_b":3, "prop_c":"Hi"}
+
class PropertyAction(Action):
def __init__(self, na):
- self._a = na
+ Action.__init__(self)
+ self.prop_a = TIntProperty(test_props["prop_a"])
+ self.prop_b = TIntProperty(test_props["prop_b"])
+ self.prop_c = TStringProperty(test_props["prop_c"])
+
+def has_function(obj, function_name):
+ """
+ Checks whether the object has a function by that name.
+ """
+ if hasattr(obj, function_name) and hasattr(obj.__getattribute__(function_name), "__call__"):
+ return True
+ return False
+
+class PropsTest(unittest.TestCase):
+ def test_get_properties(self):
+ act = PropertyAction(8)
+
+ assert act.get_properties() == test_props.keys(), "Action does not contain property 'a'"
+
+ for prop_name in act.get_properties():
+ assert act.properties[prop_name].value == test_props[prop_name], "Wrong initial value for property %s : %s"%(prop_name,str(act.properties[prop_name]))
+
- def set_a(self, na):
- self._a = na
+class OnceWrapperTests(unittest.TestCase):
+ def validate_wrapper(self, wrapper_instance):
+ """
+ Ensures that a given instance of a wrapper implements the Action
+ interface.
+ """
+ assert has_function(wrapper_instance, "do"),\
+ "The wrapper does not have the 'do' function"
+
+ assert has_function(wrapper_instance, "undo"),\
+ "The wrapper does not have the 'undo' function."
+
+ assert has_function(wrapper_instance, "get_properties"),\
+ "The wrapper does not have the 'get_properties' function."
+
+ def test_once_wrapper_interface(self):
+ """
+ Tests that the interface of the OnceWrapper actually conforms to the of
+ the Action.
+ """
+ prop = PropertyAction(7)
- def get_a(self):
- return self._a
+ onceWrap = OnceWrapper(prop)
+
+ self.validate_wrapper(onceWrap)
+
+ assert onceWrap.get_properties() == test_props.keys(), "OnceWrapper should give access to properties of the contained action"
- a = property(fget=get_a, fset=set_a)
+class DialogMessageTest(unittest.TestCase):
+ def setUp(self):
+ self.dial = DialogMessage("Message text", [200, 300])
-class PropsTest(unittest.TestCase):
+ def test_properties(self):
+ assert self.dial.message.value == "Message text", "Wrong start value for the message"
+
+ assert self.dial.position.value == [200, 300], "Wrong start value for the position"
- def test_get_properties(self):
- prop = PropertyAction(8)
+class BubbleMessageTest(unittest.TestCase):
+ def setUp(self):
+ self.bubble = BubbleMessage(message="Message text", pos=[200, 300], tailpos=[-15, -25])
+
+ def test_properties(self):
+ props = self.bubble.get_properties()
+
+ assert "message" in props, 'No message property of BubbleMessage'
+
+ assert "position" in props, 'No position property in BubbleMessage'
+
+ assert "tail_pos" in props, 'No tail position property in BubbleMessage'
- assert prop.get_properties() == ['a'], "Action does not contain property 'a'"
class CountAction(Action):
"""
diff --git a/src/sugar/tutorius/tests/constraintstests.py b/src/sugar/tutorius/tests/constraintstests.py
index 407cc24..b7b0a47 100644
--- a/src/sugar/tutorius/tests/constraintstests.py
+++ b/src/sugar/tutorius/tests/constraintstests.py
@@ -77,28 +77,50 @@ class LowerLimitConstraintTest(unittest.TestCase):
except LowerLimitConstraintError:
assert True, "Validation of LowerLimit(10) on 20 should not raise an exception"
-class SizeConstraintTest(unittest.TestCase):
+class MaxSizeConstraintTest(unittest.TestCase):
def test_empty_constraint(self):
- cons = SizeConstraint(None)
+ cons = MaxSizeConstraint(None)
try:
cons.validate(20)
- except SizeConstraintError:
+ except MaxSizeConstraintError:
assert False, "Empty contraint should not raise an exception"
def test_validate(self):
- cons = SizeConstraint(10)
+ cons = MaxSizeConstraint(10)
try:
cons.validate(range(0, 20))
- assert False, "Validation of SizeLimit(10) on list of length 20 should raise an exception"
- except SizeConstraintError:
+ assert False, "Validation of MaxSizeConstraint(10) on list of length 20 should raise an exception"
+ except MaxSizeConstraintError:
pass
try:
cons.validate(range(0,5))
- except SizeConstraintError:
- assert True, "Validation of SizeLimit(10) on list of length 5 should not raise an exception"
+ except MaxSizeConstraintError:
+ assert True, "Validation of MaxSizeConstraint(10) on list of length 5 should not raise an exception"
+class MinSizeConstraintTest(unittest.TestCase):
+ def test_empty_constraint(self):
+ cons = MinSizeConstraint(None)
+ try:
+ cons.validate(20)
+ except MinSizeConstraintError:
+ assert False, "Empty contraint should not raise an exception"
+
+ def test_validate(self):
+ cons = MinSizeConstraint(10)
+
+ try:
+ cons.validate(range(0, 5))
+ assert False, "Validation of MinSizeConstraint(10) on list of length 20 should raise an exception"
+ except MinSizeConstraintError:
+ pass
+
+ try:
+ cons.validate(range(0,20))
+ except MinSizeConstraintError:
+ assert True, "Validation of MinSizeConstraint(10) on list of length 5 should not raise an exception"
+
class ColorConstraintTest(unittest.TestCase):
def test_validate(self):
cons = ColorConstraint()
diff --git a/src/sugar/tutorius/tests/coretests.py b/src/sugar/tutorius/tests/coretests.py
index 5f91a64..c27846d 100644
--- a/src/sugar/tutorius/tests/coretests.py
+++ b/src/sugar/tutorius/tests/coretests.py
@@ -18,7 +18,7 @@
Core Tests
This module contains all the tests that pertain to the usage of the Tutorius
-Core. This means that the the Finite State Machine, States and all the
+Core. This means that the Event Filters, the Finite State Machine and all the
related elements and interfaces are tested here.
Usage of actions and event filters is tested, but not the concrete actions
@@ -29,7 +29,7 @@ and event filters. Those are in their separate test module
import unittest
import logging
-from sugar.tutorius.actions import Action, ClickAction, TypeTextAction
+from sugar.tutorius.actions import Action, OnceWrapper, ClickAction, TypeTextAction
from sugar.tutorius.core import *
from sugar.tutorius.filters import *
@@ -49,6 +49,14 @@ class SimpleTutorial(Tutorial):
def set_state(self, name):
self.current_state_name = name
+class TutorialWithFSM(Tutorial):
+ """
+ Fake tutorial, but associated with a FSM.
+ """
+ def __init__(self, start_name="INIT", fsm=None):
+ Tutorial.__init__(self, start_name, fsm)
+ self.activity = activity.Activity()
+
class TrueWhileActiveAction(Action):
"""
This action's active member is set to True after a do and to False after
@@ -483,7 +491,6 @@ class FSMTest(unittest.TestCase):
except Exception:
assert False, "Removing a non-existing state dit not throw the right kind of exception"
-
# Now try removing the second state
fsm.remove_state("second")
@@ -502,6 +509,33 @@ class FSMTest(unittest.TestCase):
assert "second" not in fsm.get_following_states("third"),\
"The link to second from third still exists after removal"
+ def test_set_same_state(self):
+ fsm = FiniteStateMachine("Set same state")
+
+ st1 = State("INIT")
+ st1.add_action(CountAction())
+
+ fsm.add_state(st1)
+
+ tut = SimpleTutorial()
+
+ fsm.set_tutorial(tut)
+
+ fsm.set_state("INIT")
+
+ assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \
+ "The action was not triggered on 'INIT'"
+
+ fsm.set_state("INIT")
+
+ do_count = fsm.get_state_by_name("INIT").get_action_list()[0].do_count
+ assert fsm.get_state_by_name("INIT").get_action_list()[0].do_count == 1, \
+ "The action was triggered a second time, do_count = %d"%do_count
+
+ undo_count = fsm.get_state_by_name("INIT").get_action_list()[0].undo_count
+ assert fsm.get_state_by_name("INIT").get_action_list()[0].undo_count == 0,\
+ "The action has been undone unappropriately, undo_count = %d"%undo_count
+
class FSMExplorationTests(unittest.TestCase):
def setUp(self):
self.buildFSM()
@@ -556,6 +590,7 @@ class FSMExplorationTests(unittest.TestCase):
self.validate_previous_states("Third", ("INIT", "Second"))
self.validate_previous_states("Fourth", ("Second"))
+
if __name__ == "__main__":
unittest.main()
diff --git a/src/sugar/tutorius/tests/propertiestests.py b/src/sugar/tutorius/tests/propertiestests.py
index 52a9a75..45ba264 100644
--- a/src/sugar/tutorius/tests/propertiestests.py
+++ b/src/sugar/tutorius/tests/propertiestests.py
@@ -204,7 +204,7 @@ class TStringPropertyTest(unittest.TestCase):
try:
prop.set("My string is too big!")
assert False, "String should not set to longer than max size"
- except SizeConstraintError:
+ except MaxSizeConstraintError:
pass
except:
assert False, "Wrong exception type thrown"
@@ -218,7 +218,7 @@ class TStringPropertyTest(unittest.TestCase):
try:
prop = TStringProperty("This is normal", 5)
assert False, "Creation of the property should fail."
- except SizeConstraintError:
+ except MaxSizeConstraintError:
pass
except:
assert False, "Wrong exception type on failed constructor"
@@ -236,26 +236,34 @@ class TArrayPropertyTest(unittest.TestCase):
try_wrong_values(prop)
- def test_size_limit(self):
- prop = TArrayProperty([1,2], 4)
+ def test_size_limits(self):
+ prop = TArrayProperty([1,2], None, 4)
try:
prop.set([1,2,4,5,6,7])
- assert False, "Size limit constraint was not properly applied"
- except SizeConstraintError:
+ assert False, "Maximum size limit constraint was not properly applied"
+ except MaxSizeConstraintError:
+ pass
+
+ prop = TArrayProperty([1,2,3,4], 2)
+
+ try:
+ prop.set([1])
+ assert False, "Minimum size limit constraint was not properly applied"
+ except MinSizeConstraintError:
pass
- except:
- assert False, "Wrong type of exception thrown"
-
def test_failing_constructor(self):
try:
- prop = TArrayProperty([100, 0, 20], 2)
+ prop = TArrayProperty([100, 0, 20], None, 2)
assert False, "Creation of the property should fail."
- except SizeConstraintError:
+ except MaxSizeConstraintError:
+ pass
+ try:
+ prop = TArrayProperty([100, 0, 20], 4, None)
+ assert False, "Creation of the property should fail."
+ except MinSizeConstraintError:
pass
- except:
- assert False, "Wrong exception type on failed constructor"
class TColorPropertyTest(unittest.TestCase):
def test_basic_color(self):
@@ -286,6 +294,8 @@ class TBooleanPropertyTest(unittest.TestCase):
def test_basic_boolean(self):
assert self.prop.value == False, "Could not set initial value via constructor"
+ assert self.prop.type == "boolean", "Wrong type for TBooleanProperty : %s"%self.prop.type
+
self.prop.set(True)
assert self.prop.value == True, "Could not change the value via set"
@@ -313,6 +323,8 @@ class TEnumPropertyTest(unittest.TestCase):
def test_basic_enum(self):
assert self.prop.value == "hello", "Could not set initial value on property"
+ assert self.prop.type == "enum", "Wrong type for TEnumProperty : %s"%self.prop.type
+
self.prop.set(True)
assert self.prop.value, "Could not change the value via set"
@@ -333,6 +345,8 @@ class TFilePropertyTest(unittest.TestCase):
def test_basic_file(self):
assert self.prop.value == "propertiestests.py", "Could not set initial value"
+ assert self.prop.type == "file", "Wrong type for TFileProperty : %s"%self.prop.type
+
self.prop.set("run-tests.py")
assert self.prop.value == "run-tests.py", "Could not change value"
diff --git a/src/sugar/tutorius/tests/run-tests.py b/src/sugar/tutorius/tests/run-tests.py
index 0c9219b..1fc534e 100755
--- a/src/sugar/tutorius/tests/run-tests.py
+++ b/src/sugar/tutorius/tests/run-tests.py
@@ -10,9 +10,15 @@ sys.path.insert(0,
)
FULL_PATH = os.path.join(INSTALL_PATH,"sugar/tutorius")
+SUBDIRS = ["uam"]
GLOB_PATH = os.path.join(FULL_PATH,"*.py")
import unittest
from glob import glob
+def report_files():
+ ret = glob(GLOB_PATH)
+ for dir in SUBDIRS:
+ ret += glob(os.path.join(FULL_PATH,dir,"*.py"))
+ return ret
import sys
if __name__=='__main__':
@@ -28,25 +34,42 @@ if __name__=='__main__':
import gtkutilstests
import overlaytests
import linear_creatortests
+ import actiontests
+ import uamtests
+ import filterstests
+ import constraintstests
+ import propertiestests
import serializertests
-
suite = unittest.TestSuite()
suite.addTests(unittest.findTestCases(coretests))
suite.addTests(unittest.findTestCases(servicestests))
suite.addTests(unittest.findTestCases(gtkutilstests))
suite.addTests(unittest.findTestCases(overlaytests))
suite.addTests(unittest.findTestCases(linear_creatortests))
+ suite.addTests(unittest.findTestCases(actiontests))
+ suite.addTests(unittest.findTestCases(uamtests))
+ suite.addTests(unittest.findTestCases(filterstests))
+ suite.addTests(unittest.findTestCases(constraintstests))
+ suite.addTests(unittest.findTestCases(propertiestests))
suite.addTests(unittest.findTestCases(serializertests))
runner = unittest.TextTestRunner()
runner.run(suite)
coverage.stop()
- coverage.report(glob(GLOB_PATH))
+ coverage.report(report_files())
coverage.erase()
else:
from coretests import *
from servicestests import *
from gtkutilstests import *
from overlaytests import *
+ from actiontests import *
+ from linear_creatortests import *
+ from uamtests import *
+ from filterstests import *
+ from constraintstests import *
+ from propertiestests import *
+ from actiontests import *
+ from serializertests import *
unittest.main()