Files
odoo_source/odoo/tests/test_module_operations.py
T
Adrian Torres ca91e13dee [IMP] testing: forbid module operations during testing
With this commit, module operations such as install, upgrades and
uninstalls are henceforth forbidden inside unit tests and instead such
tests must be performed with standalone Odoo scripts.

This is done because module operations during tests are not
transactional, this can leave the registry in an unclean state and
further tests may be affected by this, it also creates a new registry
which complicates registry cleanup if anything crashes,
because the registry to be cleaned up is not the same one that crashed.

Instead, what should be done is a script that imports odoo as a library,
and loads the database necessary then performs whichever operations
necessary. This script should contain a single function with a single
parameter (env) and should be decorated with
@odoo.tests.common.standalone in order to be executed properly, this
decorator accepts any amount of positional parameters as tags that can
be specified when calling the script in order to execute only a select
subset of scripts.

Special tags are: 'all' and <module_name>, these are generated
automatically, the first will execute ALL scripts available whereas
<module_name> will execute all scripts introduced by said module.

When calling the test_module_operations script, only scripts found in
*installed* modules will be executed, script discovery is only possible
if the code is loaded therefore it is only possible if the module is
installed.

closes odoo/odoo#49669

Signed-off-by: Adrian Torres (adt) <adt@odoo.com>
2020-04-22 07:45:53 +00:00

164 lines
6.1 KiB
Python
Executable File

#!/usr/bin/env python3
import argparse
import logging
import os
import sys
import time
sys.path.append(os.path.abspath(os.path.join(__file__,'../../../')))
import odoo
from odoo.tools import topological_sort, unique
from odoo.netsvc import init_logger
from odoo.tests import standalone_tests
_logger = logging.getLogger('test_module_operations')
BLACKLIST = {
'auth_ldap', 'document_ftp', 'base_gengo', 'website_gengo', 'website_instantclick', 'pad',
'pad_project', 'note_pad', 'pos_cache', 'pos_blackbox_be', 'payment_test',
}
IGNORE = ('hw_', 'theme_', 'l10n_', 'test_', 'payment_')
def install(db_name, module_id, module_name):
with odoo.api.Environment.manage():
with odoo.registry(db_name).cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
module = env['ir.module.module'].browse(module_id)
module.button_immediate_install()
_logger.info('%s installed', module_name)
def uninstall(db_name, module_id, module_name):
with odoo.api.Environment.manage():
with odoo.registry(db_name).cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
module = env['ir.module.module'].browse(module_id)
module.button_immediate_uninstall()
_logger.info('%s uninstalled', module_name)
def cycle(db_name, module_id, module_name):
install(db_name, module_id, module_name)
uninstall(db_name, module_id, module_name)
install(db_name, module_id, module_name)
def parse_args():
parser = argparse.ArgumentParser(
description="Script for testing the install / uninstall / reinstall cycle of Odoo modules")
parser.add_argument("--database", "-d", type=str, required=True,
help="The database to test (note: must have only 'base' installed)")
parser.add_argument("--skip", "-s", type=str,
help="Comma-separated list of modules to skip (they will only be installed)")
parser.add_argument("--resume-at", "-r", type=str,
help="Skip modules (only install) up to the specified one in topological order")
parser.add_argument("--addons-path", "-p", type=str,
help="Comma-separated list of paths to directories containing extra Odoo modules")
parser.add_argument("--uninstall", "-U", type=str,
help="Comma-separated list of modules to uninstall/reinstall")
parser.add_argument("--standalone", type=str,
help="Launch standalone scripts tagged with @standalone. Accepts a list of "
"module names or tags separated by commas. 'all' will run all available scripts."
)
return parser.parse_args()
def test_full(args):
""" Test full install/uninstall/reinstall cycle for all modules """
with odoo.api.Environment.manage():
with odoo.registry(args.database).cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
def valid(module):
return not (
module.name in BLACKLIST
or module.name.startswith(IGNORE)
or module.state in ('installed', 'uninstallable')
)
modules = env['ir.module.module'].search([]).filtered(valid)
# order modules in topological order
modules = modules.browse(topological_sort({
module.id: module.dependencies_id.depend_id.ids
for module in modules
}))
modules_todo = [(module.id, module.name) for module in modules]
resume = args.resume_at
skip = set(args.skip.split(',')) if args.skip else set()
for module_id, module_name in modules_todo:
if module_name == resume:
resume = None
if resume or module_name in skip:
install(args.database, module_id, module_name)
else:
cycle(args.database, module_id, module_name)
def test_uninstall(args):
""" Tries to uninstall/reinstall one ore more modules"""
domain = [('name', 'in', args.uninstall.split(',')), ('state', '=', 'installed')]
with odoo.api.Environment.manage():
with odoo.registry(args.database).cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
modules = env['ir.module.module'].search(domain)
modules_todo = [(module.id, module.name) for module in modules]
for module_id, module_name in modules_todo:
uninstall(args.database, module_id, module_name)
install(args.database, module_id, module_name)
def test_scripts(args):
""" Tries to launch standalone scripts tagged with @post_testing """
# load the registry once for script discovery
registry = odoo.registry(args.database)
for module_name in registry._init_modules:
# import tests for loaded modules
odoo.modules.module.get_test_modules(module_name)
# fetch and filter scripts to test
funcs = list(unique(
func
for tag in args.standalone.split(',')
for func in standalone_tests[tag]
))
start_time = time.time()
for index, func in enumerate(funcs, start=1):
with odoo.api.Environment.manage():
with odoo.registry(args.database).cursor() as cr:
env = odoo.api.Environment(cr, odoo.SUPERUSER_ID, {})
_logger.info("Executing standalone script: %s (%d / %d)",
func.__name__, index, len(funcs))
try:
func(env)
except Exception:
_logger.error("Standalone script %s failed", func.__name__, exc_info=True)
_logger.info("%d standalone scripts executed in %.2fs" % (len(funcs), time.time() - start_time))
if __name__ == '__main__':
args = parse_args()
# handle paths option
if args.addons_path:
odoo.tools.config['addons_path'] = ','.join([args.addons_path, odoo.tools.config['addons_path']])
odoo.modules.module.initialize_sys_path()
init_logger()
logging.getLogger('odoo.modules.loading').setLevel(logging.CRITICAL)
logging.getLogger('odoo.sql_db').setLevel(logging.CRITICAL)
if args.uninstall:
test_uninstall(args)
elif args.standalone:
test_scripts(args)
else:
test_full(args)