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