From b94b444aaab2680bcdc6b85c0a00ed929dd58ef7 Mon Sep 17 00:00:00 2001 From: Jean-Christophe Savard Date: Sun, 19 Apr 2009 21:23:42 +0000 Subject: Work on bundler.py and serializertests.py, tests OK --- (limited to 'src') diff --git a/src/sugar/tutorius/actions.py b/src/sugar/tutorius/actions.py index ad91fb4..2dca6c7 100644 --- a/src/sugar/tutorius/actions.py +++ b/src/sugar/tutorius/actions.py @@ -157,7 +157,7 @@ class BubbleMessage(Action): def set_pos(self, x, y): self._position = [x, y] def get_pos(self): - return self.position + 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): diff --git a/src/sugar/tutorius/bundler.py b/src/sugar/tutorius/bundler.py index 6f61779..09babc6 100644 --- a/src/sugar/tutorius/bundler.py +++ b/src/sugar/tutorius/bundler.py @@ -24,17 +24,17 @@ import logging import os import uuid import xml.dom.minidom -import xml.dom.ext +#import xml.dom.ext from sugar.tutorius import gtkutils, overlayer from sugar.tutorius.core import Tutorial, State, FiniteStateMachine -import sugar.tutorius.actions +from sugar.tutorius.actions import * import sugar.tutorius.filters from ConfigParser import SafeConfigParser -def __get_store_root(): +def _get_store_root(): return os.path.join(os.getenv("SUGAR_PREFIX"),"share","tutorius","data") -def __get_bundle_root(): +def _get_bundle_root(): return os.path.join(os.getenv("SUGAR_BUNDLE_PATH"),"data","tutorius","data") INI_ACTIVITY_SECTION = "RELATED_ACTIVITIES" @@ -51,8 +51,8 @@ class TutorialStore: given activity. """ - store_root = __get_store_root() - bundle_root = __get_bundle_root() + store_root = _get_store_root() + bundle_root = _get_bundle_root() logging.debug("*********** Path of store_root : " + store_root) @@ -119,54 +119,56 @@ class XMLSerializer(Serializer): used in the tutorials to/from a .xml file. Inherit from Serializer """ - def create_state_dict_node(self, state_dict, doc): + def _create_state_dict_node(self, state_dict, doc): """ Create and return a xml Node from a State dictionnary. """ statesList = doc.createElement("States") for state_name, state in state_dict.items(): - stateNode = statesList.appendChild("State") - stateNode.setAttribute("State:Name", state_name) - stateNode = stateNode.appendChild(create_action_list_node(state.action_list, doc)) - stateNode = stateNode.appendChild(create_event_filters_node(state.event_filters, doc)) + stateNode = doc.createElement("State") + statesList.appendChild(stateNode) + stateNode.setAttribute("Name", state_name) + actionsList = stateNode.appendChild(self._create_action_list_node(state.get_action_list(), doc)) + eventfiltersList = stateNode.appendChild(self._create_event_filters_node(state.get_event_filter_list(), doc)) return statesList - def create_action_list_node(self, action_list, doc): + def _create_action_list_node(self, action_list, doc): """ Create and return a xml Node from a Action list. """ actionsList = doc.createElement("Actions") for action in action_list: - actionNode = actionsList.appendChild("Action") + actionNode = doc.createElement("Action") + actionsList.appendChild(actionNode) if type(action) is DialogMessage: - actionNode.setAttribute("Action:Class", type(action)) - actionNode.setAttribute("Action:Message", action.message) - actionNode.setAttribute("Action:Position", action.position) + actionNode.setAttribute("Class", type(action)) + actionNode.setAttribute("Message", action.message) + actionNode.setAttribute("Position", action.position) elif type(action) is BubbleMessage: - actionNode.setAttribute("Action:Class", str(type(action))) - actionNode.setAttribute("Action:Message", action.message) - actionNode.setAttribute("Action:Position", action.position) - actionNode.setAttribute("Action:Tail_pos", action.tail_pos) + actionNode.setAttribute("Class", str(type(action))) + actionNode.setAttribute("Message", action.message) + actionNode.setAttribute("Position", str(action.position)) + actionNode.setAttribute("Tail_pos", str(action.tail_pos)) # TODO : elif for each type of action elif type(action) is WidgetIdentifyAction: - actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Class", str(type(action))) # TODO elif type(action) is ChainAction: # TODO - actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Class", str(type(action))) elif type(action) is DisableWidgetAction: # TODO - actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Class", str(type(action))) elif type(action) is TypeTextAction: # TODO - actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Class", str(type(action))) elif type(action) is ClickAction: # TODO - actionNode.setAttribute("Action:Class", str(type(action))) + actionNode.setAttribute("Class", str(type(action))) return actionsList - def create_event_filters_node(self, event_filters, doc): + def _create_event_filters_node(self, event_filters, doc): """ Create and return a xml Node from a event filters. """ @@ -176,41 +178,176 @@ class XMLSerializer(Serializer): # TODO : elif for each type of event filters if type(event_f) is TimerEvent: # TODO - eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + eventFilterNode.setAttribute("Class", str(type(event_f))) elif type(event_f) is GtkWidgetEventFilter: # TODO - eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + eventFilterNode.setAttribute("Class", str(type(event_f))) elif type(event_f) is GtkWidgetTypeFilter: # TODO - eventFilterNode.setAttribute("EventFilter:Class", str(type(event_f))) + eventFilterNode.setAttribute("Class", str(type(event_f))) return eventFiltersList def save_fsm(self, fsm, xml_filename, path): """ Save fsm to disk, in the xml file specified by "xml_filename", in the - "path" folder. If the specified file dont exist, it will be created. + "path" folder. If the specified file doesn't exist, it will be created. """ doc = xml.dom.minidom.Document() fsm_element = doc.createElement("FSM") doc.appendChild(fsm_element) - fsm_element.setAttribute("fsm:Name", fsm.name) - fsm_element.setAttribute("fsm:StartStateName", fsm.start_state_name) - fsm_element = fsm_element.appendChild(create_state_dict_node(fsm.state_dict, doc)) - fsm_element = fsm_element.appendChild(create_action_list_node(fsm.action_list, doc)) + fsm_element.setAttribute("Name", fsm.name) + fsm_element.setAttribute("StartStateName", fsm.start_state_name) + statesDict = fsm_element.appendChild(self._create_state_dict_node(fsm._states, doc)) + actionsList = fsm_element.appendChild(self._create_action_list_node(fsm.actions, doc)) - file_object = open(path + "/" + xml_filename, "w") - xml.dom.ext.PrettyPrint(doc, file_object) + file_object = open(os.path.join(path, xml_filename), "w") + # Commented for DEBUG purposes + #xml.dom.ext.PrettyPrint(doc, file_object) + file_object.write(doc.toprettyxml()) file_object.close() + def _find_tutorial_dir_with_guid(self, guid): + """ + Finds the tutorial with the associated GUID. If it is found, return + the path to the tutorial's directory. If it doesn't exist, raise an + IOError. + + A note : if there are two tutorials with this GUID in the folders, + they will both be inspected and the one with the highest version + number will be returned. If they have the same version number, the one + from the global store will be returned. + + @param guid The GUID of the tutorial that is to be loaded. + """ + # Attempt to find the tutorial's directory in the global directory + global_dir = os.path.join(_get_store_root(), guid) + # Then in the activty's bundle path + activity_dir = os.path.join(_get_bundle_root(), guid) + + # If they both exist + if os.path.isdir(global_dir) and os.path.isdir(activity_dir): + # Inspect both metadata files + global_meta = os.path.join(global_dir, "meta.ini") + activity_meta = os.path.join(activity_dir, "meta.ini") + + # Open both config files + global_parser = SafeConfigParser() + global_parser.read(global_meta) + + activity_parser = SafeConfigParser() + activity_parser.read(activity_meta) + + # Get the version number for each tutorial + global_version = global_parser.get(INI_METADATA_SECTION, "version") + activity_version = activity_parser.get(INI_METADATA_SECTION, "version") + + # If the global version is higher or equal, we'll take it + if global_version >= activity_version: + return global_dir + else: + return activity_dir + + # Do we just have the global directory? + if os.path.isdir(global_dir): + return global_dir + + # Or just the activity's bundle directory? + if os.path.isdir(activity_dir): + return activity_dir + + # Error : none of these directories contain the tutorial + raise IOError(2, "Neither the global nor the bundle directory contained the tutorial with GUID %s"%guid) + + def _load_xml_properties(self, properties_elem): + """ + Changes a list of properties into fully instanciated properties. + + @param properties_elem An XML element reprensenting a list of + properties + """ + return [] + + def _load_xml_event_filters(self, filters_elem): + """ + Loads up a list of Event Filters. + + @param filters_elem An XML Element representing a list of event filters + """ + return [] + + def _load_xml_actions(self, actions_elem): + """ + Transforms an Actions element into a list of instanciated Action. + + @param actions_elem An XML Element representing a list of Actions + """ + return [] + + def _load_xml_states(self, states_elem): + """ + Takes in a States element and fleshes out a complete list of State + objects. + + @param states_elem An XML Element that represents a list of States + """ + return [] + + def _load_xml_fsm(self, fsm_elem): + """ + Takes in an XML element representing an FSM and returns the fully + crafted FSM. + + @param fsm_elem The XML element that describes a FSM + """ + # Load the FSM's name and start state's name + fsm_name = fsm_elem.getAttribute("Name") + + fsm_start_state_name = None + try: + fsm_start_state_name = fsm_elem.getAttribute("StartStateName") + except: + pass + + fsm = FiniteStateMachine(fsm_name, start_state_name=fsm_start_state_name) + + # Load the states + states = self._load_xml_states(fsm_elem.getElementsByTagName("States")) + for state in states: + fsm.add_state(state) + + # Load the actions on this FSM + actions = self._load_xml_actions(fsm_elem.getElementsByTagName("Actions")) + for action in actions: + fsm.add_action(action) + + # Load the event filters + events = self._load_xml_event_filters(fsm_elem.getElementsByTagName("EventFiltersList")) + for event in events: + fsm.add_event_filter(event) + + return fsm + + def load_fsm(self, guid): """ - Load fsm from xml file who .ini file guid match argument guid. + Load fsm from xml file whose .ini file guid match argument guid. """ + # Fetch the directory (if any) + tutorial_dir = self._find_tutorial_dir_with_guid(guid) + + # Open the XML file + tutorial_file = os.path.join(tutorial_dir, "fsm.xml") + + xml_dom = xml.dom.minidom.parse(tutorial_file) + fsm_elem = xml_dom.getElementsByTagName("FSM")[0] + return self._load_xml_fsm(fsm_elem) + + class TutorialBundler: """ This class provide the various data handling methods useable by the tutorial @@ -230,12 +367,12 @@ class TutorialBundler: #Look for the file in the path if a uid is supplied if generated_guid: #General store - store_path = os.path.join(__get_store_root(), generated_guid, INI_FILENAME) + store_path = os.path.join(_get_store_root(), generated_guid, INI_FILENAME) if os.path.isfile(store_path): self.Path = os.path.dirname(store_path) else: #Bundle store - bundle_path = os.path.join(__get_bundle_root(), generated_guid, INI_FILENAME) + bundle_path = os.path.join(_get_bundle_root(), generated_guid, INI_FILENAME) if os.path.isfile(bundle_path): self.Path = os.path.dirname(bundle_path) else: @@ -243,7 +380,7 @@ class TutorialBundler: else: #Create the folder, any failure will go through to the caller for now - store_path = os.path.join(__get_store_root(), generated_guid) + store_path = os.path.join(_get_store_root(), generated_guid) os.mkdir(store_path) self.Path = store_path @@ -292,8 +429,8 @@ class TutorialBundler: more than one path, the store_root is given priority. """ - store_root = __get_store_root() - bundle_root = __get_bundle_root() + store_root = _get_store_root() + bundle_root = _get_bundle_root() config = SafeConfigParser() path = None @@ -351,10 +488,11 @@ class TutorialBundler: config = SafeConfigParser() if guid is not None: + serializer = XMLSerializer() path = get_tutorial_path() + "/meta.ini" config.read(path) xml_filename = config.get(INI_METADATA_SECTION, INI_XML_FSM_PROPERTY) - save_fsm(fsm, xml_filename, store_root) + serializer.save_fsm(fsm, xml_filename, store_root) def add_resources(self, typename, file): diff --git a/src/sugar/tutorius/tests/serializertests.py b/src/sugar/tutorius/tests/serializertests.py index 7a31602..c2aeda1 100644 --- a/src/sugar/tutorius/tests/serializertests.py +++ b/src/sugar/tutorius/tests/serializertests.py @@ -34,87 +34,147 @@ 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.tutoserialize import TutoSerializer +from sugar.tutorius.bundler import * +from uuid import * +import rpdb2 -# Helper classes to help testing - - - - -class SerializerTest(unittest.TestCase): +class XMLSerializerTest(unittest.TestCase): """ - This class has to test the Serializer methods as well as the expected - functionality. + Tests the transformation of XML to FSM, then back. """ + def setUp(self): + # Create the sample FSM + self.fsm = FiniteStateMachine("testingMachine") - def test_pickle_integrity(self): - """ - Validates content is uncorrupted trough a pickle file save/load. - """ + # Add a few states + act1 = BubbleMessage(message="Hi", pos=[300, 450]) + ev1 = GtkWidgetEventFilter("0.12.31.2.2", "clicked", "Second") + act2 = BubbleMessage(message="Second message", pos=[250, 150], tailpos=[1,2]) - # 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/" + st1 = State("INIT") + st1.add_action(act1) - # Create testdata/ folder if no exists - if not os.path.exists(testpath): - os.mkdir(testpath) + st2 = State("Second") - serialize = TutoSerializer() + st2.add_action(act2) - # Make the class believe the test is in a activity path - os.environ["SUGAR_ACTIVITY_ROOT"] = testpath + self.fsm.add_state(st1) + self.fsm.add_state(st2) - fsm = FiniteStateMachine("Test", state_dict=sampleDict) + self.uuid = uuid1() + + def test_save(self): + """ + Writes an FSM to disk, then compares the file to the expected results. + """ + # Make the class 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))) - serialize.save_tutorial("Test", "Test", fsm, "serializeTest") + # Compare the two files - fileDict = serialize.load_tuto_list() + #Remove test file and path +## os.remove(testpath + os.path.join(sugar.tutorius.bundler._get_store_root(), str(self.uuid)) + "/fsm.xml") +## os.removedirs(testpath) + + def test_save_and_load(self): + """ + Load up the written FSM and compare it with the object representation. + """ + self.test_save() - for filekey, tutorial in fileDict.items(): - if filekey == "Test": - reformedTuto = serialize.build_tutorial(filekey) - - reformedfsm = reformedTuto.get("Test").state_machine + #rpdb2.start_embedded_debugger('flakyPass') + xml_ser = XMLSerializer() - #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' + # This interface needs to be redone... It's not clean because there is + # a responsibility mixup between the XML reader and the bundler. + loaded_fsm = xml_ser.load_fsm(str(self.uuid)) + # Compare the two FSMs + +# Helper classes to help testing +class SerializerTest(unittest.TestCase): + """ + This class has to test the Serializer methods as well as the expected + functionality. + """ - os.remove(testpath + "serializeTest.tml") - os.rmdir(testpath) - os.rmdir("/tmp") + # 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") if __name__ == "__main__": -- cgit v0.9.1