Package rosunit :: Module xmlrunner
[frames] | no frames]

Source Code for Module rosunit.xmlrunner

  1  """ 
  2  XML Test Runner for PyUnit 
  3  """ 
  4   
  5  # Written by Sebastian Rittau <srittau@jroger.in-berlin.de> and placed in 
  6  # the Public Domain. With contributions by Paolo Borelli. 
  7   
  8  from __future__ import print_function 
  9   
 10  __revision__ = "$Id$" 
 11   
 12  import codecs 
 13  import os.path 
 14  import re 
 15  import sys 
 16  import time 
 17  import traceback 
 18  import unittest 
 19  try: 
 20      from cStringIO import StringIO 
 21      python2 = True 
 22  except ImportError: 
 23      from io import StringIO 
 24      python2 = False 
 25  from xml.sax.saxutils import escape 
 26  import xml.etree.ElementTree as ET 
27 28 -def cdata(cdata_text):
29 return '<![CDATA[\n{}\n]]>'.format(cdata_text)
30
31 -class _TestInfo(object):
32 33 """Information about a particular test. 34 35 Used by _XMLTestResult. 36 37 """ 38
39 - def __init__(self, test, time):
40 (self._class, self._method) = test.id().rsplit(".", 1) 41 self._time = time 42 self._error = None 43 self._failure = None
44 45 @staticmethod
46 - def create_success(test, time):
47 """Create a _TestInfo instance for a successful test.""" 48 return _TestInfo(test, time)
49 50 @staticmethod
51 - def create_failure(test, time, failure):
52 """Create a _TestInfo instance for a failed test.""" 53 info = _TestInfo(test, time) 54 info._failure = failure 55 return info
56 57 @staticmethod
58 - def create_error(test, time, error):
59 """Create a _TestInfo instance for an erroneous test.""" 60 info = _TestInfo(test, time) 61 info._error = error 62 return info
63
64 - def xml(self):
65 """Create an XML tag with information about this test case. 66 67 """ 68 testcase = ET.Element("testcase") 69 testcase.set('classname', self._class) 70 testcase.set('name', self._method) 71 testcase.set('time', '%.4f' % self._time) 72 if self._failure != None: 73 self._print_error(testcase, 'failure', self._failure) 74 if self._error != None: 75 self._print_error(testcase, 'error', self._error) 76 return testcase
77
78 - def print_report(self, stream):
79 """Print information about this test case in XML format to the 80 supplied stream. 81 82 """ 83 stream.write(ET.tostring(self.xml()))
84
85 - def print_report_text(self, stream):
86 #stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ 87 # { 88 # "class": self._class, 89 # "method": self._method, 90 # "time": self._time, 91 # }) 92 stream.write('[Testcase: ' + self._method + ']') 93 if self._failure != None: 94 stream.write(' ... FAILURE!\n') 95 self._print_error_text(stream, 'failure', self._failure) 96 if self._error != None: 97 stream.write(' ... ERROR!\n') 98 self._print_error_text(stream, 'error', self._error) 99 if self._failure == None and self._error == None: 100 stream.write(' ... ok\n')
101
102 - def _print_error(self, testcase, tagname, error):
103 """ 104 Append an XML tag with information from a failure or error to the 105 supplied testcase. 106 """ 107 tag = ET.SubElement(testcase, tagname) 108 tag.set('type', str(error[0].__name__)) 109 tb_stream = StringIO() 110 traceback.print_tb(error[2], None, tb_stream) 111 tag.text ='%s\n%s' % (str(error[1]), tb_stream.getvalue())
112
113 - def _print_error_text(self, stream, tagname, error):
114 """Print information from a failure or error to the supplied stream.""" 115 text = escape(str(error[1])) 116 stream.write('%s: %s\n' \ 117 % (tagname.upper(), text)) 118 tb_stream = StringIO() 119 traceback.print_tb(error[2], None, tb_stream) 120 stream.write(escape(tb_stream.getvalue())) 121 stream.write('-'*80 + '\n')
122
123 -class _XMLTestResult(unittest.TestResult):
124 125 """A test result class that stores result as XML. 126 127 Used by XMLTestRunner. 128 129 """ 130
131 - def __init__(self, classname):
132 unittest.TestResult.__init__(self) 133 self._test_name = classname 134 self._start_time = None 135 self._tests = [] 136 self._error = None 137 self._failure = None
138
139 - def startTest(self, test):
140 unittest.TestResult.startTest(self, test) 141 self._error = None 142 self._failure = None 143 self._start_time = time.time()
144
145 - def stopTest(self, test):
146 time_taken = time.time() - self._start_time 147 unittest.TestResult.stopTest(self, test) 148 if self._error: 149 info = _TestInfo.create_error(test, time_taken, self._error) 150 elif self._failure: 151 info = _TestInfo.create_failure(test, time_taken, self._failure) 152 else: 153 info = _TestInfo.create_success(test, time_taken) 154 self._tests.append(info)
155
156 - def addError(self, test, err):
157 unittest.TestResult.addError(self, test, err) 158 self._error = err
159
160 - def addFailure(self, test, err):
161 unittest.TestResult.addFailure(self, test, err) 162 self._failure = err
163
164 - def filter_nonprintable_text(self, text):
165 pattern = r'[^\x09\x0A\x0D\x20-\x7E\x85\xA0-\xFF\u0100-\uD7FF\uE000-\uFDCF\uFDE0-\uFFFD]' 166 if python2: 167 pattern = pattern.decode('unicode_escape') 168 else: 169 pattern = codecs.decode(pattern, 'unicode_escape') 170 invalid_chars = re.compile(pattern) 171 172 def invalid_char_replacer(m): 173 return "&#x"+('%04X' % ord(m.group(0)))+";"
174 return re.sub(invalid_chars, invalid_char_replacer, str(text))
175
176 - def xml(self, time_taken, out, err):
177 """ 178 @return XML tag representing the object 179 @rtype: xml.etree.ElementTree.Element 180 """ 181 test_suite = ET.Element('testsuite') 182 test_suite.set('errors', str(len(self.errors))) 183 test_suite.set('failures', str(len(self.failures))) 184 test_suite.set('name', self._test_name) 185 test_suite.set('tests', str(self.testsRun)) 186 test_suite.set('time', '%.3f' % time_taken) 187 for info in self._tests: 188 test_suite.append(info.xml()) 189 system_out = ET.SubElement(test_suite, 'system-out') 190 system_out.text = cdata(self.filter_nonprintable_text(out)) 191 system_err = ET.SubElement(test_suite, 'system-err') 192 system_err.text = cdata(self.filter_nonprintable_text(err)) 193 return ET.ElementTree(test_suite)
194
195 - def print_report(self, stream, time_taken, out, err):
196 """Prints the XML report to the supplied stream. 197 198 The time the tests took to perform as well as the captured standard 199 output and standard error streams must be passed in.a 200 201 """ 202 root = self.xml(time_taken, out, err).getroot() 203 stream.write(ET.tostring(root, encoding='utf-8', method='xml').decode('utf-8'))
204
205 - def print_report_text(self, stream, time_taken, out, err):
206 """Prints the text report to the supplied stream. 207 208 The time the tests took to perform as well as the captured standard 209 output and standard error streams must be passed in.a 210 211 """ 212 #stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ 213 # { "e": len(self.errors), "f": len(self.failures) }) 214 #stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ 215 # { 216 # "n": self._test_name, 217 # "t": self.testsRun, 218 # "time": time_taken, 219 # }) 220 for info in self._tests: 221 info.print_report_text(stream)
222
223 224 -class XMLTestRunner(object):
225 226 """A test runner that stores results in XML format compatible with JUnit. 227 228 XMLTestRunner(stream=None) -> XML test runner 229 230 The XML file is written to the supplied stream. If stream is None, the 231 results are stored in a file called TEST-<module>.<class>.xml in the 232 current working directory (if not overridden with the path property), 233 where <module> and <class> are the module and class name of the test class. 234 235 """ 236
237 - def __init__(self, stream=None):
238 self._stream = stream 239 self._path = "."
240
241 - def run(self, test):
242 """Run the given test case or test suite.""" 243 class_ = test.__class__ 244 classname = class_.__module__ + "." + class_.__name__ 245 if self._stream == None: 246 filename = "TEST-%s.xml" % classname 247 stream = file(os.path.join(self._path, filename), "w") 248 stream.write('<?xml version="1.0" encoding="utf-8"?>\n') 249 else: 250 stream = self._stream 251 252 result = _XMLTestResult(classname) 253 start_time = time.time() 254 255 # TODO: Python 2.5: Use the with statement 256 old_stdout = sys.stdout 257 old_stderr = sys.stderr 258 sys.stdout = StringIO() 259 sys.stderr = StringIO() 260 261 try: 262 test(result) 263 try: 264 out_s = sys.stdout.getvalue() 265 except AttributeError: 266 out_s = "" 267 try: 268 err_s = sys.stderr.getvalue() 269 except AttributeError: 270 err_s = "" 271 finally: 272 sys.stdout = old_stdout 273 sys.stderr = old_stderr 274 275 time_taken = time.time() - start_time 276 result.print_report(stream, time_taken, out_s, err_s) 277 278 result.print_report_text(sys.stdout, time_taken, out_s, err_s) 279 280 return result
281
282 - def _set_path(self, path):
283 self._path = path
284 285 path = property(lambda self: self._path, _set_path, None, 286 """The path where the XML files are stored. 287 288 This property is ignored when the XML file is written to a file 289 stream.""")
290