+ Cleaned up many pylint/style warnings + Added unique extension for temp files retained due to exception + Unified del/exit-handling behavior + Added XMLTreeFile method for retrieving an XPATH to an element + Improved unittests
Signed-off-by: Chris Evich <[email protected]> --- client/shared/xml_utils.py | 77 +++++++++++++++++++++++++++-------- client/shared/xml_utils_unittest.py | 52 +++++++++++++++--------- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/client/shared/xml_utils.py b/client/shared/xml_utils.py index 2caf0df..9da40ee 100644 --- a/client/shared/xml_utils.py +++ b/client/shared/xml_utils.py @@ -36,7 +36,7 @@ module for examples. """ -import os.path, shutil, tempfile, string +import os.path, shutil, tempfile, string, StringIO try: import autotest.common as common @@ -48,16 +48,17 @@ import logging from autotest.client.shared import ElementTree # Also used by unittests -TMPPFX='xml_utils_temp_' -TMPSFX='.xml' -EXSFX='_exception_retained' +TMPPFX = 'xml_utils_temp_' +TMPSFX = '.xml' +EXSFX = '_exception_retained' +ENCODING = "UTF-8" class TempXMLFile(file): """ Temporary XML file auto-removed on instance del / module exit. """ - def __init__(self, suffix=TMPSFX, prefix=TMPPFX, mode="wb+", buffer=1): + def __init__(self, suffix=TMPSFX, prefix=TMPPFX, mode="wb+", buffsz=1): """ Initialize temporary XML file removed on instance destruction. @@ -66,17 +67,16 @@ class TempXMLFile(file): param: mode: file access mode param: buffer: size of buffer in bytes, 1: line buffered """ - fd,path = tempfile.mkstemp(suffix=suffix, prefix=prefix) + fd, path = tempfile.mkstemp(suffix=suffix, prefix=prefix) os.close(fd) - super(TempXMLFile, self).__init__(path, mode, buffer) + super(TempXMLFile, self).__init__(path, mode, buffsz) def _info(self): """ Inform user that file was not auto-deleted due to exceptional exit. """ - logging.info("Retaining backup of %s in %s", self.sourcefilename, - self.name + EXSFX) + logging.info("Retaining %s", self.name + EXSFX) def unlink(self): @@ -105,7 +105,7 @@ class TempXMLFile(file): """ unlink temporary file on instance delete. """ - + self.close() self.unlink() @@ -128,10 +128,19 @@ class XMLBackup(TempXMLFile): def __del__(self): + # Drop reference, don't delete source! self.sourcefilename = None super(XMLBackup, self).__del__() + def _info(self): + """ + Inform user that file was not auto-deleted due to exceptional exit. + """ + logging.info("Retaining backup of %s in %s", self.sourcefilename, + self.name + EXSFX) + + def backup(self): """ Overwrite temporary backup with contents of original source. @@ -139,7 +148,7 @@ class XMLBackup(TempXMLFile): self.flush() self.seek(0) - shutil.copyfileobj(file(self.sourcefilename, "rb"), super(XMLBackup,self)) + shutil.copyfileobj(file(self.sourcefilename, "rb"), super(XMLBackup, self)) self.seek(0) @@ -150,7 +159,7 @@ class XMLBackup(TempXMLFile): self.flush() self.seek(0) - shutil.copyfileobj(super(XMLBackup,self), file(self.sourcefilename, "wb+")) + shutil.copyfileobj(super(XMLBackup, self), file(self.sourcefilename, "wb+")) self.seek(0) @@ -177,6 +186,7 @@ class XMLTreeFile(ElementTree.ElementTree, XMLBackup): # Test if xml is a valid filename self.sourcebackupfile = open(xml, "rb") self.sourcebackupfile.close() + # XMLBackup init will take care of creating a copy except (IOError, OSError): # Assume xml is a string that needs a temporary source file self.sourcebackupfile = TempXMLFile() @@ -191,6 +201,11 @@ class XMLTreeFile(ElementTree.ElementTree, XMLBackup): self.flush() # make sure it's on-disk + def __str__(self): + self.write() + self.flush() + return self.sourcebackupfile.read() + def backup_copy(self): """Return a copy of instance, including copies of files""" return self.__class__(self.name) @@ -219,6 +234,30 @@ class XMLTreeFile(ElementTree.ElementTree, XMLBackup): return None + def get_xpath(self, element): + """Return the XPath string formed from first-match tag names""" + parent_map = self.get_parent_map() + root = self.getroot() + assert root in parent_map.values() + if element == root: + return '.' + # List of strings reversed at end + path_list = [] + while element != root: + # 2.7+ ElementPath supports predicates, so: + # element_index = list(parent_map[element]).index(element) + # element_index += 1 # XPath indexes are 1 based + # if element_index > 1: + # path_list.append(u"%s[%d]" % (element.tag, element_index)) + # else: + # path_list.append(u"%s" % element.tag) + path_list.append(u"%s" % element.tag) + element = parent_map[element] + assert element == root + path_list.reverse() + return "/".join(path_list) + + def remove(self, element): """ Removes a matching subelement. @@ -234,16 +273,18 @@ class XMLTreeFile(ElementTree.ElementTree, XMLBackup): @param: xpath: element name or path to remove """ - self.remove(self.find(xpath)) + self.remove(self.find(xpath)) # can't remove root - def write(self, filename=None, encoding="UTF-8"): + # This overrides the file.write() method + def write(self, filename=None, encoding=ENCODING): """ Write current XML tree to filename, or self.name if None. """ if filename is None: filename = self.name + # Avoid calling file.write() by mistake ElementTree.ElementTree.write(self, filename, encoding) @@ -310,15 +351,17 @@ class TemplateXML(XMLTreeFile): # XMLBase.__init__ calls self.write() after super init - def parse(self, source): + def parse(self, source, parser=None): """ Parse source XML file or filename using TemplateXMLTreeBuilder @param: source: XML file or filename @param: parser: ignored """ - - return super(TemplateXML, self).parse(source, self.parser) + if parser is None: + return super(TemplateXML, self).parse(source, self.parser) + else: + return super(TemplateXML, self).parse(source, parser) def restore(self): diff --git a/client/shared/xml_utils_unittest.py b/client/shared/xml_utils_unittest.py index 05ffd97..ebaa141 100755 --- a/client/shared/xml_utils_unittest.py +++ b/client/shared/xml_utils_unittest.py @@ -60,6 +60,7 @@ class xml_test_data(unittest.TestCase): if len(leftovers) > 0: self.fail('Leftover files: %s' % str(leftovers)) + def canonicalize_test_xml(self): et = ElementTree.parse(self.XMLFILE) et.write(self.XMLFILE, encoding="UTF-8") @@ -68,6 +69,22 @@ class xml_test_data(unittest.TestCase): f.close() + def is_same_contents(self, filename, other=None): + """Compare filename contents with XMLSTR, or contents of other""" + try: + f = file(filename, "rb") + s = f.read() + except (IOError, OSError): + logging.warning("File %s does not exist" % filename) + return False + if other is None: + return s == self.XMLSTR + else: + other_f = file(other, "rb") + other_s = other_f.read() + return s == other_s + + class test_ElementTree(xml_test_data): def test_bundled_elementtree(self): @@ -88,7 +105,6 @@ class test_TempXMLFile(xml_test_data): tmpf.seek(0) stuff = tmpf.read() self.assertEqual(stuff, self.XMLSTR) - del tmpf def test_TempXMLFile_implicit(self): @@ -111,22 +127,6 @@ class test_XMLBackup(xml_test_data): class_to_test = xml_utils.XMLBackup - - def is_same_contents(self, filename, other=None): - try: - f = file(filename, "rb") - s = f.read() - except (IOError, OSError): - logging.warning("File %s does not exist" % filename) - return False - if other is None: - return s == self.XMLSTR - else: - other_f = file(other, "rb") - other_s = other_f.read() - return s == other_s - - def test_backup_filename(self): xmlbackup = self.class_to_test(self.XMLFILE) self.assertEqual(xmlbackup.sourcefilename, self.XMLFILE) @@ -245,8 +245,6 @@ class test_XMLTreeFile(test_XMLBackup): self.assertFalse(self.is_same_contents(bu_tmps.name, tmps.name)) self.assertTrue(self.is_same_contents(bu_tmpf.name, bu_tmps.name)) self.assertFalse(self.is_same_contents(tmpf.name, tmps.name)) - del bu_tmpf - del bu_tmps def test_write_default(self): @@ -297,6 +295,22 @@ class test_XMLTreeFile(test_XMLBackup): self.assertTrue(self.is_same_contents(otherfile.name)) + def get_xpath_elements(self, target_path_string): + xmlbackup = self.class_to_test(self.XMLSTR) + target_element = xmlbackup.find(target_path_string) + test_path_string = xmlbackup.get_xpath(target_element) + test_element = xmlbackup.find(test_path_string) + return (target_element, test_element) + + + def test_get_xpath(self): + # 2.6 ElementPath doesn't support predicates as in 2.7 :( + # (it blindly returns the first match) + self.assertEqual(*self.get_xpath_elements('guest/arch/wordsize')) + self.assertEqual(*self.get_xpath_elements('guest/arch/machine')) + self.assertEqual(*self.get_xpath_elements('host/cpu/arch')) + + class test_templatized_xml(xml_test_data): def setUp(self): -- 1.7.1 _______________________________________________ Autotest-kernel mailing list [email protected] https://www.redhat.com/mailman/listinfo/autotest-kernel
