gooderp18绿色标准版
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

280 line
10KB

  1. """Test case implementation"""
  2. import contextlib
  3. import inspect
  4. import logging
  5. import sys
  6. from pathlib import PurePath
  7. from unittest import SkipTest
  8. from unittest import TestCase as _TestCase
  9. _logger = logging.getLogger(__name__)
  10. __unittest = True
  11. _subtest_msg_sentinel = object()
  12. class _Outcome(object):
  13. def __init__(self, test, result):
  14. self.result = result
  15. self.success = True
  16. self.test = test
  17. @contextlib.contextmanager
  18. def testPartExecutor(self, test_case, isTest=False):
  19. try:
  20. yield
  21. except KeyboardInterrupt:
  22. raise
  23. except SkipTest as e:
  24. self.success = False
  25. self.result.addSkip(test_case, str(e))
  26. except: # pylint: disable=bare-except
  27. exc_info = sys.exc_info()
  28. self.success = False
  29. if exc_info is not None:
  30. exception_type, exception, tb = exc_info
  31. tb = self._complete_traceback(tb)
  32. exc_info = (exception_type, exception, tb)
  33. self.test._addError(self.result, test_case, exc_info)
  34. # explicitly break a reference cycle:
  35. # exc_info -> frame -> exc_info
  36. exc_info = None
  37. def _complete_traceback(self, initial_tb):
  38. Traceback = type(initial_tb)
  39. # make the set of frames in the traceback
  40. tb_frames = set()
  41. tb = initial_tb
  42. while tb:
  43. tb_frames.add(tb.tb_frame)
  44. tb = tb.tb_next
  45. tb = initial_tb
  46. # find the common frame by searching the last frame of the current_stack present in the traceback.
  47. current_frame = inspect.currentframe()
  48. common_frame = None
  49. while current_frame:
  50. if current_frame in tb_frames:
  51. common_frame = current_frame # we want to find the last frame in common
  52. current_frame = current_frame.f_back
  53. if not common_frame: # not really useful but safer
  54. _logger.warning('No common frame found with current stack, displaying full stack')
  55. tb = initial_tb
  56. else:
  57. # remove the tb_frames until the common_frame is reached (keep the current_frame tb since the line is more accurate)
  58. while tb and tb.tb_frame != common_frame:
  59. tb = tb.tb_next
  60. # add all current frame elements under the common_frame to tb
  61. current_frame = common_frame.f_back
  62. while current_frame:
  63. tb = Traceback(tb, current_frame, current_frame.f_lasti, current_frame.f_lineno)
  64. current_frame = current_frame.f_back
  65. # remove traceback root part (odoo_bin, main, loading, ...), as
  66. # everything under the testCase is not useful. Using '_callTestMethod',
  67. # '_callSetUp', '_callTearDown', '_callCleanup' instead of the test
  68. # method since the error does not comme especially from the test method.
  69. while tb:
  70. code = tb.tb_frame.f_code
  71. if PurePath(code.co_filename).name == 'case.py' and code.co_name in ('_callTestMethod', '_callSetUp', '_callTearDown', '_callCleanup'):
  72. return tb.tb_next
  73. tb = tb.tb_next
  74. _logger.warning('No root frame found, displaying full stacks')
  75. return initial_tb # this shouldn't be reached
  76. class TestCase(_TestCase):
  77. _class_cleanups = [] # needed, backport for versions < 3.8
  78. __unittest_skip__ = False
  79. __unittest_skip_why__ = ''
  80. _moduleSetUpFailed = False
  81. # pylint: disable=super-init-not-called
  82. def __init__(self, methodName='runTest'):
  83. """Create an instance of the class that will use the named test
  84. method when executed. Raises a ValueError if the instance does
  85. not have a method with the specified name.
  86. """
  87. self._testMethodName = methodName
  88. self._outcome = None
  89. if methodName != 'runTest' and not hasattr(self, methodName):
  90. # we allow instantiation with no explicit method name
  91. # but not an *incorrect* or missing method name
  92. raise ValueError("no such test method in %s: %s" %
  93. (self.__class__, methodName))
  94. self._cleanups = []
  95. self._subtest = None
  96. # Map types to custom assertEqual functions that will compare
  97. # instances of said type in more detail to generate a more useful
  98. # error message.
  99. self._type_equality_funcs = {}
  100. self.addTypeEqualityFunc(dict, 'assertDictEqual')
  101. self.addTypeEqualityFunc(list, 'assertListEqual')
  102. self.addTypeEqualityFunc(tuple, 'assertTupleEqual')
  103. self.addTypeEqualityFunc(set, 'assertSetEqual')
  104. self.addTypeEqualityFunc(frozenset, 'assertSetEqual')
  105. self.addTypeEqualityFunc(str, 'assertMultiLineEqual')
  106. def addCleanup(self, function, *args, **kwargs):
  107. """Add a function, with arguments, to be called when the test is
  108. completed. Functions added are called on a LIFO basis and are
  109. called after tearDown on test failure or success.
  110. Cleanup items are called even if setUp fails (unlike tearDown)."""
  111. self._cleanups.append((function, args, kwargs))
  112. @classmethod
  113. def addClassCleanup(cls, function, *args, **kwargs):
  114. """Same as addCleanup, except the cleanup items are called even if
  115. setUpClass fails (unlike tearDownClass)."""
  116. cls._class_cleanups.append((function, args, kwargs))
  117. def shortDescription(self):
  118. return None
  119. @contextlib.contextmanager
  120. def subTest(self, msg=_subtest_msg_sentinel, **params):
  121. """Return a context manager that will return the enclosed block
  122. of code in a subtest identified by the optional message and
  123. keyword parameters. A failure in the subtest marks the test
  124. case as failed but resumes execution at the end of the enclosed
  125. block, allowing further test code to be executed.
  126. """
  127. parent = self._subtest
  128. if parent:
  129. params = {**params, **{k: v for k, v in parent.params.items() if k not in params}}
  130. self._subtest = _SubTest(self, msg, params)
  131. try:
  132. with self._outcome.testPartExecutor(self._subtest, isTest=True):
  133. yield
  134. finally:
  135. self._subtest = parent
  136. def _addError(self, result, test, exc_info):
  137. """
  138. This method is similar to feed_errors_to_result in python<=3.10
  139. but only manage one error at a time
  140. This is also inspired from python 3.11 _addError but still manages
  141. subtests errors as in python 3.7-3.10 for minimal changes.
  142. The method remains on the test to easily override it in test_test_suite
  143. """
  144. if isinstance(test, _SubTest):
  145. result.addSubTest(test.test_case, test, exc_info)
  146. elif exc_info is not None:
  147. if issubclass(exc_info[0], self.failureException):
  148. result.addFailure(test, exc_info)
  149. else:
  150. result.addError(test, exc_info)
  151. def _callSetUp(self):
  152. self.setUp()
  153. def _callTestMethod(self, method):
  154. method()
  155. def _callTearDown(self):
  156. self.tearDown()
  157. def _callCleanup(self, function, *args, **kwargs):
  158. function(*args, **kwargs)
  159. def run(self, result):
  160. result.startTest(self)
  161. testMethod = getattr(self, self._testMethodName)
  162. skip = False
  163. skip_why = ''
  164. try:
  165. skip = self.__class__.__unittest_skip__ or testMethod.__unittest_skip__
  166. skip_why = self.__class__.__unittest_skip_why__ or testMethod.__unittest_skip_why__ or ''
  167. except AttributeError: # testMethod may not have a __unittest_skip__ or __unittest_skip_why__
  168. pass
  169. if skip:
  170. result.addSkip(self, skip_why)
  171. result.stopTest(self)
  172. return
  173. outcome = _Outcome(self, result)
  174. try:
  175. self._outcome = outcome
  176. with outcome.testPartExecutor(self):
  177. self._callSetUp()
  178. if outcome.success:
  179. with outcome.testPartExecutor(self, isTest=True):
  180. self._callTestMethod(testMethod)
  181. with outcome.testPartExecutor(self):
  182. self._callTearDown()
  183. self.doCleanups()
  184. if outcome.success:
  185. result.addSuccess(self)
  186. return result
  187. finally:
  188. result.stopTest(self)
  189. # clear the outcome, no more needed
  190. self._outcome = None
  191. def doCleanups(self):
  192. """Execute all cleanup functions. Normally called for you after
  193. tearDown."""
  194. while self._cleanups:
  195. function, args, kwargs = self._cleanups.pop()
  196. with self._outcome.testPartExecutor(self):
  197. self._callCleanup(function, *args, **kwargs)
  198. @classmethod
  199. def doClassCleanups(cls):
  200. """Execute all class cleanup functions. Normally called for you after
  201. tearDownClass."""
  202. cls.tearDown_exceptions = []
  203. while cls._class_cleanups:
  204. function, args, kwargs = cls._class_cleanups.pop()
  205. try:
  206. function(*args, **kwargs)
  207. except Exception:
  208. cls.tearDown_exceptions.append(sys.exc_info())
  209. class _SubTest(TestCase):
  210. def __init__(self, test_case, message, params):
  211. super().__init__()
  212. self._message = message
  213. self.test_case = test_case
  214. self.params = params
  215. self.failureException = test_case.failureException
  216. def runTest(self):
  217. raise NotImplementedError("subtests cannot be run directly")
  218. def _subDescription(self):
  219. parts = []
  220. if self._message is not _subtest_msg_sentinel:
  221. parts.append("[{}]".format(self._message))
  222. if self.params:
  223. params_desc = ', '.join(
  224. "{}={!r}".format(k, v)
  225. for (k, v) in self.params.items())
  226. parts.append("({})".format(params_desc))
  227. return " ".join(parts) or '(<subtest>)'
  228. def id(self):
  229. return "{} {}".format(self.test_case.id(), self._subDescription())
  230. def __str__(self):
  231. return "{} {}".format(self.test_case, self._subDescription())
上海开阖软件有限公司 沪ICP备12045867号-1