Coverage for adhoc-cicd-odoo-odoo / odoo / tests / test_module_operations.py: 20%

134 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-03-09 18:15 +0000

1#!/usr/bin/env python3 

2import argparse 

3import contextlib 

4import logging.config 

5import os 

6import sys 

7import threading 

8import time 

9 

10sys.path.append(os.path.abspath(os.path.join(__file__,'../../../'))) 

11 

12from odoo import api 

13from odoo.tools import config, topological_sort, unique, profiler 

14from odoo.modules.registry import Registry 

15from odoo.netsvc import init_logger 

16from odoo.tests import standalone_tests 

17import odoo.tests.loader 

18 

19_logger = logging.getLogger('odoo.tests.test_module_operations') 

20 

21BLACKLIST = { 

22 'auth_ldap', 

23 'pos_blackbox_be', 

24} 

25IGNORE = ('hw_', 'theme_', 'l10n_', 'test_') 

26 

27INSTALL_BLACKLIST = { 

28 'payment_alipay', 'payment_payulatam', 'payment_payumoney', 

29} # deprecated modules (cannot be installed manually through button_install anymore) 

30 

31 

32def install(db_name, module_id, module_name): 

33 with Registry(db_name).cursor() as cr: 

34 env = api.Environment(cr, api.SUPERUSER_ID, {}) 

35 module = env['ir.module.module'].browse(module_id) 

36 module.button_immediate_install() 

37 _logger.info('%s installed', module_name) 

38 

39 

40def uninstall(db_name, module_id, module_name): 

41 with Registry(db_name).cursor() as cr: 

42 env = api.Environment(cr, api.SUPERUSER_ID, {}) 

43 module = env['ir.module.module'].browse(module_id) 

44 module.button_immediate_uninstall() 

45 _logger.info('%s uninstalled', module_name) 

46 

47 

48def cycle(db_name, module_id, module_name): 

49 install(db_name, module_id, module_name) 

50 uninstall(db_name, module_id, module_name) 

51 install(db_name, module_id, module_name) 

52 

53 

54def addons_path(value): 

55 return config._check_addons_path(config.options_index['init'], '-i', value) 

56 

57 

58def parse_args(): 

59 parser = argparse.ArgumentParser( 

60 description="Script for testing the install / uninstall / reinstall" 

61 " cycle of Odoo modules. Prefer the 'cycle' subcommand to" 

62 " running this without anything specified (this is the" 

63 " default behaviour).") 

64 parser.set_defaults( 

65 func=test_cycle, 

66 reinstall=True, 

67 ) 

68 fake_commands = parser.add_mutually_exclusive_group() 

69 

70 parser.add_argument("--database", "-d", type=str, required=True, 

71 help="The database to test (/ run the command on)") 

72 parser.add_argument("--data-dir", "-D", dest="data_dir", type=str, 

73 help="Directory where to store Odoo data" 

74 ) 

75 parser.add_argument("--skip", "-s", type=str, 

76 help="Comma-separated list of modules to skip (they will only be installed)") 

77 parser.add_argument("--resume-at", "-r", type=str, 

78 help="Skip modules (only install) up to the specified one in topological order") 

79 parser.add_argument("--addons-path", "-p", type=addons_path, 

80 help="Comma-separated list of paths to directories containing extra Odoo modules") 

81 

82 cmds = parser.add_subparsers(title="subcommands", metavar='') 

83 cycle = cmds.add_parser( 

84 'cycle', help="Full install/uninstall/reinstall cycle.", 

85 description="Installs, uninstalls, and reinstalls all modules which are" 

86 " not skipped or blacklisted, the database should have" 

87 " 'base' installed (only).") 

88 cycle.set_defaults(func=test_cycle) 

89 

90 fake_commands.add_argument( 

91 "--uninstall", "-U", action=UninstallAction, 

92 help="Comma-separated list of modules to uninstall/reinstall. Prefer the 'uninstall' subcommand." 

93 ) 

94 uninstall = cmds.add_parser( 

95 'uninstall', help="Uninstallation", 

96 description="Uninstalls then (by default) reinstalls every specified " 

97 "module. Modules which are not installed before running " 

98 "are ignored.") 

99 uninstall.set_defaults(func=test_uninstall) 

100 uninstall.add_argument('uninstall', help="comma-separated list of modules to uninstall/reinstall") 

101 uninstall.add_argument( 

102 '-n', '--no-reinstall', dest='reinstall', action='store_false', 

103 help="Skips reinstalling the module(s) after uninstalling." 

104 ) 

105 

106 fake_commands.add_argument("--standalone", action=StandaloneAction, 

107 help="Launch standalone scripts tagged with @standalone. Accepts a list of " 

108 "module names or tags separated by commas. 'all' will run all available scripts. Prefer the 'standalone' subcommand." 

109 ) 

110 standalone = cmds.add_parser('standalone', help="Run scripts tagged with @standalone") 

111 standalone.set_defaults(func=test_standalone) 

112 standalone.add_argument('standalone', help="List of module names or tags separated by commas, 'all' will run all available scripts.") 

113 

114 return parser.parse_args() 

115 

116class UninstallAction(argparse.Action): 

117 def __call__(self, parser, namespace, values, option_string=None): 

118 namespace.func = test_uninstall 

119 setattr(namespace, self.dest, values) 

120 

121class StandaloneAction(argparse.Action): 

122 def __call__(self, parser, namespace, values, option_string=None): 

123 namespace.func = test_standalone 

124 setattr(namespace, self.dest, values) 

125 

126def test_cycle(args): 

127 """ Test full install/uninstall/reinstall cycle for all modules """ 

128 with Registry(args.database).cursor() as cr: 

129 env = odoo.api.Environment(cr, odoo.api.SUPERUSER_ID, {}) 

130 

131 def valid(module): 

132 return not ( 

133 module.name in BLACKLIST 

134 or module.name in INSTALL_BLACKLIST 

135 or module.name.startswith(IGNORE) 

136 or module.state in ('installed', 'uninstallable') 

137 ) 

138 

139 modules = env['ir.module.module'].search([]).filtered(valid) 

140 

141 # order modules in topological order 

142 modules = modules.browse(topological_sort({ 

143 module.id: module.dependencies_id.depend_id.ids 

144 for module in modules 

145 })) 

146 modules_todo = [(module.id, module.name) for module in modules] 

147 

148 resume = args.resume_at 

149 skip = set(args.skip.split(',')) if args.skip else set() 

150 for module_id, module_name in modules_todo: 

151 if module_name == resume: 

152 resume = None 

153 

154 if resume or module_name in skip: 

155 install(args.database, module_id, module_name) 

156 else: 

157 cycle(args.database, module_id, module_name) 

158 

159 

160def test_uninstall(args): 

161 """ Tries to uninstall/reinstall one ore more modules""" 

162 for module_name in args.uninstall.split(','): 

163 with Registry(args.database).cursor() as cr: 

164 env = odoo.api.Environment(cr, odoo.api.SUPERUSER_ID, {}) 

165 module = env['ir.module.module'].search([('name', '=', module_name)]) 

166 module_id, module_state = module.id, module.state 

167 

168 if module_state == 'installed': 

169 uninstall(args.database, module_id, module_name) 

170 if args.reinstall and module_name not in INSTALL_BLACKLIST: 

171 install(args.database, module_id, module_name) 

172 elif module_state: 

173 _logger.warning("Module %r is not installed", module_name) 

174 else: 

175 _logger.warning("Module %r does not exist", module_name) 

176 

177 

178def test_standalone(args): 

179 """ Tries to launch standalone scripts tagged with @post_testing """ 

180 odoo.service.db._check_faketime_mode(args.database) # noqa: SLF001 

181 # load the registry once for script discovery 

182 registry = Registry(args.database) 

183 for module_name in registry._init_modules: 

184 # import tests for loaded modules 

185 odoo.tests.loader.get_test_modules(module_name) 

186 

187 # fetch and filter scripts to test 

188 funcs = list(unique( 

189 func 

190 for tag in args.standalone.split(',') 

191 for func in standalone_tests[tag] 

192 )) 

193 

194 start_time = time.time() 

195 for index, func in enumerate(funcs, start=1): 

196 with Registry(args.database).cursor() as cr: 

197 env = odoo.api.Environment(cr, odoo.api.SUPERUSER_ID, {}) 

198 _logger.info("Executing standalone script: %s (%d / %d)", 

199 func.__name__, index, len(funcs)) 

200 try: 

201 func(env) 

202 except Exception: 

203 _logger.error("Standalone script %s failed", func.__name__, exc_info=True) 

204 

205 _logger.info("%d standalone scripts executed in %.2fs", len(funcs), time.time() - start_time) 

206 

207 

208if __name__ == '__main__': 208 ↛ 209line 208 didn't jump to line 209 because the condition on line 208 was never true

209 args = parse_args() 

210 

211 config['db_name'] = threading.current_thread().dbname = args.database 

212 # handle paths option 

213 if args.addons_path: 

214 config['addons_path'] = args.addons_path + config['addons_path'] 

215 if args.data_dir: 

216 config['data_dir'] = args.data_dir 

217 odoo.modules.module.initialize_sys_path() 

218 

219 init_logger() 

220 logging.config.dictConfig({ 

221 'version': 1, 

222 'incremental': True, 

223 'disable_existing_loggers': False, 

224 'loggers': { 

225 'odoo.modules.loading': {'level': 'CRITICAL'}, 

226 'odoo.sql_db': {'level': 'CRITICAL'}, 

227 'odoo.models.unlink': {'level': 'WARNING'}, 

228 'odoo.addons.base.models.ir_model': {'level': "WARNING"}, 

229 } 

230 }) 

231 

232 prof = contextlib.nullcontext() 

233 if os.environ.get('ODOO_PROFILE_PRELOAD'): 

234 interval = float(os.environ.get('ODOO_PROFILE_PRELOAD_INTERVAL', '0.1')) 

235 collectors = [profiler.PeriodicCollector(interval=interval)] 

236 if os.environ.get('ODOO_PROFILE_PRELOAD_SQL'): 

237 collectors.append('sql') 

238 prof = profiler.Profiler(db=args.database, collectors=collectors) 

239 try: 

240 with prof: 

241 args.func(args) 

242 except Exception: 

243 _logger.exception("%s tests failed", args.func.__name__[5:]) 

244 exit(1)