Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tests/coretests.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/coretests.py')
-rw-r--r--tests/coretests.py597
1 files changed, 597 insertions, 0 deletions
diff --git a/tests/coretests.py b/tests/coretests.py
new file mode 100644
index 0000000..eadea01
--- /dev/null
+++ b/tests/coretests.py
@@ -0,0 +1,597 @@
+# Copyright (C) 2009, Tutorius.org
+# Copyright (C) 2009, Michael Janelle-Montcalm <michael.jmontcalm@gmail.com>
+#
+# 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
+"""
+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.
+
+Usage of actions and event filters is tested, but not the concrete actions
+and event filters. Those are in their separate test module
+
+"""
+
+import unittest
+
+import logging
+from sugar.tutorius.actions import Action, OnceWrapper, ClickAction, TypeTextAction
+from sugar.tutorius.core import *
+from sugar.tutorius.filters import *
+
+
+from actiontests import CountAction
+
+# Helper classes to help testing
+class SimpleTutorial(Tutorial):
+ """
+ Fake tutorial
+ """
+ def __init__(self, start_name="INIT"):
+ #Tutorial.__init__(self, "Simple Tutorial", None)
+ self.current_state_name = start_name
+ self.activity = "TODO : This should be an activity"
+
+ 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
+ an undo.
+
+ Used to verify that a State correctly triggers the do and undo actions.
+ """
+ def __init__(self):
+ Action.__init__(self)
+ self.active = False
+
+ def do(self):
+ self.active = True
+
+ def undo(self):
+ self.active = False
+
+class ClickableWidget():
+ """
+ This class fakes a widget with a clicked() method
+ """
+ def __init__(self):
+ self.click_count = 0
+
+ def clicked(self):
+ self.click_count += 1
+
+class FakeTextEntry():
+ """
+ This class fakes a widget with an insert_text() method
+ """
+ def __init__(self):
+ self.text_lines = []
+ self.last_entered_line = ""
+ self.displayed_text = ""
+
+ def insert_text(self, text, index):
+ self.last_entered_line = text
+ self.text_lines.append(text)
+ self.displayed_text = self.displayed_text[0:index] + text + self.displayed_text[index+1:]
+
+class FakeParentWidget():
+ """
+ This class fakes a widet container, it implements the get_children() method
+ """
+ def __init__(self):
+ self._children = []
+
+ def add_child(self, child):
+ self._children.append(child)
+
+ def get_children(self):
+ return self._children
+
+
+
+
+class TriggerEventFilter(EventFilter):
+ """
+ This event filter can be triggered by simply calling its do_callback function.
+
+ Used to fake events and see the effect on the FSM.
+ """
+ def __init__(self, next_state):
+ EventFilter.__init__(self, next_state)
+ self.toggle_on_callback = False
+
+ def install_handlers(self, callback, **kwargs):
+ """
+ Forsakes the incoming callback function and just set the inner one.
+ """
+ self._callback = self._inner_cb
+
+ def _inner_cb(self, event_filter):
+ self.toggle_on_callback = not self.toggle_on_callback
+
+class FakeEventFilter(TriggerEventFilter):
+ """
+ This is a fake event that is connected to the tutorial.
+
+ 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() after creating the object.
+ """
+ def set_tutorial(self, tutorial):
+ self.tutorial = tutorial
+
+ def _inner_cb(self, event_filter):
+ self.toggle_on_callback = not self.toggle_on_callback
+ self.tutorial.set_state(event_filter.get_next_state())
+
+
+class ClickActionTests(unittest.TestCase):
+ """
+ Test class for click action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = ClickAction("0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.click_count == 1, "clicked() should have been called by do()"
+
+ action.do()
+
+ assert widget.click_count == 2, "clicked() should have been called by do()"
+
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = ClickableWidget()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ action = ClickAction("0.0")
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
+
+
+
+class TypeTextActionTests(unittest.TestCase):
+ """
+ Test class for type text action
+ """
+ def test_do_action(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = TypeTextAction("0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+
+ action.do()
+
+ assert widget.last_entered_line == test_text, "insert_text() should have been called by do()"
+ assert len(widget.text_lines) == 2, "insert_text() should have been called twice"
+
+ def test_undo(self):
+ activity = FakeParentWidget()
+ widget = FakeTextEntry()
+ activity.add_child(widget)
+ ObjectStore().activity = activity
+
+ test_text = "This is text"
+
+
+ action = TypeTextAction("0.0", test_text)
+
+ assert widget == ObjectStore().activity.get_children()[0],\
+ "The clickable widget isn't reachable from the object store \
+ the test cannot pass"
+
+ action.undo()
+
+ #There is no undo for this action so the test should not fail
+ assert True
+
+# State testing class
+class StateTest(unittest.TestCase):
+ """
+ This class has to test the State interface as well as the expected
+ functionality.
+ """
+
+ def test_action_toggle(self):
+ """
+ Validate that the actions are properly done on setup and undone on
+ teardown.
+
+ Pretty awesome.
+ """
+ act = TrueWhileActiveAction()
+
+ state = State("action_test", action_list=[act])
+
+ assert act.active == False, "Action is not initialized properly"
+
+ state.setup()
+
+ assert act.active == True, "Action was not triggered properly"
+
+ state.teardown()
+
+ assert act.active == False, "Action was not undone properly"
+
+ def test_event_filter(self):
+ """
+ Tests the fact that the event filters are correctly installed on setup
+ and uninstalled on teardown.
+ """
+ event_filter = TriggerEventFilter("second_state")
+
+ state = State("event_test", event_filter_list=[event_filter])
+ state.set_tutorial(SimpleTutorial())
+
+ assert event_filter.toggle_on_callback == False, "Wrong init of event_filter"
+ assert event_filter._callback == None, "Event filter has a registered callback before installing handlers"
+
+ state.setup()
+
+ assert event_filter._callback != None, "Event filter did not register callback!"
+
+ # 'Trigger' the event - This is more like a EventFilter test.
+ event_filter.do_callback()
+
+ assert event_filter.toggle_on_callback == True, "Event filter did not execute callback"
+
+ state.teardown()
+
+ assert event_filter._callback == None, "Event filter did not remove callback properly"
+
+ def test_warning_set_tutorial_twice(self):
+ """
+ Calls set_tutorial twice and expects a warning on the second.
+ """
+ state = State("start_state")
+ tut = SimpleTutorial("First")
+ tut2 = SimpleTutorial("Second")
+
+ state.set_tutorial(tut)
+
+ try:
+ state.set_tutorial(tut2)
+ assert False, "No RuntimeWarning was raised on second set_tutorial"
+ except :
+ pass
+
+ def test_add_action(self):
+ """
+ Tests on manipulating the actions inside a state.
+ """
+ state = State("INIT")
+
+ act1 = CountAction()
+ act2 = CountAction()
+ act3 = CountAction()
+
+ # Try to add the actions
+ assert state.add_action(act1), "Could not add the first action"
+ assert state.add_action(act2), "Could not add the second action"
+ assert state.add_action(act3), "Could not add the third action"
+
+ # Try to add a second time an action that was already inserted
+ assert state.add_action(act1) == False, "Not supposed to insert an action twice"
+
+ # Fetch the associated actions
+ actions = state.get_action_list()
+
+ # Make sure all the actions are present in the state
+ assert act1 in actions and act2 in actions and act3 in actions,\
+ "The actions were not properly inserted in the state"
+
+ # Clear the list
+ state.clear_actions()
+
+ # Make sure the list of actions is empty now
+ assert len(state.get_action_list()) == 0, "Clearing of actions failed"
+
+ def test_add_event_filter(self):
+ state = State("INIT")
+
+ event1 = TriggerEventFilter("s")
+ event2 = TriggerEventFilter("t")
+ event3 = TriggerEventFilter("r")
+
+ # Insert the event filters
+ assert state.add_event_filter(event1), "Could not add event filter 1"
+ assert state.add_event_filter(event2), "Could not add event filter 2"
+ assert state.add_event_filter(event3), "Could not add event filter 3"
+
+ # Make sure we cannot insert an event twice
+ assert state.add_event_filter(event1) == False, "Could add twice the event filter"
+
+ # Get the list of event filters
+ event_filters = state.get_event_filter_list()
+
+ assert event1 in event_filters and event2 in event_filters and event3 in event_filters, \
+ "The event filters were not all added inside the state"
+
+ # Clear the list
+ state.clear_event_filters()
+
+ assert len(state.get_event_filter_list()) == 0, \
+ "Could not clear the event filter list properly"
+
+class FSMTest(unittest.TestCase):
+ """
+ This class needs to text the interface and functionality of the Finite
+ State Machine.
+ """
+
+ def test_sample_usage(self):
+ act_init = TrueWhileActiveAction()
+ act_second = TrueWhileActiveAction()
+
+ event_init = FakeEventFilter("SECOND")
+
+ content = {
+ "INIT": State("INIT", action_list=[act_init],event_filter_list=[event_init]),
+ "SECOND": State("SECOND", action_list=[act_second])
+ }
+
+ fsm = FiniteStateMachine("SampleUsage", state_dict=content)
+
+ assert fsm is not None, "Unable to create FSM"
+
+ tut = Tutorial("SampleUsageTutorial", fsm)
+
+ tut.attach(None)
+ event_init.set_tutorial(tut)
+
+ assert fsm.current_state.name == "INIT", "Unable to set state to initial state"
+
+ assert act_init.active, "FSM did not call the state's action DO properly"
+
+ # Trigger the event of the INIT state
+ event_init.do_callback()
+
+ assert act_init.active == False, "FSM did not teardown INIT properly"
+
+ assert fsm.current_state.name == "SECOND", "FSM did not switch to SECOND state"
+
+ assert act_second.active == True, "FSM did not setup SECOND properly"
+
+ tut.detach()
+
+ assert act_second.active == False, "FSM did not teardown SECOND properly"
+
+
+ def test_state_insert(self):
+ """
+ This is a simple test to insert, then find a state.
+ """
+ st1 = State("FakeState")
+
+ fsm = FiniteStateMachine("StateInsertTest")
+
+ fsm.add_state(st1)
+
+ inserted_state = fsm.get_state_by_name(st1.name)
+
+ assert inserted_state is st1, "Inserting, then fetching a state did not work"
+
+ # Make sure we cannot insert it twice
+ try :
+ fsm.add_state(st1)
+ assert False, "No error raised on addition of an already present state"
+ except KeyError:
+ pass
+
+ def test_state_find_by_name(self):
+ """
+ Tests the interface for fetching a state by name.
+ - Basic functionnality
+ - Non-existent state
+ """
+
+ st1 = State("INIT")
+
+ st2 = State("second")
+
+ fsm = FiniteStateMachine("StateFindTest")
+
+ fsm.add_state(st1)
+ fsm.add_state(st2)
+
+ # Test the fetch by name
+ fetched_st1 = fsm.get_state_by_name(st1.name)
+
+ assert fetched_st1 is st1, "Fetched state is not the same as the inserted one"
+
+ fetched_st2 = fsm.get_state_by_name(st2.name)
+
+ assert fetched_st2 is st2, "Fetched state is not the same as the inserted one"
+
+ try:
+ fsm.get_state_by_name("no such state")
+ assert False, "Did not get a KeyError on non-existing key search"
+ except KeyError:
+ pass
+ except Exception:
+ assert False, "Did not get the right error on non-existing key search"
+
+ def test_state_removal(self):
+ """
+ This test removes a state from the FSM. It also verifies that the links
+ from other states going into the removed state are gone.
+ """
+ st1 = State("INIT", event_filter_list=[TriggerEventFilter("second")])
+ st2 = State("second", event_filter_list=[TriggerEventFilter("third")])
+ st3 = State("third", event_filter_list=[TriggerEventFilter("second")])
+
+ fsm = FiniteStateMachine("StateRemovalTest")
+
+ fsm.add_state(st1)
+ fsm.add_state(st2)
+ fsm.add_state(st3)
+
+ # First tests - Removing a non-existing state and make sure we get a
+ # KeyError
+ try:
+ fsm.remove_state("Non-existing")
+ assert False, "Removing a non-existing state did not throw a KeyError"
+ except KeyError:
+ pass
+ 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")
+
+ # Make sure it cannot be fetched
+ try :
+ fetched_state = fsm.get_state_by_name("second")
+ assert False, "The supposedly removed state is still present in the FSM"
+ except KeyError:
+ pass
+
+ # Make sure that there is no link to the removed state in the rest
+ # of the FSM
+ assert "second" not in fsm.get_following_states("INIT"),\
+ "The link to second from INIT still exists after removal"
+
+ 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()
+
+ 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.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.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()