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
« 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
10sys.path.append(os.path.abspath(os.path.join(__file__,'../../../')))
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
19_logger = logging.getLogger('odoo.tests.test_module_operations')
21BLACKLIST = {
22 'auth_ldap',
23 'pos_blackbox_be',
24}
25IGNORE = ('hw_', 'theme_', 'l10n_', 'test_')
27INSTALL_BLACKLIST = {
28 'payment_alipay', 'payment_payulatam', 'payment_payumoney',
29} # deprecated modules (cannot be installed manually through button_install anymore)
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)
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)
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)
54def addons_path(value):
55 return config._check_addons_path(config.options_index['init'], '-i', value)
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()
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")
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)
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 )
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.")
114 return parser.parse_args()
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)
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)
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, {})
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 )
139 modules = env['ir.module.module'].search([]).filtered(valid)
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]
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
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)
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
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)
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)
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 ))
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)
205 _logger.info("%d standalone scripts executed in %.2fs", len(funcs), time.time() - start_time)
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()
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()
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 })
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)