Coverage for adhoc-cicd-odoo-odoo / odoo / tests / suite.py: 65%
110 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:15 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-09 18:15 +0000
1"""
2Vendor unittest.TestSuite
4This is a modified version of python 3.8 unitest.TestSuite
6Odoo tests customisation combined with the need of a cross version compatibility
7started to make TestSuite and other unitest object more complicated than vendoring
8the part we need for Odoo. This versions is simplified in order
9to minimise the code to maintain
11- Removes expected failure support
12- Removes module setUp/tearDown support
14"""
16import logging
17import sys
19import odoo
20from . import case
21from .common import HttpCase
22from .result import stats_logger
23from unittest import util, BaseTestSuite, TestCase
25__unittest = True
27class TestSuite(BaseTestSuite):
28 """A test suite is a composite test consisting of a number of TestCases.
29 For use, create an instance of TestSuite, then add test case instances.
30 When all tests have been added, the suite can be passed to a test
31 runner, such as TextTestRunner. It will run the individual test cases
32 in the order in which they were added, aggregating the results. When
33 subclassing, do not forget to call the base class constructor.
34 """
36 def run(self, result, debug=False):
37 for test in self:
38 if result.shouldStop: 38 ↛ 39line 38 didn't jump to line 39 because the condition on line 38 was never true
39 break
40 assert isinstance(test, (TestCase))
41 odoo.modules.module.current_test = test
42 self._tearDownPreviousClass(test, result)
43 self._handleClassSetUp(test, result)
44 result._previousTestClass = test.__class__
46 if not test.__class__._classSetupFailed: 46 ↛ 37line 46 didn't jump to line 37 because the condition on line 46 was always true
47 test(result)
49 self._tearDownPreviousClass(None, result)
50 return result
52 def _handleClassSetUp(self, test, result):
53 previousClass = result._previousTestClass
54 currentClass = test.__class__
55 if currentClass == previousClass:
56 return
57 if result._moduleSetUpFailed: 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true
58 return
59 if currentClass.__unittest_skip__: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 return
62 currentClass._classSetupFailed = False
64 try:
65 currentClass.setUpClass()
66 except Exception as e:
67 currentClass._classSetupFailed = True
68 className = util.strclass(currentClass)
69 self._createClassOrModuleLevelException(result, e,
70 'setUpClass',
71 className)
72 finally:
73 if currentClass._classSetupFailed is True: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 currentClass.doClassCleanups()
75 if len(currentClass.tearDown_exceptions) > 0:
76 for exc in currentClass.tearDown_exceptions:
77 self._createClassOrModuleLevelException(
78 result, exc[1], 'setUpClass', className,
79 info=exc)
81 def _createClassOrModuleLevelException(self, result, exception, method_name,
82 parent, info=None):
83 errorName = f'{method_name} ({parent})'
84 error = _ErrorHolder(errorName)
85 if isinstance(exception, case.SkipTest):
86 result.addSkip(error, str(exception))
87 else:
88 if not info:
89 result.addError(error, sys.exc_info())
90 else:
91 result.addError(error, info)
93 def _tearDownPreviousClass(self, test, result):
94 previousClass = result._previousTestClass
95 currentClass = test.__class__
96 if currentClass == previousClass:
97 return
98 if not previousClass:
99 return
100 if previousClass._classSetupFailed: 100 ↛ 101line 100 didn't jump to line 101 because the condition on line 100 was never true
101 return
102 if previousClass.__unittest_skip__: 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true
103 return
104 try:
105 previousClass.tearDownClass()
106 except Exception as e:
107 className = util.strclass(previousClass)
108 self._createClassOrModuleLevelException(result, e,
109 'tearDownClass',
110 className)
111 finally:
112 previousClass.doClassCleanups()
113 if len(previousClass.tearDown_exceptions) > 0: 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true
114 for exc in previousClass.tearDown_exceptions:
115 className = util.strclass(previousClass)
116 self._createClassOrModuleLevelException(result, exc[1],
117 'tearDownClass',
118 className,
119 info=exc)
122class _ErrorHolder(object):
123 """
124 Placeholder for a TestCase inside a result. As far as a TestResult
125 is concerned, this looks exactly like a unit test. Used to insert
126 arbitrary errors into a test suite run.
127 """
128 # Inspired by the ErrorHolder from Twisted:
129 # http://twistedmatrix.com/trac/browser/trunk/twisted/trial/runner.py
131 # attribute used by TestResult._exc_info_to_string
132 failureException = None
134 def __init__(self, description):
135 self.description = description
137 def id(self):
138 return self.description
140 def shortDescription(self):
141 return None
143 def __repr__(self):
144 return "<ErrorHolder description=%r>" % (self.description,)
146 def __str__(self):
147 return self.id()
149 def run(self, result):
150 # could call result.addError(...) - but this test-like object
151 # shouldn't be run anyway
152 pass
154 def __call__(self, result):
155 return self.run(result)
157 def countTestCases(self):
158 return 0
161class OdooSuite(TestSuite):
162 def _handleClassSetUp(self, test, result):
163 previous_test_class = result._previousTestClass
164 if not (
165 previous_test_class != type(test)
166 and hasattr(result, 'stats')
167 and stats_logger.isEnabledFor(logging.INFO)
168 ):
169 super()._handleClassSetUp(test, result)
170 return
172 test_class = type(test)
173 test_id = f'{test_class.__module__}.{test_class.__qualname__}.setUpClass'
174 with result.collectStats(test_id):
175 super()._handleClassSetUp(test, result)
177 def _tearDownPreviousClass(self, test, result):
178 previous_test_class = result._previousTestClass
179 if not (
180 previous_test_class
181 and previous_test_class != type(test)
182 and hasattr(result, 'stats')
183 and stats_logger.isEnabledFor(logging.INFO)
184 ):
185 super()._tearDownPreviousClass(test, result)
186 return
188 test_id = f'{previous_test_class.__module__}.{previous_test_class.__qualname__}.tearDownClass'
189 with result.collectStats(test_id):
190 super()._tearDownPreviousClass(test, result)
192 def has_http_case(self):
193 return self.countTestCases() and any(isinstance(test_case, HttpCase) for test_case in self)