From 2c29c0111ee94d69d038c22294dfdd1549fdbed3 Mon Sep 17 00:00:00 2001 From: mike Date: Sun, 04 Oct 2009 21:36:50 +0000 Subject: LP 439980 : Adding tests for the is_identical function in the Core, on the FSM and the State --- diff --git a/tests/coretests.py b/tests/coretests.py index f90374f..10cc716 100644 --- a/tests/coretests.py +++ b/tests/coretests.py @@ -28,6 +28,7 @@ and event filters. Those are in their separate test module import unittest +import copy import logging from sugar.tutorius.actions import * from sugar.tutorius.addon import * @@ -49,6 +50,28 @@ class SimpleTutorial(Tutorial): def set_state(self, name): self.current_state_name = name +class TutorialTest(unittest.TestCase): + """Tests the tutorial functions that are not covered elsewhere.""" + def test_detach(self): + class Activity(object): + name = "this" + + activity1 = Activity() + activity2 = Activity() + + fsm = FiniteStateMachine("Sample example") + + tutorial = Tutorial("Test tutorial", fsm) + + assert tutorial.activity == None, "There is a default activity in the tutorial" + + tutorial.attach(activity1) + + assert tutorial.activity == activity1, "Activity should have been associated to this tutorial" + + tutorial.attach(activity2) + assert tutorial.activity == activity2, "Activity should have been changed to activity2" + class TutorialWithFSM(Tutorial): """ Fake tutorial, but associated with a FSM. @@ -201,7 +224,79 @@ class StateTest(unittest.TestCase): assert len(state.get_event_filter_list()) == 0, \ "Could not clear the event filter list properly" + + def test_is_identical_simple(self): + """ + Two empty states with the same name must be identical + """ + st1 = State("Identical") + st2 = State("Identical") + + assert st1.is_identical(st2), "Empty states with the same name should be identical" + + def test_is_identical(self): + """ + Test whether two states share the same set of actions and event filters. + """ + st1 = State("Identical") + st2 = State("Identical") + + non_state = object() + + act1 = addon.create("BubbleMessage", message="Hi", position=[132,450]) + act2 = addon.create("BubbleMessage", message="Hi", position=[132,450]) + + event1 = addon.create("GtkWidgetEventFilter", "nextState", "0.0.0.1.1.2.3.1", "clicked") + + act3 = addon.create("DialogMessage", message="Hello again.", position=[200, 400]) + + # Build the first state + st1.add_action(act1) + st1.add_action(act3) + st1.add_event_filter(event1) + + # Build the second state + st2.add_action(act2) + st2.add_action(act3) + st2.add_event_filter(event1) + + # Make sure that they are identical for now + assert st1.is_identical(st2), "States should be considered as identical" + assert st2.is_identical(st1), "States should be considered as identical" + + # Modify the second bubble message action + act2.message = "New message" + + # Since one action changed in the second state, this should indicate that the states + # are not identical anymore + assert st1.is_identical(st2) == False, "Action was changed and states should be different" + assert st2.is_identical(st1) == False, "Action was changed and states should be different" + + # Make sure that trying to find identity with something else than a State object fails properly + assert st1.is_identical(non_state) == False, "Passing a non-State object should fail for identity" + + st2.name = "Not identical anymore" + assert st1.is_identical(st2) == False, "Different state names should give different states" + st2.name = "Identical" + + st3 = copy.deepcopy(st1) + st3.add_action(addon.create("BubbleMessage", "Hi!", [128,264])) + + assert st1.is_identical(st3) == False, "States having a different number of actions should be different" + + st4 = copy.deepcopy(st1) + st4.add_event_filter(addon.create("GtkWidgetEventFilter", "next_state", "0.0.1.1.2.2.3", "clicked")) + + assert st1.is_identical(st4) == False, "States having a different number of events should be different" + + st5 = copy.deepcopy(st1) + st5._event_filters = [] + st5.add_event_filter(addon.create("GtkWidgetEventFilter", "other_state", "0.1.2.3.4.1.2", "pressed")) + + #import rpdb2; rpdb2.start_embedded_debugger('pass') + assert st1.is_identical(st5) == False, "States having the same number of event filters but those being different should be different" + class FSMTest(unittest.TestCase): """ This class needs to text the interface and functionality of the Finite @@ -246,6 +341,7 @@ class FSMTest(unittest.TestCase): 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. @@ -369,6 +465,112 @@ class FSMTest(unittest.TestCase): 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 + + def test_setup(self): + fsm = FiniteStateMachine("New state machine") + + try: + fsm.setup() + assert False, "fsm should throw an exception when trying to setup and not bound to a tutorial" + except UnboundLocalError: + pass + + def test_setup_actions(self): + tut = SimpleTutorial() + + states_dict = {"INIT": State("INIT")} + fsm = FiniteStateMachine("New FSM", state_dict=states_dict) + + act = CountAction() + fsm.add_action(act) + + fsm.set_tutorial(tut) + + fsm.setup() + + # Let's also test the current state name + assert fsm.get_current_state_name() == "INIT", "Initial state should be INIT" + + assert act.do_count == 1, "Action should have been called during setup" + + fsm._fsm_has_finished = True + + fsm.teardown() + + assert act.undo_count == 1, "Action should have been undone" + + def test_string_rep(self): + fsm = FiniteStateMachine("Testing machine") + + st1 = State("INIT") + st2 = State("Other State") + st3 = State("Final State") + + st1.add_action(addon.create("BubbleMessage", "Hi!", [132,312])) + + fsm.add_state(st1) + fsm.add_state(st2) + fsm.add_state(st3) + + assert str(fsm) == "INIT, Final State, Other State, " + + def test_is_identical(self): + fsm = FiniteStateMachine("Identity test") + + non_fsm_object = object() + + assert fsm.is_identical(non_fsm_object) == False, "Testing with non FSM object should not give identity" + + # Compare FSMs + act1 = CountAction() + + fsm.add_action(act1) + + fsm2 = copy.deepcopy(fsm) + + assert fsm.is_identical(fsm2) + + act2 = CountAction() + fsm2.add_action(act2) + + assert fsm.is_identical(fsm2) == False, "FSMs having a different number of actions should be different" + + fsm3 = FiniteStateMachine("Identity test") + + act3 = addon.create("BubbleMessage", "Hi!", [123,312]) + fsm3.add_action(act3) + + assert fsm3.is_identical(fsm) == False, "Actions having the same number of actions but different ones should be different" + + st1 = State("INIT") + + st2 = State("OtherState") + + fsm.add_state(st1) + fsm.add_state(st2) + + fsm4 = copy.deepcopy(fsm) + + assert fsm.is_identical(fsm4) + + st3 = State("Last State") + + fsm4.add_state(st3) + + assert fsm.is_identical(fsm4) == False, "FSMs having a different number of states should not be identical" + + fsm4.remove_state("OtherState") + + assert fsm.is_identical(fsm4) == False, "FSMs having different states should be different" + + fsm4.remove_state("Last State") + + st5 = State("OtherState") + st5.add_action(CountAction()) + + fsm4.add_state(st5) + + assert fsm.is_identical(fsm4) == False, "FSMs having states with same name but different content should be different" class FSMExplorationTests(unittest.TestCase): def setUp(self): @@ -425,6 +627,34 @@ class FSMExplorationTests(unittest.TestCase): self.validate_previous_states("Fourth", ("Second")) + def test_is_identical(self): + otherFSM = copy.deepcopy(self.fsm) + + assert self.fsm.is_identical(otherFSM), "Copied FSM was different" + + # Change the name of the second FSM + otherFSM.name = "OtherName" + + assert self.fsm.is_identical(otherFSM) == False, "Name change should make the FSMs different" + + otherFSM.name = self.fsm.name + + # Add an extra state to the second FSM + new_state = State("New State!") + + act1 = addon.create("BubbleMessage", message="This will make the second FSM different", position=[100, 0]) + new_state.add_action(act1) + + otherFSM.add_state(new_state) + + assert self.fsm.is_identical(otherFSM) == False, "The second FSM has an extra state and should not be identical" + + otherFSM.remove_state("New State!") + + # Test difference with one FSM having an FSM-level action + otherFSM.add_action(act1) + + assert self.fsm.is_identical(otherFSM) == False, "The second FSM has an FSM-level action and should be different" if __name__ == "__main__": unittest.main() diff --git a/tests/propertiestests.py b/tests/propertiestests.py index e1f6f4b..389671d 100644 --- a/tests/propertiestests.py +++ b/tests/propertiestests.py @@ -83,7 +83,129 @@ class BasePropertyTest(unittest.TestCase): obj.prop = 2 assert obj.prop == 2, "Unable to set a value on base class" + + def test_is_identical(self): + class klass(TPropContainer): + prop = TutoriusProperty() + obj = klass() + + obj2 = klass() + + assert obj.is_identical(obj2), "Base property containers should be identical" +class AdvancedPropertyTest(unittest.TestCase): + def test_properties_groups(self): + """ + Tests complex properties containers for identity. + """ + + class klass1(TPropContainer): + message = TutoriusProperty() + property = TutoriusProperty() + data = TutoriusProperty() + + class klass2(TPropContainer): + property = TutoriusProperty() + message = TutoriusProperty() + data = TutoriusProperty() + + class klass3(TPropContainer): + property = TutoriusProperty() + message = TutoriusProperty() + data = TutoriusProperty() + extra_prop = TutoriusProperty() + + class klass4(TPropContainer): + property = TutoriusProperty() + message = TutoriusProperty() + data = TFloatProperty(13.0) + + obj1 = klass1() + obj1.property = 12 + obj1.message = "Initial message" + obj1.data = [132, 208, 193, 142] + + obj2 = klass2() + obj2.property = 12 + obj2.message = "Initial message" + obj2.data = [132, 208, 193, 142] + + obj3 = klass3() + obj3.property = 12 + obj3.message = "Initial message" + obj3.data = [132, 208, 193, 142] + obj3.extra_prop = "Suprprise!" + + obj4 = klass4() + obj4.property = 12 + obj4.message = "Initial message" + obj4.data = 13.4 + + # Ensure that both obj1 and obj2 are identical (they have the same list of + # properties and they have the same values + assert obj1.is_identical(obj2), "Identical objects were considered as different" + + # Ensure that obj1 is different from obj3, since obj3 has an extra property + assert obj1.is_identical(obj3) == False, "Objects should not be identical since obj3 has more props" + assert obj3.is_identical(obj1) == False, "Objects should not be identical since obj3 has more properties" + + # Ensure that properties of different type are considered as different + assert obj1.is_identical(obj4) == False, "Properties of different type should not be equal" + + def test_addon_properties(self): + class klass1(TPropContainer): + addon = TAddonProperty() + + class inner1(TPropContainer): + internal = TutoriusProperty() + def __init__(self, value): + TPropContainer.__init__(self) + self.internal = value + + obj1 = klass1() + obj1.addon = inner1("Hi!") + + obj2 = klass1() + obj2.addon = inner1("Hi!") + + assert obj1.is_identical(obj2), "Identical objects with addon proeprties were treated as different" + + obj3 = klass1() + obj3.addon = inner1("Hello!") + + assert obj1.is_identical(obj3) == False, "Objects with addon property having a different value should be considered different" + + def test_addonlist_properties(self): + class klass1(TPropContainer): + addon_list = TAddonListProperty() + + class inner1(TPropContainer): + message = TutoriusProperty() + data = TutoriusProperty() + def __init__(self, message, data): + TPropContainer.__init__(self) + self.message = message + self.data = data + + class inner2(TPropContainer): + message = TutoriusProperty() + other_data = TutoriusProperty() + def __init__(self, message, data): + TPropContainer.__init__(self) + self.message = message + self.other_data = data + + obj1 = klass1() + obj1.addon_list = [inner1('Hi!', 12), inner1('Hello.', [1,2])] + obj2 = klass1() + obj2.addon_list = [inner1('Hi!', 12), inner1('Hello.', [1,2])] + + assert obj1.is_identical(obj2), "Addon lists with the same containers were considered different" + + obj3 = klass1() + obj3.addon_list = [inner1('Hi!', 12), inner2('Hello.', [1,2])] + assert obj1.is_identical(obj3) == False, "Differently named proeprties should be considered different in the addon list tests" + class TIntPropertyTest(unittest.TestCase): def test_int_property(self): class klass(TPropContainer): @@ -412,6 +534,45 @@ class TFilePropertyTest(unittest.TestCase): except FileConstraintError: pass +class TAddonPropertyTest(unittest.TestCase): + def test_wrong_value(self): + class klass1(TPropContainer): + addon = TAddonProperty() + + class wrongAddon(object): + pass + + obj1 = klass1() + obj1.addon = klass1() + + try: + obj1.addon = wrongAddon() + assert False, "Addon Property should not accept non-TPropContainer values" + except ValueError: + pass + +class TAddonPropertyList(unittest.TestCase): + def test_wrong_value(self): + class klass1(TPropContainer): + addonlist = TAddonListProperty() + + class wrongAddon(object): + pass + + obj1 = klass1() + + obj1.addonlist = [klass1(), klass1()] + + try: + obj1.addonlist = klass1() + assert False, "TAddonPropeprty shouldn't accept anything else than a list" + except ValueError: + pass + + try: + obj1.addonlist = [klass1(), klass1(), wrongAddon(), klass1()] + except ValueError: + pass if __name__ == "__main__": unittest.main() diff --git a/tutorius/core.py b/tutorius/core.py index ff592ad..d034c30 100644 --- a/tutorius/core.py +++ b/tutorius/core.py @@ -90,16 +90,16 @@ class Tutorial (object): self.state_machine.set_state(name) - # Currently unused -- equivalent function is in each state - def _eventfilter_state_done(self, eventfilter): - """ - Callback handler for eventfilter to notify - when we must go to the next state. - """ - #XXX Tests should be run here normally - - #Swith to the next state pointed by the eventfilter - self.set_state(eventfilter.get_next_state()) +## # Currently unused -- equivalent function is in each state +## def _eventfilter_state_done(self, eventfilter): +## """ +## Callback handler for eventfilter to notify +## when we must go to the next state. +## """ +## #XXX Tests should be run here normally +## +## #Swith to the next state pointed by the eventfilter +## self.set_state(eventfilter.get_next_state()) def _prepare_activity(self): """ @@ -141,9 +141,6 @@ class State(object): self._actions = action_list or [] - # Unused for now - #self.tests = [] - self._event_filters = event_filter_list or [] self.tutorial = tutorial @@ -402,7 +399,7 @@ class FiniteStateMachine(State): # Flag the FSM level setup as done self._fsm_setup_done = True # Execute all the FSM level actions - for action in self.actions: + for action in self._actions: action.do() # Then, we need to run the setup of the current state @@ -467,7 +464,7 @@ class FiniteStateMachine(State): # Flag the FSM teardown as not needed anymore self._fsm_teardown_done = True # Undo all the FSM level actions here - for action in self.actions: + for action in self._actions: action.undo() # TODO : It might be nice to have a start() and stop() method for the diff --git a/tutorius/properties.py b/tutorius/properties.py index 7bfbad0..2afe119 100644 --- a/tutorius/properties.py +++ b/tutorius/properties.py @@ -101,13 +101,13 @@ class TPropContainer(object): the every property of the first one can be found in the other container, with the same name and the same value. - This is an approximation of identity because we are really looking to see - if this container is at least a subset of the other. - @param otherContainer The other container that we wish to test for equality. @returns True if every property in the first container can be found with the same value and the same name in the second container. """ + # Make sure both have the same number of properties + if len(self._props) != len(otherContainer._props): + return False # For every property in this container for prop in self._props.keys(): found = False @@ -224,19 +224,6 @@ class TAddonListProperty(TutoriusProperty): """ pass - - def get_constraints(self): - """ - Returns the list of constraints associated to this property. - """ - if self._constraints is None: - self._constraints = [] - for i in dir(self): - typ = getattr(self, i) - if isinstance(typ, Constraint): - self._constraints.append(i) - return self._constraints - class TIntProperty(TutoriusProperty): """ Represents an integer. Can have an upper value limit and/or a lower value -- cgit v0.9.1