1 '''Test runner and result class for the regression test suite.
2
3 '''
4
5 import functools
6 import io
7 import sys
8 import time
9 import traceback
10 import unittest
11 from test import support
12
13 class ESC[4;38;5;81mRegressionTestResult(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTextTestResult):
14 USE_XML = False
15
16 def __init__(self, stream, descriptions, verbosity):
17 super().__init__(stream=stream, descriptions=descriptions,
18 verbosity=2 if verbosity else 0)
19 self.buffer = True
20 if self.USE_XML:
21 from xml.etree import ElementTree as ET
22 from datetime import datetime
23 self.__ET = ET
24 self.__suite = ET.Element('testsuite')
25 self.__suite.set('start', datetime.utcnow().isoformat(' '))
26 self.__e = None
27 self.__start_time = None
28
29 @classmethod
30 def __getId(cls, test):
31 try:
32 test_id = test.id
33 except AttributeError:
34 return str(test)
35 try:
36 return test_id()
37 except TypeError:
38 return str(test_id)
39 return repr(test)
40
41 def startTest(self, test):
42 super().startTest(test)
43 if self.USE_XML:
44 self.__e = e = self.__ET.SubElement(self.__suite, 'testcase')
45 self.__start_time = time.perf_counter()
46
47 def _add_result(self, test, capture=False, **args):
48 if not self.USE_XML:
49 return
50 e = self.__e
51 self.__e = None
52 if e is None:
53 return
54 ET = self.__ET
55
56 e.set('name', args.pop('name', self.__getId(test)))
57 e.set('status', args.pop('status', 'run'))
58 e.set('result', args.pop('result', 'completed'))
59 if self.__start_time:
60 e.set('time', f'{time.perf_counter() - self.__start_time:0.6f}')
61
62 if capture:
63 if self._stdout_buffer is not None:
64 stdout = self._stdout_buffer.getvalue().rstrip()
65 ET.SubElement(e, 'system-out').text = stdout
66 if self._stderr_buffer is not None:
67 stderr = self._stderr_buffer.getvalue().rstrip()
68 ET.SubElement(e, 'system-err').text = stderr
69
70 for k, v in args.items():
71 if not k or not v:
72 continue
73 e2 = ET.SubElement(e, k)
74 if hasattr(v, 'items'):
75 for k2, v2 in v.items():
76 if k2:
77 e2.set(k2, str(v2))
78 else:
79 e2.text = str(v2)
80 else:
81 e2.text = str(v)
82
83 @classmethod
84 def __makeErrorDict(cls, err_type, err_value, err_tb):
85 if isinstance(err_type, type):
86 if err_type.__module__ == 'builtins':
87 typename = err_type.__name__
88 else:
89 typename = f'{err_type.__module__}.{err_type.__name__}'
90 else:
91 typename = repr(err_type)
92
93 msg = traceback.format_exception(err_type, err_value, None)
94 tb = traceback.format_exception(err_type, err_value, err_tb)
95
96 return {
97 'type': typename,
98 'message': ''.join(msg),
99 '': ''.join(tb),
100 }
101
102 def addError(self, test, err):
103 self._add_result(test, True, error=self.__makeErrorDict(*err))
104 super().addError(test, err)
105
106 def addExpectedFailure(self, test, err):
107 self._add_result(test, True, output=self.__makeErrorDict(*err))
108 super().addExpectedFailure(test, err)
109
110 def addFailure(self, test, err):
111 self._add_result(test, True, failure=self.__makeErrorDict(*err))
112 super().addFailure(test, err)
113 if support.failfast:
114 self.stop()
115
116 def addSkip(self, test, reason):
117 self._add_result(test, skipped=reason)
118 super().addSkip(test, reason)
119
120 def addSuccess(self, test):
121 self._add_result(test)
122 super().addSuccess(test)
123
124 def addUnexpectedSuccess(self, test):
125 self._add_result(test, outcome='UNEXPECTED_SUCCESS')
126 super().addUnexpectedSuccess(test)
127
128 def get_xml_element(self):
129 if not self.USE_XML:
130 raise ValueError("USE_XML is false")
131 e = self.__suite
132 e.set('tests', str(self.testsRun))
133 e.set('errors', str(len(self.errors)))
134 e.set('failures', str(len(self.failures)))
135 return e
136
137 class ESC[4;38;5;81mQuietRegressionTestRunner:
138 def __init__(self, stream, buffer=False):
139 self.result = RegressionTestResult(stream, None, 0)
140 self.result.buffer = buffer
141
142 def run(self, test):
143 test(self.result)
144 return self.result
145
146 def get_test_runner_class(verbosity, buffer=False):
147 if verbosity:
148 return functools.partial(unittest.TextTestRunner,
149 resultclass=RegressionTestResult,
150 buffer=buffer,
151 verbosity=verbosity)
152 return functools.partial(QuietRegressionTestRunner, buffer=buffer)
153
154 def get_test_runner(stream, verbosity, capture_output=False):
155 return get_test_runner_class(verbosity, capture_output)(stream)
156
157 if __name__ == '__main__':
158 import xml.etree.ElementTree as ET
159 RegressionTestResult.USE_XML = True
160
161 class ESC[4;38;5;81mTestTests(ESC[4;38;5;149munittestESC[4;38;5;149m.ESC[4;38;5;149mTestCase):
162 def test_pass(self):
163 pass
164
165 def test_pass_slow(self):
166 time.sleep(1.0)
167
168 def test_fail(self):
169 print('stdout', file=sys.stdout)
170 print('stderr', file=sys.stderr)
171 self.fail('failure message')
172
173 def test_error(self):
174 print('stdout', file=sys.stdout)
175 print('stderr', file=sys.stderr)
176 raise RuntimeError('error message')
177
178 suite = unittest.TestSuite()
179 suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestTests))
180 stream = io.StringIO()
181 runner_cls = get_test_runner_class(sum(a == '-v' for a in sys.argv))
182 runner = runner_cls(sys.stdout)
183 result = runner.run(suite)
184 print('Output:', stream.getvalue())
185 print('XML: ', end='')
186 for s in ET.tostringlist(result.get_xml_element()):
187 print(s.decode(), end='')
188 print()