suite.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. """
  2. Vendor unittest.TestSuite
  3. This is a modified version of python 3.8 unitest.TestSuite
  4. Odoo tests customisation combined with the need of a cross version compatibility
  5. started to make TestSuite and other unitest object more complicated than vendoring
  6. the part we need for Odoo. This versions is simplified in order
  7. to minimise the code to maintain
  8. - Removes expected failure support
  9. - Removes module setUp/tearDown support
  10. """
  11. import logging
  12. import sys
  13. from . import case
  14. from .common import HttpCase
  15. from .result import stats_logger
  16. from unittest import util, BaseTestSuite, TestCase
  17. __unittest = True
  18. class TestSuite(BaseTestSuite):
  19. """A test suite is a composite test consisting of a number of TestCases.
  20. For use, create an instance of TestSuite, then add test case instances.
  21. When all tests have been added, the suite can be passed to a test
  22. runner, such as TextTestRunner. It will run the individual test cases
  23. in the order in which they were added, aggregating the results. When
  24. subclassing, do not forget to call the base class constructor.
  25. """
  26. def run(self, result, debug=False):
  27. for test in self:
  28. assert isinstance(test, (TestCase))
  29. self._tearDownPreviousClass(test, result)
  30. self._handleClassSetUp(test, result)
  31. result._previousTestClass = test.__class__
  32. if not test.__class__._classSetupFailed:
  33. test(result)
  34. self._tearDownPreviousClass(None, result)
  35. return result
  36. def _handleClassSetUp(self, test, result):
  37. previousClass = result._previousTestClass
  38. currentClass = test.__class__
  39. if currentClass == previousClass:
  40. return
  41. if result._moduleSetUpFailed:
  42. return
  43. if getattr(currentClass, "__unittest_skip__", False):
  44. return
  45. currentClass._classSetupFailed = False
  46. try:
  47. currentClass.setUpClass()
  48. except Exception as e:
  49. currentClass._classSetupFailed = True
  50. className = util.strclass(currentClass)
  51. self._createClassOrModuleLevelException(result, e,
  52. 'setUpClass',
  53. className)
  54. finally:
  55. if currentClass._classSetupFailed is True:
  56. currentClass.doClassCleanups()
  57. if len(currentClass.tearDown_exceptions) > 0:
  58. for exc in currentClass.tearDown_exceptions:
  59. self._createClassOrModuleLevelException(
  60. result, exc[1], 'setUpClass', className,
  61. info=exc)
  62. def _createClassOrModuleLevelException(self, result, exception, method_name,
  63. parent, info=None):
  64. errorName = f'{method_name} ({parent})'
  65. error = _ErrorHolder(errorName)
  66. if isinstance(exception, case.SkipTest):
  67. result.addSkip(error, str(exception))
  68. else:
  69. if not info:
  70. result.addError(error, sys.exc_info())
  71. else:
  72. result.addError(error, info)
  73. def _tearDownPreviousClass(self, test, result):
  74. previousClass = result._previousTestClass
  75. currentClass = test.__class__
  76. if currentClass == previousClass:
  77. return
  78. if not previousClass:
  79. return
  80. if previousClass._classSetupFailed:
  81. return
  82. if getattr(previousClass, "__unittest_skip__", False):
  83. return
  84. try:
  85. previousClass.tearDownClass()
  86. except Exception as e:
  87. className = util.strclass(previousClass)
  88. self._createClassOrModuleLevelException(result, e,
  89. 'tearDownClass',
  90. className)
  91. finally:
  92. previousClass.doClassCleanups()
  93. if len(previousClass.tearDown_exceptions) > 0:
  94. for exc in previousClass.tearDown_exceptions:
  95. className = util.strclass(previousClass)
  96. self._createClassOrModuleLevelException(result, exc[1],
  97. 'tearDownClass',
  98. className,
  99. info=exc)
  100. class _ErrorHolder(object):
  101. """
  102. Placeholder for a TestCase inside a result. As far as a TestResult
  103. is concerned, this looks exactly like a unit test. Used to insert
  104. arbitrary errors into a test suite run.
  105. """
  106. # Inspired by the ErrorHolder from Twisted:
  107. # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
  108. # attribute used by TestResult._exc_info_to_string
  109. failureException = None
  110. def __init__(self, description):
  111. self.description = description
  112. def id(self):
  113. return self.description
  114. def shortDescription(self):
  115. return None
  116. def __repr__(self):
  117. return "<ErrorHolder description=%r>" % (self.description,)
  118. def __str__(self):
  119. return self.id()
  120. def run(self, result):
  121. # could call result.addError(...) - but this test-like object
  122. # shouldn't be run anyway
  123. pass
  124. def __call__(self, result):
  125. return self.run(result)
  126. def countTestCases(self):
  127. return 0
  128. class OdooSuite(TestSuite):
  129. def _handleClassSetUp(self, test, result):
  130. previous_test_class = result._previousTestClass
  131. if not (
  132. previous_test_class != type(test)
  133. and hasattr(result, 'stats')
  134. and stats_logger.isEnabledFor(logging.INFO)
  135. ):
  136. super()._handleClassSetUp(test, result)
  137. return
  138. test_class = type(test)
  139. test_id = f'{test_class.__module__}.{test_class.__qualname__}.setUpClass'
  140. with result.collectStats(test_id):
  141. super()._handleClassSetUp(test, result)
  142. def _tearDownPreviousClass(self, test, result):
  143. previous_test_class = result._previousTestClass
  144. if not (
  145. previous_test_class
  146. and previous_test_class != type(test)
  147. and hasattr(result, 'stats')
  148. and stats_logger.isEnabledFor(logging.INFO)
  149. ):
  150. super()._tearDownPreviousClass(test, result)
  151. return
  152. test_id = f'{previous_test_class.__module__}.{previous_test_class.__qualname__}.tearDownClass'
  153. with result.collectStats(test_id):
  154. super()._tearDownPreviousClass(test, result)
  155. def has_http_case(self):
  156. return self.countTestCases() and any(isinstance(test_case, HttpCase) for test_case in self)