From 74dcbcb643cfd66df071020320ba6c0004e92c17 Mon Sep 17 00:00:00 2001 From: mike Date: Mon, 12 Oct 2009 06:54:12 +0000 Subject: LP 448319 : Refactoring to translate function, adding tests for translation of resources --- diff --git a/tests/run-tests.py b/tests/run-tests.py index 23d7e24..9b4fa25 100755 --- a/tests/run-tests.py +++ b/tests/run-tests.py @@ -41,6 +41,7 @@ if __name__=='__main__': import propertiestests import serializertests import probetests + import translatortests suite = unittest.TestSuite() suite.addTests(unittest.findTestCases(coretests)) suite.addTests(unittest.findTestCases(servicestests)) @@ -54,6 +55,7 @@ if __name__=='__main__': suite.addTests(unittest.findTestCases(propertiestests)) suite.addTests(unittest.findTestCases(serializertests)) suite.addTests(unittest.findTestCases(probetests)) + suite.addTests(unittest.findTestCases(translatortests)) runner = unittest.TextTestRunner() runner.run(suite) coverage.stop() @@ -73,5 +75,6 @@ if __name__=='__main__': from actiontests import * from serializertests import * from probetests import * + from translatortests import * unittest.main() diff --git a/tests/translatortests.py b/tests/translatortests.py new file mode 100644 index 0000000..0f053b9 --- /dev/null +++ b/tests/translatortests.py @@ -0,0 +1,91 @@ +# Copyright (C) 2009, Tutorius.org +# +# 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 + +import unittest +import os + +from sugar.tutorius.translator import * +from sugar.tutorius.properties import * + +############################################################################## +## Helper classes +class ResourceAction(TPropContainer): + resource = TResourceProperty() + + def __init__(self): + TPropContainer.__init__(self) + +class NestedResource(TPropContainer): + nested = TAddonProperty() + + def __init__(self): + TPropContainer.__init__(self) + self.nested = ResourceAction() + +class ListResources(TPropContainer): + nested_list = TAddonListProperty() + + def __init__(self): + TPropContainer.__init__(self) + self.nested_list = [ResourceAction(), ResourceAction()] + +## +############################################################################## + +class ResourceTranslatorTests(unittest.TestCase): + temp_path = "/tmp/tutorius" + file_name = "temp.txt" + + def setUp(self): + try: + os.mkdir(self.temp_path) + except: + pass + new_file = file(os.path.join(self.temp_path, self.file_name), "w") + + # Use a dummy prob manager - we shouldn't be using it + self.prob_man = object() + + self.translator = ResourceTranslator(self.prob_man, '0101010101', testing=True) + + pass + + def tearDown(self): + os.unlink(os.path.join(self.temp_path, self.file_name)) + pass + + def test_translate(self): + # Create an action with a resource property + res_action = ResourceAction() + + self.translator.translate(res_action) + + assert getattr(res_action, "resource").type == "file", "Resource was not converted to file" + + def test_recursive_translate(self): + nested_action = NestedResource() + + self.translator.translate(nested_action) + + assert getattr(getattr(nested_action, "nested"), "resource").type == "file", "Nested resource was not converted properly" + + def test_list_translate(self): + list_action = ListResources() + + self.translator.translate(list_action) + + for container in list_action.nested_list: + assert getattr(container, "resource").type == "file", "Element of list was not converted properly" diff --git a/tutorius/translator.py b/tutorius/translator.py index 884714e..9925346 100644 --- a/tutorius/translator.py +++ b/tutorius/translator.py @@ -20,9 +20,9 @@ import copy logger = logging.getLogger("ResourceTranslator") - from sugar.tutorius.properties import * -from sugar.tutorius.vault import * +# TODO : Uncomment this line upon integration with the Vault +##from sugar.tutorius.vault import * class ResourceTranslator(object): """ @@ -30,9 +30,15 @@ class ResourceTranslator(object): properties before action execution. This class works as a decorator to the ProbeManager class, as it is meant to be a transparent layer before sending the action to execution. + + An architectural note : every different type of translation should have its + own method (translate_resource, etc...), and this function must be called + from the translate method, under the type test. The translation_* method + must take in the input property and give the output property that should + replace it. """ - def __init__(self, probe_manager, tutorial_id): + def __init__(self, probe_manager, tutorial_id, testing=False): """ Creates a new ResourceTranslator for the given tutorial. This translator is tasked with replacing resource properties of the @@ -43,11 +49,15 @@ class ResourceTranslator(object): @param probe_manager The probe manager to decorate @param tutorial_id The ID of the current tutorial + + @param testing Triggers the usage of a fake vault for testing purposes """ self._probe_manager = probe_manager self._tutorial_id = tutorial_id - def _replace_resources(self, prop_container): + self._testing = testing + + def translate_resource(self, res_prop): """ Replace the TResourceProperty in the container by their runtime-defined file equivalent. Since the resources are stored @@ -56,34 +66,81 @@ class ResourceTranslator(object): to transform the resource identifier into the absolute path for the process to be able to use it properly. + @param res_prop The resource property to be translated + @return The TFileProperty corresponding to this resource, containing + an absolute path to the resource + """ + # We need to replace the resource by a file representation + filepath = "" + # TODO : Refactor when the Vault will be available + if not self._testing: + filepath = Vault.get_resource_path(self._tutorial_id, \ + prop_object.value) + else: + filepath = "/tmp/tutorius/temp.txt" + # Create the new file representation + file_prop = TFileProperty(filepath) + + return file_prop + + def translate(self, prop_container): + """ + Applies the required translation to be able to send the container to an + executing endpoint (like a Probe). For each type of property that + requires it, there is translation function that will take care of + mapping the property to its executable form. + This function does not return anything, but its post-condition is - that all the resources properties of container have been replaced - by its corresponding file property on disk. + that all the properties of the input container have been replaced + by their corresponding executable versions. + + An example of translation is taking a resource (a relative path to + a file under the tutorial folder) and transforming it into a file + (a full absolute path) to be able to load it when the activity receives + the action. @param prop_container The property container in which we want to replace all the resources for file properties and to recursively do so for addon and addonlist properties. """ - for propname in action.get_properties(): - prop_object = getattr(action, propname) - prop_type = prop_object.type + for propname in prop_container.get_properties(): + prop_value = getattr(prop_container, propname) + prop_type = getattr(type(prop_container), propname).type + # If the property is a resource, then we need to query the + # vault to create its correspondent if prop_type == "resource": - # We need to replace the resource by a file representation - filepath = vault.get_resource_path(self._tutorial_id, \ - prop_object.value) - # Create the new file representation - file_prop = TFileProperty(filepath) - # Replace the property - setattr(action, propname, file_prop) - elif prob_type == "addon": - self._replace_resources(prop_object.value) - elif prob_type == "addonlist": - for container in prop_object.value: - self._replace_resources(container.value) + # Apply the translation + file_prop = self.translate_resource(prop_value) + # Set the property with the new value + setattr(prop_container, propname, file_prop) + + # If the property is an addon, then its value IS a + # container too - we need to translate it + elif prop_type == "addon": + # Translate the sub properties + self.translate(prop_value) + + # If the property is an addon list, then we need to translate all + # the elements of the list + elif prop_type == "addonlist": + # Now, for each sub-container in the list, we will apply the + # translation processing. This is done by popping the head of + # the list, translating it and inserting it back at the end. + for index in range(0, len(prop_value)): + # Pop the head of the list + container = prop_value[0] + del prop_value[0] + # Translate the sub-container + self.translate(container) + + # Put the value back in the list + prop_value.append(container) + # Change the list contained in the addonlist property, since + # we got a copy of the list when requesting it + setattr(prop_container, propname, prop_value) - ### ProbeManager interface for decorator ### ## Unchanged functions ## @@ -112,35 +169,25 @@ class ResourceTranslator(object): ## Decorated functions ## def install(self, action): # Make a new copy of the action that we want to install, - # because _replace_resources changes the action and we + # because translate() changes the action and we # don't want to modify the caller's action representation new_action = copy.deepcopy(action) # Execute the replacement - self._replace_resources(new_action) + self.translate(new_action) # Send the new action to the probe manager return self._probe_manager.install(new_action) def update(self, action): - # Make a new copy of the action that we want to install, - # because _replace_resources changes the action and we - # don't want to modify the caller's action representation new_action = copy.deepcopy(action) - # Execute the replacement - self._replace_resources(new_action) + self.translate(new_action) - # Send the new action to the probe manager return self._probe_manager.update(new_action) def uninstall(self, action): - # Make a new copy of the action that we want to install, - # because _replace_resources changes the action and we - # don't want to modify the caller's action representation new_action = copy.deepcopy(action) - # Execute the replacement - self._replace_resources(new_action) + self.translate(new_action) - # Send the new action to the probe manager return self._probe_manager.uninstall(new_action) def uninstall_all(self): -- cgit v0.9.1