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 os.path 
 13  import re 
 14  import sys 
 15  import time 
 16  import traceback 
 17  import unittest 
 18  from StringIO import StringIO 
 19  from xml.sax.saxutils import escape 
 20   
 21  from StringIO import StringIO 
22 23 24 -class _TestInfo(object):
25 26 """Information about a particular test. 27 28 Used by _XMLTestResult. 29 30 """ 31
32 - def __init__(self, test, time):
33 (self._class, self._method) = test.id().rsplit(".", 1) 34 self._time = time 35 self._error = None 36 self._failure = None
37 38 @staticmethod
39 - def create_success(test, time):
40 """Create a _TestInfo instance for a successful test.""" 41 return _TestInfo(test, time)
42 43 @staticmethod
44 - def create_failure(test, time, failure):
45 """Create a _TestInfo instance for a failed test.""" 46 info = _TestInfo(test, time) 47 info._failure = failure 48 return info
49 50 @staticmethod
51 - def create_error(test, time, error):
52 """Create a _TestInfo instance for an erroneous test.""" 53 info = _TestInfo(test, time) 54 info._error = error 55 return info
56
57 - def print_report(self, stream):
58 """Print information about this test case in XML format to the 59 supplied stream. 60 61 """ 62 stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ 63 { 64 "class": self._class, 65 "method": self._method, 66 "time": self._time, 67 }) 68 if self._failure != None: 69 self._print_error(stream, 'failure', self._failure) 70 if self._error != None: 71 self._print_error(stream, 'error', self._error) 72 stream.write('</testcase>\n')
73
74 - def print_report_text(self, stream):
75 #stream.write(' <testcase classname="%(class)s" name="%(method)s" time="%(time).4f">' % \ 76 # { 77 # "class": self._class, 78 # "method": self._method, 79 # "time": self._time, 80 # }) 81 stream.write(self._method) 82 if self._failure != None: 83 stream.write(' ... FAILURE!\n') 84 self._print_error_text(stream, 'failure', self._failure) 85 if self._error != None: 86 stream.write(' ... ERROR!\n') 87 self._print_error_text(stream, 'error', self._error) 88 if self._failure == None and self._error == None: 89 stream.write(' ... ok\n')
90
91 - def _print_error(self, stream, tagname, error):
92 """Print information from a failure or error to the supplied stream.""" 93 text = escape(str(error[1])) 94 stream.write('\n') 95 stream.write(' <%s type="%s">%s\n' \ 96 % (tagname, str(error[0].__name__), text)) 97 tb_stream = StringIO() 98 traceback.print_tb(error[2], None, tb_stream) 99 stream.write(escape(tb_stream.getvalue())) 100 stream.write(' </%s>\n' % tagname) 101 stream.write(' ')
102
103 - def _print_error_text(self, stream, tagname, error):
104 """Print information from a failure or error to the supplied stream.""" 105 text = escape(str(error[1])) 106 stream.write('%s: %s\n' \ 107 % (tagname.upper(), text)) 108 tb_stream = StringIO() 109 traceback.print_tb(error[2], None, tb_stream) 110 stream.write(escape(tb_stream.getvalue())) 111 stream.write('-'*80 + '\n')
112
113 -class _XMLTestResult(unittest.TestResult):
114 115 """A test result class that stores result as XML. 116 117 Used by XMLTestRunner. 118 119 """ 120
121 - def __init__(self, classname):
122 unittest.TestResult.__init__(self) 123 self._test_name = classname 124 self._start_time = None 125 self._tests = [] 126 self._error = None 127 self._failure = None
128
129 - def startTest(self, test):
130 unittest.TestResult.startTest(self, test) 131 self._error = None 132 self._failure = None 133 self._start_time = time.time()
134
135 - def stopTest(self, test):
136 time_taken = time.time() - self._start_time 137 unittest.TestResult.stopTest(self, test) 138 if self._error: 139 info = _TestInfo.create_error(test, time_taken, self._error) 140 elif self._failure: 141 info = _TestInfo.create_failure(test, time_taken, self._failure) 142 else: 143 info = _TestInfo.create_success(test, time_taken) 144 self._tests.append(info)
145
146 - def addError(self, test, err):
147 unittest.TestResult.addError(self, test, err) 148 self._error = err
149
150 - def addFailure(self, test, err):
151 unittest.TestResult.addFailure(self, test, err) 152 self._failure = err
153
154 - def print_report(self, stream, time_taken, out, err):
155 """Prints the XML report to the supplied stream. 156 157 The time the tests took to perform as well as the captured standard 158 output and standard error streams must be passed in.a 159 160 """ 161 stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ 162 { "e": len(self.errors), "f": len(self.failures) }) 163 stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ 164 { 165 "n": self._test_name, 166 "t": self.testsRun, 167 "time": time_taken, 168 }) 169 for info in self._tests: 170 info.print_report(stream) 171 stream.write(' <system-out><![CDATA[%s]]></system-out>\n' % out) 172 stream.write(' <system-err><![CDATA[%s]]></system-err>\n' % err) 173 stream.write('</testsuite>\n')
174
175 - def print_report_text(self, stream, time_taken, out, err):
176 """Prints the text report to the supplied stream. 177 178 The time the tests took to perform as well as the captured standard 179 output and standard error streams must be passed in.a 180 181 """ 182 #stream.write('<testsuite errors="%(e)d" failures="%(f)d" ' % \ 183 # { "e": len(self.errors), "f": len(self.failures) }) 184 #stream.write('name="%(n)s" tests="%(t)d" time="%(time).3f">\n' % \ 185 # { 186 # "n": self._test_name, 187 # "t": self.testsRun, 188 # "time": time_taken, 189 # }) 190 for info in self._tests: 191 info.print_report_text(stream)
192
193 194 -class XMLTestRunner(object):
195 196 """A test runner that stores results in XML format compatible with JUnit. 197 198 XMLTestRunner(stream=None) -> XML test runner 199 200 The XML file is written to the supplied stream. If stream is None, the 201 results are stored in a file called TEST-<module>.<class>.xml in the 202 current working directory (if not overridden with the path property), 203 where <module> and <class> are the module and class name of the test class. 204 205 """ 206
207 - def __init__(self, stream=None):
208 self._stream = stream 209 self._path = "."
210
211 - def run(self, test):
212 """Run the given test case or test suite.""" 213 class_ = test.__class__ 214 classname = class_.__module__ + "." + class_.__name__ 215 if self._stream == None: 216 filename = "TEST-%s.xml" % classname 217 stream = file(os.path.join(self._path, filename), "w") 218 stream.write('<?xml version="1.0" encoding="utf-8"?>\n') 219 else: 220 stream = self._stream 221 222 result = _XMLTestResult(classname) 223 start_time = time.time() 224 225 # TODO: Python 2.5: Use the with statement 226 old_stdout = sys.stdout 227 old_stderr = sys.stderr 228 sys.stdout = StringIO() 229 sys.stderr = StringIO() 230 231 try: 232 test(result) 233 try: 234 out_s = sys.stdout.getvalue() 235 except AttributeError: 236 out_s = "" 237 try: 238 err_s = sys.stderr.getvalue() 239 except AttributeError: 240 err_s = "" 241 finally: 242 sys.stdout = old_stdout 243 sys.stderr = old_stderr 244 245 time_taken = time.time() - start_time 246 result.print_report(stream, time_taken, out_s, err_s) 247 248 result.print_report_text(sys.stdout, time_taken, out_s, err_s) 249 250 if self._stream == None: 251 stream.close() 252 253 return result
254
255 - def _set_path(self, path):
256 self._path = path
257 258 path = property(lambda self: self._path, _set_path, None, 259 """The path where the XML files are stored. 260 261 This property is ignored when the XML file is written to a file 262 stream.""")
263
264 265 -class XMLTestRunnerTest(unittest.TestCase):
266 - def setUp(self):
267 self._stream = StringIO()
268
269 - def _try_test_run(self, test_class, expected):
270 271 """Run the test suite against the supplied test class and compare the 272 XML result against the expected XML string. Fail if the expected 273 string doesn't match the actual string. All time attribute in the 274 expected string should have the value "0.000". All error and failure 275 messages are reduced to "Foobar". 276 277 """ 278 279 runner = XMLTestRunner(self._stream) 280 runner.run(unittest.makeSuite(test_class)) 281 282 got = self._stream.getvalue() 283 # Replace all time="X.YYY" attributes by time="0.000" to enable a 284 # simple string comparison. 285 got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got) 286 # Likewise, replace all failure and error messages by a simple "Foobar" 287 # string. 288 got = re.sub(r'(?s)<failure (.*?)>.*?</failure>', r'<failure \1>Foobar</failure>', got) 289 got = re.sub(r'(?s)<error (.*?)>.*?</error>', r'<error \1>Foobar</error>', got) 290 291 self.assertEqual(expected, got)
292
293 - def test_no_tests(self):
294 """Regression test: Check whether a test run without any tests 295 matches a previous run. 296 297 """ 298 class TestTest(unittest.TestCase): 299 pass
300 self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="0" time="0.000"> 301 <system-out><![CDATA[]]></system-out> 302 <system-err><![CDATA[]]></system-err> 303 </testsuite> 304 """)
305
306 - def test_success(self):
307 """Regression test: Check whether a test run with a successful test 308 matches a previous run. 309 310 """ 311 class TestTest(unittest.TestCase): 312 def test_foo(self): 313 pass
314 self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> 315 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> 316 <system-out><![CDATA[]]></system-out> 317 <system-err><![CDATA[]]></system-err> 318 </testsuite> 319 """) 320
321 - def test_failure(self):
322 """Regression test: Check whether a test run with a failing test 323 matches a previous run. 324 325 """ 326 class TestTest(unittest.TestCase): 327 def test_foo(self): 328 self.assert_(False)
329 self._try_test_run(TestTest, """<testsuite errors="0" failures="1" name="unittest.TestSuite" tests="1" time="0.000"> 330 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> 331 <failure type="exceptions.AssertionError">Foobar</failure> 332 </testcase> 333 <system-out><![CDATA[]]></system-out> 334 <system-err><![CDATA[]]></system-err> 335 </testsuite> 336 """) 337
338 - def test_error(self):
339 """Regression test: Check whether a test run with a erroneous test 340 matches a previous run. 341 342 """ 343 class TestTest(unittest.TestCase): 344 def test_foo(self): 345 raise IndexError()
346 self._try_test_run(TestTest, """<testsuite errors="1" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> 347 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"> 348 <error type="exceptions.IndexError">Foobar</error> 349 </testcase> 350 <system-out><![CDATA[]]></system-out> 351 <system-err><![CDATA[]]></system-err> 352 </testsuite> 353 """) 354
355 - def test_stdout_capture(self):
356 """Regression test: Check whether a test run with output to stdout 357 matches a previous run. 358 359 """ 360 class TestTest(unittest.TestCase): 361 def test_foo(self): 362 print("Test")
363 self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> 364 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> 365 <system-out><![CDATA[Test 366 ]]></system-out> 367 <system-err><![CDATA[]]></system-err> 368 </testsuite> 369 """) 370
371 - def test_stderr_capture(self):
372 """Regression test: Check whether a test run with output to stderr 373 matches a previous run. 374 375 """ 376 class TestTest(unittest.TestCase): 377 def test_foo(self): 378 print("Test", file=sys.stderr)
379 self._try_test_run(TestTest, """<testsuite errors="0" failures="0" name="unittest.TestSuite" tests="1" time="0.000"> 380 <testcase classname="__main__.TestTest" name="test_foo" time="0.000"></testcase> 381 <system-out><![CDATA[]]></system-out> 382 <system-err><![CDATA[Test 383 ]]></system-err> 384 </testsuite> 385 """) 386
387 - class NullStream(object):
388 """A file-like object that discards everything written to it."""
389 - def write(self, buffer):
390 pass
391
392 - def test_unittests_changing_stdout(self):
393 """Check whether the XMLTestRunner recovers gracefully from unit tests 394 that change stdout, but don't change it back properly. 395 396 """ 397 class TestTest(unittest.TestCase): 398 def test_foo(self): 399 sys.stdout = XMLTestRunnerTest.NullStream()
400 401 runner = XMLTestRunner(self._stream) 402 runner.run(unittest.makeSuite(TestTest)) 403
404 - def test_unittests_changing_stderr(self):
405 """Check whether the XMLTestRunner recovers gracefully from unit tests 406 that change stderr, but don't change it back properly. 407 408 """ 409 class TestTest(unittest.TestCase): 410 def test_foo(self): 411 sys.stderr = XMLTestRunnerTest.NullStream()
412 413 runner = XMLTestRunner(self._stream) 414 runner.run(unittest.makeSuite(TestTest)) 415
416 417 -class XMLTestProgram(unittest.TestProgram):
418 - def runTests(self):
419 if self.testRunner is None: 420 self.testRunner = XMLTestRunner() 421 unittest.TestProgram.runTests(self)
422 423 main = XMLTestProgram 424 425 426 if __name__ == "__main__": 427 main(module=None) 428