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