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