Łukasz Langa avatar Łukasz Langa committed 6644897

change the plugin implementation to use xml.etree

Comments (0)

Files changed (1)

unittest2/plugins/junitxml.py

 
 """
 
+from xml.etree import ElementTree as ET
+
 from unittest2.events import Plugin, addOption
 
 def xmlEscape(value):
     return value
 
 class JunitXml(Plugin):
-    
+
     configSection = 'junit-xml'
     commandLineSwitch = ('X', 'junit-xml', 'Generate junit-xml output report')
-    
+
     def __init__(self):
-        self.path = self.config.as_bool('path', default='junit.xml')
+        self.path = self.config.as_str('path', default='junit.xml')
         self.errors = 0
         self.failed = 0
         self.skipped = 0
         self.numtests = 0
-        self.test_logs = []
+        self.tree = ET.Element('testsuite')
 
     def startTest(self, event):
         self.numtests += 1
 
     def stopTest(self, event):
-        d = {'time': "%.6f" % event.timeTaken}
         test = event.test
-        classnames = ('%s.%s' % (test.__module__, 
+        classnames = ('%s.%s' % (test.__module__,
                                  test.__class__.__name__)).split('.')
-        d['classname'] = ".".join(classnames)
-        d['name'] = xmlEscape(test._testMethodName)
-        attrs = ['%s="%s"' % item for item in sorted(d.items())]
-        
-        self.test_logs.append('<testcase %s>' % ' '.join(attrs))
-        
+        testcase = ET.SubElement(self.tree, 'testcase')
+        testcase.set('time', "%.6f" % event.timeTaken)
+        testcase.set('classname', '.'.join(classnames))
+        testcase.set('name', test._testMethodName)
+
         msg = ''
         if event.exc_info:
             msg = xmlEscape(event.exc_info[1])
         if event.error:
             self.errors += 1
-            self.test_logs.append(
-                '<error message="test failure">%s</error>' % msg
-            )
+            error = ET.SubElement(testcase, 'error')
+            error.set('message', 'test failure')
+            error.text = msg
         elif event.failed:
             self.failed += 1
-            self.test_logs.append(
-                '<failure message="test failure">%s</failure>' % msg
-            )
+            failure = ET.SubElement(testcase, 'failure')
+            failure.set('message', 'test failure')
+            failure.text = msg
         elif event.unexpectedSuccess:
             self.skipped += 1
-            self.test_logs.append(
-                '<skipped message="test passes unexpectedly"/>'
-            )
+            skipped = ET.SubElement(testcase, 'skipped')
+            skipped.set('message', 'test passes unexpectedly')
         elif event.skipped:
             self.skipped += 1
+            skipped = ET.SubElement(testcase, 'skipped')
+            # XXX Unfinished?
             self.test_logs.append("<skipped/>")
         elif event.expectedFailure:
             self.skipped += 1
-            self.test_logs.append(
-                '<skipped message="expected test failure">%s</skipped>' % msg
-            )
-        
-        self.test_logs.append("</testcase>")
-        
+            skipped = ET.SubElement(testcase, 'skipped')
+            skipped.set('message', 'expected test failure')
+            skipped.text = msg
 
     def stopTestRun(self, event):
-        data = []
-        data.append('<?xml version="1.0" encoding="utf-8"?>')
-        data.append('<testsuite ')
-        data.append('name="" ')
-        data.append('errors="%i" ' % self.errors)
-        data.append('failures="%i" ' % self.failed)
-        data.append('skips="%i" ' % self.skipped)
-        data.append('tests="%i" ' % self.numtests)
-        data.append('time="%.3f"' % event.timeTaken)
-        data.append(' >')
-        data.extend(self.test_logs)
-        data.append('</testsuite>')
-        
-        handle = open(self.path, 'w')
-        try:
-            handle.write('\n'.join(data))
-            handle.write('\n')
-        finally:
-            handle.close()
+        self.tree.set('name', 'w00t')
+        self.tree.set('errors', str(self.errors))
+        self.tree.set('failures' , str(self.failed))
+        self.tree.set('skips', str(self.skipped))
+        self.tree.set('tests', str(self.numtests))
+        self.tree.set('time', "%.3f" % event.timeTaken)
+
+        self._indent_tree(self.tree)
+        output = ET.ElementTree(self.tree)
+        output.write(self.path, encoding="utf-8", xml_declaration=True)
+
+    def _indent_tree(self, elem, level=0):
+        """In-place pretty formatting of the ElementTree structure."""
+        i = "\n" + level*"  "
+        if len(elem):
+            if not elem.text or not elem.text.strip():
+                elem.text = i + "  "
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+            for elem in elem:
+                self._indent_tree(elem, level+1)
+            if not elem.tail or not elem.tail.strip():
+                elem.tail = i
+        else:
+            if level and (not elem.tail or not elem.tail.strip()):
+                elem.tail = i
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.