| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | '''Test runner and result class for the regression test suite.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | '''
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import functools | 
					
						
							|  |  |  | import io | 
					
						
							|  |  |  | import sys | 
					
						
							|  |  |  | import time | 
					
						
							|  |  |  | import traceback | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class RegressionTestResult(unittest.TextTestResult): | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |     USE_XML = False | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def __init__(self, stream, descriptions, verbosity): | 
					
						
							| 
									
										
										
										
											2021-08-31 20:45:09 +03:00
										 |  |  |         super().__init__(stream=stream, descriptions=descriptions, | 
					
						
							|  |  |  |                          verbosity=2 if verbosity else 0) | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         self.buffer = True | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |         if self.USE_XML: | 
					
						
							|  |  |  |             from xml.etree import ElementTree as ET | 
					
						
							| 
									
										
										
										
											2021-03-23 01:40:31 +01:00
										 |  |  |             from datetime import datetime | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |             self.__ET = ET | 
					
						
							|  |  |  |             self.__suite = ET.Element('testsuite') | 
					
						
							|  |  |  |             self.__suite.set('start', datetime.utcnow().isoformat(' ')) | 
					
						
							|  |  |  |             self.__e = None | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         self.__start_time = None | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def __getId(cls, test): | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             test_id = test.id | 
					
						
							|  |  |  |         except AttributeError: | 
					
						
							|  |  |  |             return str(test) | 
					
						
							|  |  |  |         try: | 
					
						
							|  |  |  |             return test_id() | 
					
						
							|  |  |  |         except TypeError: | 
					
						
							|  |  |  |             return str(test_id) | 
					
						
							|  |  |  |         return repr(test) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def startTest(self, test): | 
					
						
							|  |  |  |         super().startTest(test) | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |         if self.USE_XML: | 
					
						
							|  |  |  |             self.__e = e = self.__ET.SubElement(self.__suite, 'testcase') | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         self.__start_time = time.perf_counter() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def _add_result(self, test, capture=False, **args): | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |         if not self.USE_XML: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         e = self.__e | 
					
						
							|  |  |  |         self.__e = None | 
					
						
							|  |  |  |         if e is None: | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |         ET = self.__ET | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         e.set('name', args.pop('name', self.__getId(test))) | 
					
						
							|  |  |  |         e.set('status', args.pop('status', 'run')) | 
					
						
							|  |  |  |         e.set('result', args.pop('result', 'completed')) | 
					
						
							|  |  |  |         if self.__start_time: | 
					
						
							|  |  |  |             e.set('time', f'{time.perf_counter() - self.__start_time:0.6f}') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if capture: | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  |             if self._stdout_buffer is not None: | 
					
						
							|  |  |  |                 stdout = self._stdout_buffer.getvalue().rstrip() | 
					
						
							|  |  |  |                 ET.SubElement(e, 'system-out').text = stdout | 
					
						
							|  |  |  |             if self._stderr_buffer is not None: | 
					
						
							|  |  |  |                 stderr = self._stderr_buffer.getvalue().rstrip() | 
					
						
							|  |  |  |                 ET.SubElement(e, 'system-err').text = stderr | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for k, v in args.items(): | 
					
						
							|  |  |  |             if not k or not v: | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             e2 = ET.SubElement(e, k) | 
					
						
							|  |  |  |             if hasattr(v, 'items'): | 
					
						
							|  |  |  |                 for k2, v2 in v.items(): | 
					
						
							|  |  |  |                     if k2: | 
					
						
							|  |  |  |                         e2.set(k2, str(v2)) | 
					
						
							|  |  |  |                     else: | 
					
						
							|  |  |  |                         e2.text = str(v2) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 e2.text = str(v) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     @classmethod | 
					
						
							|  |  |  |     def __makeErrorDict(cls, err_type, err_value, err_tb): | 
					
						
							|  |  |  |         if isinstance(err_type, type): | 
					
						
							|  |  |  |             if err_type.__module__ == 'builtins': | 
					
						
							|  |  |  |                 typename = err_type.__name__ | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 typename = f'{err_type.__module__}.{err_type.__name__}' | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             typename = repr(err_type) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         msg = traceback.format_exception(err_type, err_value, None) | 
					
						
							|  |  |  |         tb = traceback.format_exception(err_type, err_value, err_tb) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return { | 
					
						
							|  |  |  |             'type': typename, | 
					
						
							|  |  |  |             'message': ''.join(msg), | 
					
						
							|  |  |  |             '': ''.join(tb), | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addError(self, test, err): | 
					
						
							|  |  |  |         self._add_result(test, True, error=self.__makeErrorDict(*err)) | 
					
						
							|  |  |  |         super().addError(test, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addExpectedFailure(self, test, err): | 
					
						
							|  |  |  |         self._add_result(test, True, output=self.__makeErrorDict(*err)) | 
					
						
							|  |  |  |         super().addExpectedFailure(test, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addFailure(self, test, err): | 
					
						
							|  |  |  |         self._add_result(test, True, failure=self.__makeErrorDict(*err)) | 
					
						
							|  |  |  |         super().addFailure(test, err) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addSkip(self, test, reason): | 
					
						
							|  |  |  |         self._add_result(test, skipped=reason) | 
					
						
							|  |  |  |         super().addSkip(test, reason) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addSuccess(self, test): | 
					
						
							|  |  |  |         self._add_result(test) | 
					
						
							|  |  |  |         super().addSuccess(test) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def addUnexpectedSuccess(self, test): | 
					
						
							|  |  |  |         self._add_result(test, outcome='UNEXPECTED_SUCCESS') | 
					
						
							|  |  |  |         super().addUnexpectedSuccess(test) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_xml_element(self): | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |         if not self.USE_XML: | 
					
						
							|  |  |  |             raise ValueError("USE_XML is false") | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         e = self.__suite | 
					
						
							|  |  |  |         e.set('tests', str(self.testsRun)) | 
					
						
							|  |  |  |         e.set('errors', str(len(self.errors))) | 
					
						
							|  |  |  |         e.set('failures', str(len(self.failures))) | 
					
						
							|  |  |  |         return e | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | class QuietRegressionTestRunner: | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  |     def __init__(self, stream, buffer=False): | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |         self.result = RegressionTestResult(stream, None, 0) | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  |         self.result.buffer = buffer | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     def run(self, test): | 
					
						
							|  |  |  |         test(self.result) | 
					
						
							|  |  |  |         return self.result | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  | def get_test_runner_class(verbosity, buffer=False): | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |     if verbosity: | 
					
						
							|  |  |  |         return functools.partial(unittest.TextTestRunner, | 
					
						
							|  |  |  |                                  resultclass=RegressionTestResult, | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  |                                  buffer=buffer, | 
					
						
							| 
									
										
										
										
											2022-04-03 15:33:28 -04:00
										 |  |  |                                  verbosity=verbosity) | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  |     return functools.partial(QuietRegressionTestRunner, buffer=buffer) | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-10-29 16:09:41 -04:00
										 |  |  | def get_test_runner(stream, verbosity, capture_output=False): | 
					
						
							|  |  |  |     return get_test_runner_class(verbosity, capture_output)(stream) | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  | if __name__ == '__main__': | 
					
						
							| 
									
										
										
										
											2021-03-23 01:11:31 +01:00
										 |  |  |     import xml.etree.ElementTree as ET | 
					
						
							|  |  |  |     RegressionTestResult.USE_XML = True | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |     class TestTests(unittest.TestCase): | 
					
						
							|  |  |  |         def test_pass(self): | 
					
						
							|  |  |  |             pass | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def test_pass_slow(self): | 
					
						
							|  |  |  |             time.sleep(1.0) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def test_fail(self): | 
					
						
							|  |  |  |             print('stdout', file=sys.stdout) | 
					
						
							|  |  |  |             print('stderr', file=sys.stderr) | 
					
						
							|  |  |  |             self.fail('failure message') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         def test_error(self): | 
					
						
							|  |  |  |             print('stdout', file=sys.stdout) | 
					
						
							|  |  |  |             print('stderr', file=sys.stderr) | 
					
						
							|  |  |  |             raise RuntimeError('error message') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     suite = unittest.TestSuite() | 
					
						
							| 
									
										
										
										
											2021-09-13 10:49:53 +03:00
										 |  |  |     suite.addTest(unittest.TestLoader().loadTestsFromTestCase(TestTests)) | 
					
						
							| 
									
										
										
										
											2018-09-18 09:10:26 -07:00
										 |  |  |     stream = io.StringIO() | 
					
						
							|  |  |  |     runner_cls = get_test_runner_class(sum(a == '-v' for a in sys.argv)) | 
					
						
							|  |  |  |     runner = runner_cls(sys.stdout) | 
					
						
							|  |  |  |     result = runner.run(suite) | 
					
						
							|  |  |  |     print('Output:', stream.getvalue()) | 
					
						
							|  |  |  |     print('XML: ', end='') | 
					
						
							|  |  |  |     for s in ET.tostringlist(result.get_xml_element()): | 
					
						
							|  |  |  |         print(s.decode(), end='') | 
					
						
							|  |  |  |     print() |