1 """
2 XML Test Runner for PyUnit
3 """
4
5
6
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
25
26 """Information about a particular test.
27
28 Used by _XMLTestResult.
29
30 """
31
33 (self._class, self._method) = test.id().rsplit(".", 1)
34 self._time = time
35 self._error = None
36 self._failure = None
37
38 @staticmethod
40 """Create a _TestInfo instance for a successful test."""
41 return _TestInfo(test, time)
42
43 @staticmethod
45 """Create a _TestInfo instance for a failed test."""
46 info = _TestInfo(test, time)
47 info._failure = failure
48 return info
49
50 @staticmethod
52 """Create a _TestInfo instance for an erroneous test."""
53 info = _TestInfo(test, time)
54 info._error = error
55 return info
56
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
76
77
78
79
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
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
114
115 """A test result class that stores result as XML.
116
117 Used by XMLTestRunner.
118
119 """
120
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
130 unittest.TestResult.startTest(self, test)
131 self._error = None
132 self._failure = None
133 self._start_time = time.time()
134
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
147 unittest.TestResult.addError(self, test, err)
148 self._error = err
149
151 unittest.TestResult.addFailure(self, test, err)
152 self._failure = err
153
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
183
184
185
186
187
188
189
190 for info in self._tests:
191 info.print_report_text(stream)
192
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
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
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
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
267 self._stream = StringIO()
268
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
284
285 got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got)
286
287
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
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
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
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
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
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
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
388 """A file-like object that discards everything written to it."""
389 - def write(self, buffer):
391
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
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
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