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