From fb49b482f5bd478bada50cd1ab25a876806eff31 Mon Sep 17 00:00:00 2001 From: mike Date: Tue, 05 May 2009 05:30:41 +0000 Subject: [LP 352437] Core : Minor refactoring on XMLSerializer, added tests --- (limited to 'src') diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py index d2516f7..ff7f427 100644 --- a/src/sugar/tutorius/actions.py +++ b/src/sugar/tutorius/actions.py @@ -131,7 +131,7 @@ class BubbleMessage(Action): @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): + def __init__(self, message, pos=None, speaker=None, tailpos=None): Action.__init__(self) self.message = TStringProperty(message) # Create the position as an array of fixed-size 2 diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py index 34b3a12..f9a3911 100644 --- a/src/sugar/tutorius/bundler.py +++ b/src/sugar/tutorius/bundler.py @@ -27,9 +27,8 @@ import xml.dom.minidom from sugar.tutorius import gtkutils, overlayer from sugar.tutorius.core import Tutorial, State, FiniteStateMachine +from sugar.tutorius.filters import * from sugar.tutorius.actions import * -import sugar.tutorius.filters -#from sugar.tutorius.filters import EventFilter, TimerEvent, GtkWidgetEventFilter, GtkWidgetTypeFilter from ConfigParser import SafeConfigParser def _get_store_root(): @@ -132,47 +131,73 @@ class XMLSerializer(Serializer): eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc)) return statesList + def _create_action_node(self, action, doc): + """ + Takes a single action and transforms it into a xml node. + + @param action A single action + @param doc The XML document root (used to create nodes only + @return A XML Node object with the Action tag name + """ + actionNode = doc.createElement("Action") + + # Write down just the name of the Action class as the Class + # property -- + # Using .__class__ since type() doesn't have the same behavior + # with class derivating from object and class that don't + actionNode.setAttribute("Class", str(action.__class__)) + + if type(action) is DialogMessage: + actionNode.setAttribute("Message", action.message.value) + actionNode.setAttribute("PositionX", str(action.position.value[0])) + actionNode.setAttribute("PositionY", str(action.position.value[1])) + elif type(action) is BubbleMessage: + actionNode.setAttribute("Message", action.message.value) + actionNode.setAttribute("PositionX", str(action.position.value[0])) + actionNode.setAttribute("PositionY", str(action.position.value[1])) + actionNode.setAttribute("Tail_posX", str(action.tail_pos.value[0])) + actionNode.setAttribute("Tail_posY", str(action.tail_pos.value[1])) + # TO ADD : elif for each type of action + elif type(action) is WidgetIdentifyAction: + # Nothing else to save + pass + elif type(action) is ChainAction: + # Recusively write the contained actions - in the correct order + subActionsNode = self._create_action_list_node(action._actions, doc) + actionNode.appendChild(subActionsNode) + elif type(action) is DisableWidgetAction: + # Remember the target + actionNode.setAttribute("Target", action._target) + elif type(action) is TypeTextAction: + # Save the text and the widget + actionNode.setAttribute("Widget", action._widget) + actionNode.setAttribute("Text", action._text) + elif type(action) is ClickAction: + # Save the widget to click + actionNode.setAttribute("Widget", action._widget) + elif type(action) is OnceWrapper: + # Encapsulate the action in a OnceWrapper + subActionNode = self._create_action_node(action._action, doc) + actionNode.appendChild(subActionNode) + + return actionNode + def _create_action_list_node(self, action_list, doc): """ Create and return a xml Node from a Action list. + + @param action_list A list of actions + @param doc The XML document root (used to create new nodes only) + @return A XML Node object with the Actions tag name and a serie of + Action children """ actionsList = doc.createElement("Actions") for action in action_list: - actionNode = doc.createElement("Action") + # Create the action node + actionNode = self._create_action_node(action, doc) + # Append it to the list actionsList.appendChild(actionNode) - # Using .__class__ since type() doesn't have the same behavior - # with class derivating from object and class that don't - actionNode.setAttribute("Class", str(action.__class__)) - - if type(action) is DialogMessage: - actionNode.setAttribute("Message", action.message.value) - actionNode.setAttribute("PositionX", action.position.value[0]) - actionNode.setAttribute("PositionY", str(action.position.value[1])) - elif type(action) is BubbleMessage: - actionNode.setAttribute("Message", action.message.value) - actionNode.setAttribute("PositionX", str(action.position.value[0])) - actionNode.setAttribute("PositionY", str(action.position.value[1])) - actionNode.setAttribute("Tail_posX", str(action.tail_pos.value[0])) - actionNode.setAttribute("Tail_posY", str(action.tail_pos.value[1])) - # TODO : elif for each type of action - elif type(action) is WidgetIdentifyAction: - # Nothing else to save - pass - elif type(action) is ChainAction: - # Recusively write the contained actions - in the correct order - subActionsNode = self._create_actions_node(action._actions, doc) - actionNode.appendChild(subActionsNode) - elif type(action) is DisableWidgetAction: - # Remember the target - actionNode.setAttribute("Target", action._target) - elif type(action) is TypeTextAction: - # Save the text and the widget - actionNode.setAttribute("Widget", action._widget) - actionNode.setAttribute("Text", action._text) - elif type(action) is ClickAction: - # Save the widget to click - actionNode.setAttribute("Widget", action._widget) - + return actionsList def _create_event_filters_node(self, event_filters, doc): @@ -181,23 +206,26 @@ class XMLSerializer(Serializer): """ eventFiltersList = doc.createElement("EventFiltersList") for event_f in event_filters: - eventFilterNode = eventFiltersList.appendChild("EventFilter") + eventFilterNode = doc.createElement("EventFilter") + eventFiltersList.appendChild(eventFilterNode) - # Using .__class__ since type() doesn't have the same behavior + # Write down just the name of the Action class as the Class + # property -- + # using .__class__ since type() doesn't have the same behavior # with class derivating from object and class that don't eventFilterNode.setAttribute("Class", str(event_f.__class__)) # Write the name of the next state eventFilterNode.setAttribute("NextState", event_f.next_state) - if type(event_f) is sugar.tutorius.filters.TimerEvent: + if type(event_f) is TimerEvent: eventFilterNode.setAttribute("Timeout_s", str(event_f._timeout)) - elif type(event_f) is sugar.tutorius.filters.GtkWidgetEventFilter: + elif type(event_f) is GtkWidgetEventFilter: eventFilterNode.setAttribute("EventName", event_f._event_name) eventFilterNode.setAttribute("ObjectId", event_f._object_id) - elif type(event_f) is sugar.tutorius.filters.GtkWidgetTypeFilter: + elif type(event_f) is GtkWidgetTypeFilter: eventFilterNode.setAttribute("ObjectId", event_f._object_id) if event_f._strokes is not None: eventFilterNode.setAttribute("Strokes", event_f._strokes) @@ -295,26 +323,24 @@ class XMLSerializer(Serializer): @param filters_elem An XML Element representing a list of event filters """ reformed_event_filters_list = [] - # item(0) because there is always only one tag in the xml file - # so states_elem should always contain only one element - event_filter_element_list = filters_elem.item(0).getElementsByTagName("EventFilter") + event_filter_element_list = filters_elem.getElementsByTagName("EventFilter") new_event_filter = None for event_filter in event_filter_element_list: # Load the name of the next state for this filter next_state = event_filter.getAttribute("NextState") - if event_filter.getAttribute("Class") == str(sugar.tutorius.filters.TimerEvent): + if event_filter.getAttribute("Class") == str(TimerEvent): timeout = int(event_filter.getAttribute("Timeout_s")) new_event_filter = TimerEvent(next_state, timeout) - elif event_filter.getAttribute("Class") == str(sugar.tutorius.filters.GtkWidgetEventFilter): + elif event_filter.getAttribute("Class") == str(GtkWidgetEventFilter): # Get the event name and the object's ID event_name = event_filter.getAttribute("EventName") object_id = event_filter.getAttribute("ObjectId") new_event_filter = GtkWidgetEventFilter(next_state, object_id, event_name) - elif event_filter.getAttribute("Class") == str(sugar.tutorius.filters.GtkWidgetTypeFilter): + elif event_filter.getAttribute("Class") == str(GtkWidgetTypeFilter): # Get the widget to write in and the text object_id = event_filter.getAttribute("ObjectId") if event_filter.hasAttribute("Text"): @@ -325,10 +351,57 @@ class XMLSerializer(Serializer): new_event_filter = GtkWidgetTypeFilter(next_state, object_id, strokes=strokes) if new_event_filter is not None: - reformed_event_filter.append(new_event_filter) + reformed_event_filters_list.append(new_event_filter) return reformed_event_filters_list + def _load_xml_action(self, action): + """ + Loads a single action from an Xml Action node. + + @param action The Action XML Node to transform + object + @return The Action object of the correct type according to the XML + description + """ + # TO ADD: an elif for each type of action + if action.getAttribute("Class") == str(DialogMessage): + message = action.getAttribute("Message") + positionX = int(action.getAttribute("PositionX")) + positionY = int(action.getAttribute("PositionY")) + position = [positionX, positionY] + return DialogMessage(message,position) + elif action.getAttribute("Class") == str(BubbleMessage): + message = action.getAttribute("Message") + positionX = int(action.getAttribute("PositionX")) + positionY = int(action.getAttribute("PositionY")) + position = [positionX, positionY] + tail_posX = action.getAttribute("Tail_posX") + tail_posY = action.getAttribute("Tail_posY") + tail_pos = [tail_posX, tail_posY] + return BubbleMessage(message,position,None,tail_pos) + elif action.getAttribute("Class") == str(WidgetIdentifyAction): + return WidgetIdentifyAction() + elif action.getAttribute("Class") == str(ChainAction): + # Load the subactions + subActionsList = self._load_xml_actions(action.getElementsByTagName("Actions")[0]) + return ChainAction(subActionsList) + elif action.getAttribute("Class") == str(DisableWidgetAction): + # Get the target + targetName = action.getAttribute("Target") + return DisableWidgetAction(targetName) + elif action.getAttribute("Class") == str(TypeTextAction): + # Get the widget and the text to type + widget = action.getAttribute("Widget") + text = action.getAttribute("Text") + + return TypeTextAction(widget, text) + elif action.getAttribute("Class") == str(ClickAction): + # Load the widget to click + widget = action.getAttribute("Widget") + + return ClickAction(widget) + def _load_xml_actions(self, actions_elem): """ Transforms an Actions element into a list of instanciated Action. @@ -336,48 +409,12 @@ class XMLSerializer(Serializer): @param actions_elem An XML Element representing a list of Actions """ reformed_actions_list = [] - # item(0) because there is always only one tag in the xml file - # so states_elem should always contain only one element - actions_element_list = actions_elem.item(0).getElementsByTagName("Action") + actions_element_list = actions_elem.getElementsByTagName("Action") for action in actions_element_list: - # TODO : elif for each type of action - if action.getAttribute("Class") == str(DialogMessage): - message = action.getAttribute("Message") - positionX = int(action.getAttribute("PositionX")) - positionY = int(action.getAttribute("PositionY")) - position = [positionX, positionY] - reformed_actions_list.append(DialogMessage(message,position)) - elif action.getAttribute("Class") == str(BubbleMessage): - message = action.getAttribute("Message") - positionX = int(action.getAttribute("PositionX")) - positionY = int(action.getAttribute("PositionY")) - position = [positionX, positionY] - tail_posX = action.getAttribute("Tail_posX") - tail_posY = action.getAttribute("Tail_posY") - tail_pos = [tail_posX, tail_posY] - reformed_actions_list.append(BubbleMessage(message,position,None,tail_pos)) - elif action.getAttribute("Class") == str(WidgetIdentifyAction): - reformed_actions_list.append(WidgetIdentifyAction()) - elif action.getAttribute("Class") == str(ChainAction): - # Load the subactions - subActionsList = _load_xml_actions(action.getElementsByTagName("Actions")) - reformed_actions_list.append(ChainAction(subActionsList)) - elif action.getAttribute("Class") == str(DisableWidgetAction): - # Get the target - targetName = action.getAttribute("Target") - reformed_actions_list.append(DisableWidgetAction(targetName)) - elif action.getAttribute("Class") == str(TypeTextAction): - # Get the widget and the text to type - widget = action.getAttribute("Widget") - text = action.getAttribute("Text") - - reformed_actions_list.append(TypeTextAction(widget, text)) - elif action.getAttribute("Class") == str(ClickAction): - # Load the widget to click - widget = action.getAttribute("Widget") - - reformed_actions_list.append(ClickAction(widget)) + new_action = self._load_xml_action(action) + + reformed_actions_list.append(new_action) return reformed_actions_list @@ -395,8 +432,10 @@ class XMLSerializer(Serializer): for state in states_element_list: stateName = state.getAttribute("Name") - actions_list = self._load_xml_actions(state.getElementsByTagName("Actions")) - event_filters_list = self._load_xml_event_filters(state.getElementsByTagName("EventFiltersList")) + # Using item 0 in the list because there is always only one + # Actions and EventFilterList element per State node. + actions_list = self._load_xml_actions(state.getElementsByTagName("Actions")[0]) + event_filters_list = self._load_xml_event_filters(state.getElementsByTagName("EventFiltersList")[0]) reformed_state_list.append(State(stateName, actions_list, event_filters_list)) return reformed_state_list @@ -425,12 +464,12 @@ class XMLSerializer(Serializer): fsm.add_state(state) # Load the actions on this FSM - actions = self._load_xml_actions(fsm_elem.getElementsByTagName("FSMActions")) + actions = self._load_xml_actions(fsm_elem.getElementsByTagName("FSMActions")[0]) for action in actions: fsm.add_action(action) # Load the event filters - events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFiltersList")) + events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFiltersList")[0]) for event in events: fsm.add_event_filter(event) diff --git a/src/sugar/tutorius/tests/serializertests.py b/src/sugar/tutorius/tests/serializertests.py index 2a743e1..bc29601 100644 --- a/src/sugar/tutorius/tests/serializertests.py +++ b/src/sugar/tutorius/tests/serializertests.py @@ -32,10 +32,10 @@ import shutil from sugar.tutorius import gtkutils, overlayer from sugar.tutorius.core import Tutorial, State, FiniteStateMachine -from sugar.tutorius.actions import DialogMessage, OnceWrapper, BubbleMessage -from sugar.tutorius.filters import GtkWidgetEventFilter, TimerEvent -from sugar.tutorius.bundler import * - +from sugar.tutorius.actions import * +from sugar.tutorius.filters import * +from sugar.tutorius.bundler import XMLSerializer, Serializer +import sugar from uuid import * import rpdb2 @@ -56,7 +56,7 @@ class SerializerInterfaceTest(unittest.TestCase): ser = Serializer() try: - ser.load_fsm(str(uuid.uuid1)) + ser.load_fsm(str(uuid.uuid1())) assert False, "load_fsm() should throw an unimplemented error" except: pass @@ -66,6 +66,12 @@ class XMLSerializerTest(unittest.TestCase): Tests the transformation of XML to FSM, then back. """ def setUp(self): + # Make the serializer believe the test is in a activity path + self.testpath = "/tmp/testdata/" + os.environ["SUGAR_BUNDLE_PATH"] = self.testpath + os.environ["SUGAR_PREFIX"] = self.testpath +## os.mkdir(sugar.tutorius.bundler._get_store_root()) + # Create the sample FSM self.fsm = FiniteStateMachine("testingMachine") @@ -76,6 +82,7 @@ class XMLSerializerTest(unittest.TestCase): st1 = State("INIT") st1.add_action(act1) + st1.add_event_filter(ev1) st2 = State("Second") @@ -86,33 +93,34 @@ class XMLSerializerTest(unittest.TestCase): self.uuid = uuid1() + # Flag to set to True if the output can be deleted after execution of + # the test + self.remove = True + + def tearDown(self): + """ + Removes the created files, if need be. + """ + if self.remove == True: + os.remove(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml") + if os.path.isdir(self.testpath): + shutil.rmtree(self.testpath) - def test_save(self, remove=True): + def test_save(self): """ Writes an FSM to disk, then compares the file to the expected results. "Remove" boolean argument specify if the test data must be removed or not - """ - # Make the serializer believe the test is in a activity path - testpath = "/tmp/testdata/" - os.environ["SUGAR_BUNDLE_PATH"] = testpath - os.environ["SUGAR_PREFIX"] = testpath -## os.mkdir(sugar.tutorius.bundler._get_store_root()) + """ xml_ser = XMLSerializer() os.makedirs(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid))) #rpdb2.start_embedded_debugger('flakyPass') xml_ser.save_fsm(self.fsm, "fsm.xml", os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid))) - - #Remove test file and path - if remove == True: - os.remove(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml") - if os.path.isdir(testpath): - shutil.rmtree(testpath) - + def test_save_and_load(self): """ Load up the written FSM and compare it with the object representation. """ - self.test_save(False) + self.test_save() testpath = "/tmp/testdata/" #rpdb2.start_embedded_debugger('flakyPass') xml_ser = XMLSerializer() @@ -130,90 +138,59 @@ class XMLSerializerTest(unittest.TestCase): self.fsm._states.get("INIT").get_action_list()[0].message.value, \ 'FSM underlying State underlying Action differ from original to reformed one' assert len(loaded_fsm.get_action_list()) == 0, "FSM should not have any actions on itself" - - os.remove(os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml") - if os.path.isdir(testpath): - shutil.rmtree(testpath) - - -# Helper classes to help testing -class SerializerTest(unittest.TestCase): - """ - This class has to test the Serializer methods as well as the expected - functionality. - """ + def test_all_actions(self): + """ + Inserts all the known action types in a FSM, then attempt to load it. + """ + st = State("INIT") - # Voiding test as it is meant to be used with the pickle serializer, - # that was deprecated -## def test_pickle_integrity(self): -## """ -## Validates content is uncorrupted trough a pickle file save/load. -## """ -## -## # Sample valid FSM dict -## sampleDict = { -## "INIT":State("INIT", -## action_list=[ -## OnceWrapper(BubbleMessage(message="Welcome to the text editor tutorial!\n\n Click on the canvas and type a letter.", pos=[100,100], tailpos=[-10,-20])), -## ], -## event_filter_list=[ -## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"), -## TimerEvent("LOST",15), -## ], -## ), -## "LOST":State("LOST", -## action_list=[BubbleMessage("Click in the canvas and type on your keyboard", [400, 400]),], -## event_filter_list=[ -## GtkWidgetEventFilter("TEXT","0.0.0.1.0.0.0","key-press-event"), -## TimerEvent("INIT",5), -## ], -## ), -## "TEXT":State("TEXT", -## action_list=[OnceWrapper(BubbleMessage(" You can type more letters if you want!\n\n" + -## "To proceed to the next step, select your text.\n\n Click and drag over the text!", [200,150])),], -## event_filter_list=[ -## GtkWidgetEventFilter("SELECTED","0.0.0.1.0.0","text-selected"), -## ], -## ), -## } -## -## testpath = "/tmp/testdata/" -## -## # Create testdata/ folder if no exists -## if not os.path.exists(testpath): -## os.mkdir(testpath) -## -## serialize = TutoSerializer() -## -## # Make the class believe the test is in a activity path -## os.environ["SUGAR_ACTIVITY_ROOT"] = testpath -## -## fsm = FiniteStateMachine("Test", state_dict=sampleDict) -## -## serialize.save_tutorial("Test", "Test", fsm, "serializeTest") -## -## fileDict = serialize.load_tuto_list() -## -## for filekey, tutorial in fileDict.items(): -## if filekey == "Test": -## reformedTuto = serialize.build_tutorial(filekey) -## -## reformedfsm = reformedTuto.get("Test").state_machine -## -## #Tests -## assert reformedfsm._states.get("INIT").name == fsm._states.get("INIT").name, \ -## 'FSM underlying dictionary differ from original to pickled/reformed one' -## assert reformedfsm._states.get("LOST").name == fsm._states.get("LOST").name, \ -## 'FSM underlying dictionary differ from original to pickled/reformed one' -## assert reformedfsm._states.get("TEXT").name == fsm._states.get("TEXT").name, \ -## 'FSM underlying dictionary differ from original to pickled/reformed one' -## -## -## os.remove(testpath + "serializeTest.tml") -## os.rmdir(testpath) -## os.rmdir("/tmp") - - + act1 = BubbleMessage("Hi!", pos=[10,120], tailpos=[-12,30]) + act2 = DialogMessage("Hello again.", pos=[120,10]) + act3 = WidgetIdentifyAction() + act4 = DisableWidgetAction("0.0.0.1.0.0.0") + act5 = TypeTextAction("0.0.0.1.1.1.0.0", "New text") + act6 = ClickAction("0.0.1.0.1.1") + act7 = OnceWrapper(act1) + act8 = ChainAction([act1, act2, act3, act4]) + actions = [act1, act2, act3, act4, act5, act6, act7, act8] + + for action in actions: + st.add_action(action) + + self.fsm.remove_state("Second") + self.fsm.remove_state("INIT") + self.fsm.add_state(st) + + xml_ser = XMLSerializer() + + self.test_save() + + reloaded_fsm = xml_ser.load_fsm(str(self.uuid)) + + def test_all_filters(self): + """ + Inserts all the known action types in a FSM, then attempt to load it. + """ + st = State("INIT") + + ev1 = TimerEvent("Second", 1000) + ev2 = GtkWidgetEventFilter("Second", "0.0.1.1.0.0.1", "clicked") + ev3 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", text="Typed stuff") + ev4 = GtkWidgetTypeFilter("Second", "0.0.1.1.1.2.3", strokes="acbd") + filters = [ev1, ev2, ev3, ev4] + + for filter in filters: + st.add_event_filter(filter) + + self.fsm.remove_state("INIT") + self.fsm.add_state(st) + + xml_ser = XMLSerializer() + + self.test_save() + + reloaded_fsm = xml_ser.load_fsm(str(self.uuid)) + if __name__ == "__main__": unittest.main() -- cgit v0.9.1