* Style cleanup * Updated comments/docstrings * Renamed class XMLBase -> XMLTreeFile for clarity * Merged XMLTreeFile.readablefile() into __init__() * Made XMLTreeFile.sourcebackupfile content uniform * Made XMLTreeFile.sourcefilename content uniform * Updated & enhanced unittests
Signed-off-by: Chris Evich <[email protected]> --- client/shared/xml_utils.py | 86 ++++++++++++++++++++++++----------- client/shared/xml_utils_unittest.py | 38 +++++++++++++-- 2 files changed, 92 insertions(+), 32 deletions(-) diff --git a/client/shared/xml_utils.py b/client/shared/xml_utils.py index 6f90aa4..4d5c977 100644 --- a/client/shared/xml_utils.py +++ b/client/shared/xml_utils.py @@ -20,7 +20,7 @@ TMPSFX='.xml' class TempXMLFile(file): """ - Temporary XML file removed on instance deletion / unexceptional module exit. + Temporary XML file auto-removed on instance del / module exit. """ def __init__(self, suffix=TMPSFX, prefix=TMPPFX, mode="wb+", buffer=1): @@ -36,77 +36,99 @@ class TempXMLFile(file): os.close(fd) super(TempXMLFile, self).__init__(path, mode, buffer) + def __exit__(self, exc_type, exc_value, traceback): """ Always remove temporary file on module exit. """ + self.__del__() super(TempXMLFile, self).__exit__(exc_type, exc_value, traceback) + def __del__(self): """ Remove temporary file on instance delete. """ + try: os.unlink(self.name) except OSError: pass # don't care + class XMLBackup(TempXMLFile): - """Temporary XML backuap, removed on unexceptional destruction.""" + """ + Backup file copy of XML data, automatically removed on instance destruction. + """ + # Allow users to reference original source of XML data sourcefilename = None def __init__(self, sourcefilename): """ Initialize a temporary backup from sourcefilename. """ + super(XMLBackup, self).__init__() self.sourcefilename = sourcefilename self.backup() + def backup(self): """ Overwrite temporary backup with contents of original source. """ + self.flush() self.seek(0) shutil.copyfileobj(file(self.sourcefilename, "rb"), super(XMLBackup,self)) self.seek(0) + def restore(self): """ Overwrite original source with contents of temporary backup """ + self.flush() self.seek(0) shutil.copyfileobj(super(XMLBackup,self), file(self.sourcefilename, "wb+")) self.seek(0) + def _info(self): logging.info("Retaining backup of %s in %s", self.sourcefilename, self.name) + def __exit__(self, exc_type, exc_value, traceback): """ Remove temporary backup on unexceptional module exit. """ + if exc_type is None and exc_value is None and traceback is None: super(XMLBackup, self).__del__() else: self._info() + def __del__(self): """ Remove temporary file on instance delete. """ + self._info() -class XMLBase(ElementTree.ElementTree, XMLBackup): - """ElementTree backed by a file copy of source""" - # Automaticaly remove temp file instance destruction - tempsource = None +class XMLTreeFile(ElementTree.ElementTree, XMLBackup): + """ + Combination of ElementTree root and auto-cleaned XML backup file. + """ + + # Closed file object of original source or TempXMLFile + # self.sourcefilename inherited from parent + sourcebackupfile = None def __init__(self, xml): """ @@ -114,57 +136,60 @@ class XMLBase(ElementTree.ElementTree, XMLBackup): param: xml: A filename or string containing XML """ + # xml param could be xml string or readable filename - if not self.readablefile(xml): - self.tempsource = TempXMLFile() - self.tempsource.write(xml) + # If it's a string, use auto-delete TempXMLFile + # to hold the original content. + try: + self.sourcebackupfile = open(xml, "rb") # Prevent source modification - self.tempsource.close() - xml = self.tempsource.name - # xml guaranteed to be a filename + self.sourcebackupfile.close() + except (IOError, OSError): + # this will auto-delete when XMLBase instance goes out of scope + self.sourcebackupfile = TempXMLFile() + self.sourcebackupfile.write(xml) + # Prevent source modification + self.sourcebackupfile.close() + xml = self.sourcebackupfile.name XMLBackup.__init__(self, sourcefilename=xml) - ElementTree.ElementTree.__init__(self, element=None, file=xml) + ElementTree.ElementTree.__init__(self, element=None, + file=self.name) # Ensure parsed content matches file content self.write() self.flush() - @classmethod - def readablefile(cls, filename): - """ - Returns True/False if filename exists and is readable - """ - try: - test = file(filename, "rb") - test.close() - return True - except (OSError, IOError): - return False def write(self, filename=None, encoding="UTF-8"): """ Write current XML tree to filename, or self.name if None. """ + if filename is None: filename = self.name ElementTree.ElementTree.write(self, filename, encoding) + def read(self, xml): self.__del__() self.__init__(xml) + class Sub(object): """String substituter using string.Template""" def __init__(self, **mapping): """Initialize substitution mapping.""" + self._mapping = mapping + def substitute(self, text): """ Use string.safe_substitute on text and return the result @param: text: string to substitute """ + return string.Template(text).safe_substitute(**self._mapping) @@ -179,14 +204,16 @@ class TemplateXMLTreeBuilder(ElementTree.XMLTreeBuilder, Sub): @param: **mapping: values to be substituted for ${key} in XML input """ + Sub.__init__(self, **mapping) ElementTree.XMLTreeBuilder.__init__(self, target=self.BuilderClass()) + def feed(self, data): ElementTree.XMLTreeBuilder.feed(self, self.substitute(data)) -class TemplateXML(XMLBase): +class TemplateXML(XMLTreeFile): """Template-sourced XML ElementTree backed by temporary file.""" ParserClass = TemplateXMLTreeBuilder @@ -198,11 +225,13 @@ class TemplateXML(XMLBase): @param: xml: A filename or string containing XML @param: **mapping: keys/values to feed with XML to string.template """ + self.parser = self.ParserClass(**mapping) # ElementTree.init calls self.parse() super(TemplateXML, self).__init__(xml) # XMLBase.__init__ calls self.write() after super init + def parse(self, source): """ Parse source XML file or filename using TemplateXMLTreeBuilder @@ -210,11 +239,14 @@ class TemplateXML(XMLBase): @param: source: XML file or filename @param: parser: ignored """ - return super(XMLBase, self).parse(source, self.parser) + + return super(TemplateXML, self).parse(source, self.parser) + def restore(self): """ Raise an IOError to protect the original template source. """ - raise(IOError, "Protecting template source, disallowing restore to %s" % + + raise IOError("Protecting template source, disallowing restore to %s" % self.sourcefilename) diff --git a/client/shared/xml_utils_unittest.py b/client/shared/xml_utils_unittest.py index 7c79e43..58a03d5 100755 --- a/client/shared/xml_utils_unittest.py +++ b/client/shared/xml_utils_unittest.py @@ -43,12 +43,14 @@ class xml_test_data(unittest.TestCase): os.close(fd) self.canonicalize_test_xml() + def tearDown(self): for filename in glob.glob(os.path.join('/tmp', "%s*%s" % (xml_utils.TMPPFX, xml_utils.TMPSFX) )): os.unlink(filename) + def canonicalize_test_xml(self): et = ElementTree.parse(self.XMLFILE) et.write(self.XMLFILE, encoding="UTF-8") @@ -70,6 +72,7 @@ class test_TempXMLFile(xml_test_data): self.assert_(filename.startswith(xml_utils.TMPPFX)) self.assert_(filename.endswith(xml_utils.TMPSFX)) + def test_test_TempXMLFile_canread(self): tmpf = xml_utils.TempXMLFile() tmpf.write(self.XMLSTR) @@ -78,6 +81,7 @@ class test_TempXMLFile(xml_test_data): self.assertEqual(stuff, self.XMLSTR) del tmpf + def test_TempXMLFile_implicit(self): def out_of_scope_tempxmlfile(): tmpf = xml_utils.TempXMLFile() @@ -103,14 +107,17 @@ class test_XMLBackup(xml_test_data): s = f.read() return s == self.XMLSTR + def test_backup_filename(self): xmlbackup = self.class_to_test(self.XMLFILE) self.assertEqual(xmlbackup.sourcefilename, self.XMLFILE) + def test_backup_file(self): xmlbackup = self.class_to_test(self.XMLFILE) self.assertTrue(self.is_same_contents(xmlbackup.name)) + def test_rebackup_file(self): xmlbackup = self.class_to_test(self.XMLFILE) oops = file(xmlbackup.name, "wb") @@ -120,6 +127,7 @@ class test_XMLBackup(xml_test_data): xmlbackup.backup() self.assertTrue(self.is_same_contents(xmlbackup.name)) + def test_restore_file(self): xmlbackup = self.class_to_test(self.XMLFILE) # nuke source @@ -127,6 +135,7 @@ class test_XMLBackup(xml_test_data): xmlbackup.restore() self.assertTrue(self.is_same_contents(xmlbackup.name)) + def test_remove_backup_file(self): xmlbackup = self.class_to_test(self.XMLFILE) filename = xmlbackup.name @@ -134,6 +143,7 @@ class test_XMLBackup(xml_test_data): del xmlbackup self.assertRaises(OSError, os.unlink, filename) + def test_TempXMLBackup_implicit(self): def out_of_scope_xmlbackup(): tmpf = self.class_to_test(self.XMLFILE) @@ -143,6 +153,7 @@ class test_XMLBackup(xml_test_data): self.assertTrue(self.is_same_contents(filename)) os.unlink(filename) + def test_TempXMLBackup_exception_exit(self): tmpf = self.class_to_test(self.XMLFILE) filename = tmpf.name @@ -151,6 +162,7 @@ class test_XMLBackup(xml_test_data): self.assertTrue(self.is_same_contents(filename)) os.unlink(filename) + def test_TempXMLBackup_unexception_exit(self): tmpf = self.class_to_test(self.XMLFILE) filename = tmpf.name @@ -159,23 +171,30 @@ class test_XMLBackup(xml_test_data): self.assertRaises(OSError, os.unlink, filename) -class test_XMLBase(test_XMLBackup): +class test_XMLTreeFile(test_XMLBackup): - class_to_test = xml_utils.XMLBase + class_to_test = xml_utils.XMLTreeFile def test_init_str(self): xml = self.class_to_test(self.XMLSTR) - self.assert_(xml.tempsource is not None) + self.assert_(xml.sourcefilename is not None) + self.assertEqual(xml.sourcebackupfile.name, + xml.sourcefilename) + def test_init_xml(self): xml = self.class_to_test(self.XMLFILE) - self.assert_(xml.tempsource is None) + self.assert_(xml.sourcefilename is not None) + self.assertEqual(xml.sourcebackupfile.name, + xml.sourcefilename) + def test_restore_from_string(self): xmlbackup = self.class_to_test(self.XMLSTR) os.unlink(xmlbackup.sourcefilename) xmlbackup.restore() - self.assertTrue(self.is_same_contents(xmlbackup.tempsource.name)) + self.assertTrue(self.is_same_contents(xmlbackup.sourcefilename)) + def test_restore_from_file(self): xmlbackup = self.class_to_test(self.XMLFILE) @@ -183,6 +202,7 @@ class test_XMLBase(test_XMLBackup): xmlbackup.restore() self.assertTrue(self.is_same_contents(xmlbackup.name)) + def test_write_default(self): xmlbackup = self.class_to_test(self.XMLFILE) wordsize = xmlbackup.find('guest/arch/wordsize') @@ -192,6 +212,7 @@ class test_XMLBase(test_XMLBackup): xmlbackup.write() self.assertFalse(self.is_same_contents(xmlbackup.name)) + def test_write_other(self): xmlbackup = self.class_to_test(self.XMLFILE) otherfile = xml_utils.TempXMLFile() @@ -199,6 +220,7 @@ class test_XMLBase(test_XMLBackup): otherfile.close() self.assertTrue(self.is_same_contents(otherfile.name)) + def test_write_other_changed(self): xmlbackup = self.class_to_test(self.XMLSTR) otherfile = xml_utils.TempXMLFile() @@ -211,6 +233,7 @@ class test_XMLBase(test_XMLBackup): self.canonicalize_test_xml() self.assertTrue(self.is_same_contents(otherfile.name)) + def test_read_other_changed(self): xmlbackup = self.class_to_test(self.XMLSTR) wordsize = xmlbackup.find('guest/arch/wordsize') @@ -236,10 +259,12 @@ class test_templatized_xml(xml_test_data): self.RESULTCHECK = """<bar baz="foo">foobarbaz</bar>""" super(test_templatized_xml, self).setUp() + def test_sub(self): sub = xml_utils.Sub(**self.MAPPING) self.assertEqual(sub.substitute(self.FULLREPLACE), self.RESULTCHECK) + def test_MappingTreeBuilder_standalone(self): txtb = xml_utils.TemplateXMLTreeBuilder(**self.MAPPING) txtb.feed(self.FULLREPLACE) @@ -247,6 +272,7 @@ class test_templatized_xml(xml_test_data): result = ElementTree.tostring(et) self.assertEqual(result, self.RESULTCHECK) + def test_TemplateXMLTreeBuilder_nosub(self): txtb = xml_utils.TemplateXMLTreeBuilder() # elementree pukes on identifiers starting with $ @@ -255,12 +281,14 @@ class test_templatized_xml(xml_test_data): result = ElementTree.tostring(et) self.assertEqual(result, self.RESULTCHECK) + def test_TemplateXML(self): tx = xml_utils.TemplateXML(self.FULLREPLACE, **self.MAPPING) et = ElementTree.ElementTree(None, tx.name) check = ElementTree.tostring(et.getroot()) self.assertEqual(check, self.RESULTCHECK) + def test_restore_fails(self): testmapping = {self.TEXT_REPLACE_KEY:"foobar"} xmlbackup = xml_utils.TemplateXML(self.XMLFILE, **testmapping) -- 1.7.1 _______________________________________________ Autotest-kernel mailing list [email protected] https://www.redhat.com/mailman/listinfo/autotest-kernel
