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

1""" 

2Vendor unittest.TestSuite 

3 

4This is a modified version of python 3.8 unitest.TestSuite 

5 

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 

10 

11- Removes expected failure support 

12- Removes module setUp/tearDown support 

13 

14""" 

15 

16import logging 

17import sys 

18 

19import odoo 

20from . import case 

21from .common import HttpCase 

22from .result import stats_logger 

23from unittest import util, BaseTestSuite, TestCase 

24 

25__unittest = True 

26 

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 """ 

35 

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__ 

45 

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) 

48 

49 self._tearDownPreviousClass(None, result) 

50 return result 

51 

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 

61 

62 currentClass._classSetupFailed = False 

63 

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) 

80 

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) 

92 

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) 

120 

121 

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 

130 

131 # attribute used by TestResult._exc_info_to_string 

132 failureException = None 

133 

134 def __init__(self, description): 

135 self.description = description 

136 

137 def id(self): 

138 return self.description 

139 

140 def shortDescription(self): 

141 return None 

142 

143 def __repr__(self): 

144 return "<ErrorHolder description=%r>" % (self.description,) 

145 

146 def __str__(self): 

147 return self.id() 

148 

149 def run(self, result): 

150 # could call result.addError(...) - but this test-like object 

151 # shouldn't be run anyway 

152 pass 

153 

154 def __call__(self, result): 

155 return self.run(result) 

156 

157 def countTestCases(self): 

158 return 0 

159 

160 

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 

171 

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) 

176 

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 

187 

188 test_id = f'{previous_test_class.__module__}.{previous_test_class.__qualname__}.tearDownClass' 

189 with result.collectStats(test_id): 

190 super()._tearDownPreviousClass(test, result) 

191 

192 def has_http_case(self): 

193 return self.countTestCases() and any(isinstance(test_case, HttpCase) for test_case in self)