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 try:
19 from cStringIO import StringIO
20 except ImportError:
21 from io import StringIO
22 from xml.sax.saxutils import escape
26
27 """Information about a particular test.
28
29 Used by _XMLTestResult.
30
31 """
32
34 (self._class, self._method) = test.id().rsplit(".", 1)
35 self._time = time
36 self._error = None
37 self._failure = None
38
39 @staticmethod
41 """Create a _TestInfo instance for a successful test."""
42 return _TestInfo(test, time)
43
44 @staticmethod
46 """Create a _TestInfo instance for a failed test."""
47 info = _TestInfo(test, time)
48 info._failure = failure
49 return info
50
51 @staticmethod
53 """Create a _TestInfo instance for an erroneous test."""
54 info = _TestInfo(test, time)
55 info._error = error
56 return info
57
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
77
78
79
80
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
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
115
116 """A test result class that stores result as XML.
117
118 Used by XMLTestRunner.
119
120 """
121
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
131 unittest.TestResult.startTest(self, test)
132 self._error = None
133 self._failure = None
134 self._start_time = time.time()
135
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
148 unittest.TestResult.addError(self, test, err)
149 self._error = err
150
152 unittest.TestResult.addFailure(self, test, err)
153 self._failure = err
154
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
184
185
186
187
188
189
190
191 for info in self._tests:
192 info.print_report_text(stream)
193
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
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
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
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
268 self._stream = StringIO()
269
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
285
286 got = re.sub(r'time="\d+\.\d+"', 'time="0.000"', got)
287
288
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
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
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
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
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
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
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
389 """A file-like object that discards everything written to it."""
390 - def write(self, buffer):
392
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
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
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