# HG changeset patch # User Vincent Hatakeyama <vincent.hatakeyama@xcg-consulting.fr> # Date 1649161345 -7200 # Tue Apr 05 14:22:25 2022 +0200 # Node ID b165d05bd4f8f1e1506ced6792f281e4e1177929 # Parent a2407b37b97de71f765c6a5ff7669525173fbd12 Support marabunta that is in the images. Marabunta is not run when using do_tests. Change how the tests are run with do_tests: modules not to be tested are installed, then modules to test are installed with test enabled. This fix the issues of installation tests that were not run previously. diff --git a/NEWS.rst b/NEWS.rst --- a/NEWS.rst +++ b/NEWS.rst @@ -2,6 +2,15 @@ History ======= +16.0.0 +------ + +Support marabunta that is in the images. + +Marabunta is not run when using do_tests. + +Change how the tests are run with do_tests: modules not to be tested are installed, then modules to test are installed with test enabled. This fix the issues of installation tests that were not run previously. + 15.3.1 ------ diff --git a/odoo_scripts/config.py b/odoo_scripts/config.py --- a/odoo_scripts/config.py +++ b/odoo_scripts/config.py @@ -168,6 +168,7 @@ "db_password", "load-language", "duplicate_repo", + "marabunta_migration_file", ): setattr(self, key_format(key), section.get(key, None)) diff --git a/odoo_scripts/do_tests.py b/odoo_scripts/do_tests.py --- a/odoo_scripts/do_tests.py +++ b/odoo_scripts/do_tests.py @@ -22,24 +22,19 @@ _logger = logging.getLogger(__name__) -__version__ = "3.0.1" +__version__ = "4.0.0" __date__ = "2018-04-13" -__updated__ = "2022-01-27" +__updated__ = "2022-03-22" def main(argv=None): # IGNORE:C0111 """Parse arguments and docker build""" - program_version = __version__ - program_build_date = str(__updated__) - program_version_message = "%%(prog)s %s (%s)" % ( - program_version, - program_build_date, - ) - program_shortdesc = __doc__.split(".", maxsplit=1)[0] - program_license = """%s + program_version_message = f"%(prog)s {__version__} ({__updated__})" + program_short_description = __doc__.split(".", maxsplit=1)[0] + program_license = f"""{program_short_description} - Created by Vincent Hatakeyama on %s. - Copyright 2018, 2020 XCG Consulting. All rights reserved. + Created by Vincent Hatakeyama on {__date__}. + Copyright 2018, 2020, 2022 XCG Consulting. All rights reserved. Licensed under the MIT License @@ -47,10 +42,7 @@ or conditions of any kind, either express or implied. USAGE -""" % ( - program_shortdesc, - str(__date__), - ) +""" # the script assume it is launched from the super project project_path = os.path.realpath(".") @@ -139,7 +131,7 @@ ) parser.add_argument( "--load-language", - help="specifies the languages for the translations you want to be" " loaded", + help="specifies the languages for the translations you want to be loaded", default=None, dest="LOAD_LANGUAGE", ) @@ -197,6 +189,7 @@ extensions.append("unaccent") else: _logger.debug("No sample configuration %s", sample_conf) + # our base images have unaccent activated extensions.append("unaccent") if not odoo_db_user: @@ -228,6 +221,9 @@ "--no-flake8", "--no-isort", "--no-dev", + "--no-marabunta", + "--without-demo", + "", ] if odoo_db_user: args.append("--db_user") @@ -254,6 +250,30 @@ args.append("--chown") # also pass build options args.extend(get_build_options(nmspc)) + + module_list = ( + override_installed_module.split(",") + if override_installed_module + else config.module_list + ) + module_list_tests = ( + override_tested_module.split(",") + if override_tested_module + else config.module_list_tests + ) + + install_modules = ",".join( + module for module in module_list if module not in module_list_tests + ) + tested_non_installed_modules = [ + module for module in module_list_tests if module not in module_list + ] + if tested_non_installed_modules: + _logger.error( + "Modules to tests but not in module list: %s", + ", ".join(tested_non_installed_modules), + ) + if recreate_db: if start_postgresql: container, stop_method, socket_path = docker_run_postgresql( @@ -289,23 +309,25 @@ ) connection.commit() connection.close() - odoo_connection = connect( + with connect( user=pg_user, database="postgres", host=socket_path, port=5432 - ) - odoo_connection.autocommit = True - with odoo_connection.cursor() as cursor: - _logger.debug("Drop database %s", dbname) - cursor.execute('DROP DATABASE IF EXISTS "%s"' % dbname) - _logger.debug("Create database %s", dbname) - cursor.execute('CREATE DATABASE "%s" OWNER %s' % (dbname, odoo_db_user)) - odoo_connection = connect( + ) as odoo_connection: + odoo_connection.autocommit = True + with odoo_connection.cursor() as cursor: + _logger.debug("Drop database %s", dbname) + cursor.execute('DROP DATABASE IF EXISTS "%s"' % dbname) + _logger.debug("Create database %s", dbname) + cursor.execute( + 'CREATE DATABASE "%s" OWNER %s' % (dbname, odoo_db_user) + ) + with connect( user=pg_user, database=dbname, host=socket_path, port=5432 - ) - odoo_connection.autocommit = True - with odoo_connection.cursor() as cursor: - for extension in extensions: - _logger.info("Adding extension %s", extension) - cursor.execute("CREATE EXTENSION %s" % extension) + ) as odoo_connection: + odoo_connection.autocommit = True + with odoo_connection.cursor() as cursor: + for extension in extensions: + _logger.info("Adding extension %s", extension) + cursor.execute("CREATE EXTENSION %s" % extension) # TODO do we really need to stop the container each time? # that seems faster # stop_method() @@ -348,14 +370,12 @@ if result: return result - # TODO start odoo and detect if install fails + _logger.info("Installing modules %s in database %s", install_modules, dbname) + # install modules that are not to be tested if nmspc.docker: install_args = list(args) - if override_installed_module: - install_args.append("--install") - install_args.append(override_installed_module) - else: - install_args.append("--install-default") + install_args.append("--install") + install_args.append(install_modules) if languages: install_args.append("--load-language") install_args.append(languages) @@ -370,12 +390,10 @@ raise NotImplementedError # start odoo and detect if test fails if nmspc.docker: + module_list_tests = ",".join(module_list_tests) test_args = list(args) - if override_tested_module: - test_args.append("--test") - test_args.append(override_tested_module) - else: - test_args.append("--test-default") + test_args.append("--install-test") + test_args.append(module_list_tests) if test_log_level: test_args.append("--log-level") test_args.append(test_log_level) diff --git a/odoo_scripts/docker_black.py b/odoo_scripts/docker_black.py --- a/odoo_scripts/docker_black.py +++ b/odoo_scripts/docker_black.py @@ -4,6 +4,7 @@ import logging import os import sys +from argparse import ArgumentParser from .config import Config from .docker_build import add_build_options, build_local_image, get_build_options @@ -19,7 +20,7 @@ """Name of destination variable used for black in parsing""" -def __parser(): +def __parser() -> ArgumentParser: program_version_message = f"%(prog)s {__version__} ({__updated__})" program_short_description = __doc__.split(".", maxsplit=1)[0] program_license = f"""{program_short_description} diff --git a/odoo_scripts/docker_dev_start.py b/odoo_scripts/docker_dev_start.py --- a/odoo_scripts/docker_dev_start.py +++ b/odoo_scripts/docker_dev_start.py @@ -7,6 +7,7 @@ import os import pwd import sys +from argparse import ArgumentParser from configparser import ConfigParser from subprocess import call from typing import List @@ -39,24 +40,19 @@ _logger = logging.getLogger(__name__) -__version__ = "3.3.1" +__version__ = "3.4.0" __date__ = "2017-08-11" __updated__ = "2022-04-04" -def main(argv=None): # IGNORE:C0111 - """Parse arguments and launch conversion""" - program_version = __version__ - program_build_date = str(__updated__) - program_version_message = "%%(prog)s %s (%s)" % ( - program_version, - program_build_date, - ) +def __parser(project_name: str) -> ArgumentParser: + """Return a parser for docker_dev_start""" + program_version_message = f"%(prog)s {__version__} ({__updated__})" program_shortdesc = __doc__.split(".", maxsplit=1)[0] - program_license = """%s + program_license = f"""{program_shortdesc} - Created by Vincent Hatakeyama on %s. - Copyright 2017, 2018, 2019, 2020, 2021 XCG Consulting. All rights reserved. + Created by Vincent Hatakeyama on {__date__}. + Copyright 2017, 2018, 2019, 2020, 2021, 2022 XCG Consulting. All rights reserved. Licensed under the MIT License @@ -64,19 +60,7 @@ or conditions of any kind, either express or implied. USAGE -""" % ( - program_shortdesc, - str(__date__), - ) - - project_path = os.path.realpath(".") - project_name = os.path.basename(project_path) - if project_name == "odoo_scripts": - logging.fatal( - "You must run this script from the super project" - " (./odoo_scripts/docker_dev_start.py)" - ) - return 1 +""" # TODO add a way to store configuration options in a project file # Argument parsing @@ -121,6 +105,14 @@ default=None, ) group.add_argument( + "--install-test", + help="Modules to install for tests (will also set --log-level=test)" + " [default: %(default)s]\n" + "Options --update, --test, --install-test and --test-default" + " cannot be used at the same time", + default=None, + ) + group.add_argument( "-t", "--test", help="Modules to test (will also set --log-level=test)" @@ -299,8 +291,49 @@ dest="run_chown", ) + marabunta_group = parser.add_mutually_exclusive_group() + marabunta_group.add_argument( + "--no-marabunta", + help="Do not run marabunta", + action="store_false", + default=True, + dest="marabunta", + ) + marabunta_options_group = marabunta_group.add_argument_group() + marabunta_options_group.add_argument( + "--marabunta-db-user", + help="specify a db user for marabunta, this is useful if the odoo user is not " + "able to create databases or extensions", + ) + marabunta_options_group.add_argument( + "--marabunta-db-password", + help="specify a db password for marabunta", + ) + marabunta_options_group.add_argument( + "--marabunta-force-version", + help="marabunta force version argument (see marabunta documentation)", + ) + + return parser + + +def main(argv=None): # IGNORE:C0111 + """Parse arguments and launch conversion""" + project_path = os.path.realpath(".") + project_name = os.path.basename(project_path) + + # basic detection to avoid errors + if project_name == "odoo_scripts": + logging.fatal( + "You must run this script from the super project" + " (./odoo_scripts/docker_dev_start.py)" + ) + return 1 + # TODO detect that user is member of docker group + parser = __parser(project_name) + # TODO add a way to add options to docker # TODO add a way to add arg to odoo @@ -324,6 +357,10 @@ run_chown = nmspc.run_chown populate_model = nmspc.populate_model populate_size = nmspc.populate_size + marabunta = nmspc.marabunta + marabunta_db_user = nmspc.marabunta_db_user + marabunta_db_password = nmspc.marabunta_db_password + marabunta_force_version = nmspc.marabunta_force_version if restore_filename: if not database: @@ -370,8 +407,11 @@ if not use_host_network: options["ports"][8069] = 8069 mounts: List[Mount] = [] + tested_modules = None # TODO handle other version of odoo (base image need to be changed too) - coverage = (nmspc.test or nmspc.test_default) and odoo_type in ( + coverage = ( + nmspc.test or nmspc.test_default or nmspc.install_test + ) and odoo_type in ( ODOO_11, ODOO_13, ODOO_15, @@ -382,29 +422,34 @@ else ("coverage-start" if coverage else "start") ] if db_password: - arg.append("--db_password") - arg.append(db_password) + arg.extend(("--db_password", db_password)) if nmspc.update: - arg.extend(("-u", nmspc.update)) - arg.append("--i18n-overwrite") + arg.extend(("-u", nmspc.update, "--i18n-overwrite")) installing_or_updating = True - if nmspc.test or nmspc.test_default: + if nmspc.test or nmspc.test_default or nmspc.install_test: arg.append("--test-enable") if nmspc.test: + tested_modules = nmspc.test arg.extend(("-u", nmspc.test)) if odoo_type == ODOO_7: arg.append("--log-level=test") installing_or_updating = True - if nmspc.test or nmspc.stop_after_init: - arg.append("--stop-after-init") + if nmspc.install_test: + tested_modules = nmspc.install_test + arg.extend(("-i", nmspc.install_test)) + if odoo_type == "odoo7": + arg.append("--log-level=test") + installing_or_updating = True if nmspc.test_default: test_modules = c.module_list_tests - str_modules = ",".join(test_modules) - arg.extend(("-u", str_modules)) + tested_modules = ",".join(test_modules) + arg.extend(("-u", tested_modules)) if odoo_type == ODOO_7: arg.append("--log-level=test") arg.append("--stop-after-init") installing_or_updating = True + if tested_modules or nmspc.stop_after_init: + arg.append("--stop-after-init") if database: arg.extend(("-d", database)) if nmspc.install or nmspc.install_default: @@ -417,7 +462,7 @@ if modules_to_install: arg.append("-i") arg.append(",".join(modules_to_install)) - if nmspc.without_demo: + if nmspc.without_demo is not None: arg.append("--without-demo") arg.append(nmspc.without_demo) if nmspc.max_cron_threads: @@ -471,7 +516,7 @@ local_ip = addresses[0] except ImportError: _logger.warn( - "Consider installing python netifaces" " to ease local IP detection" + "Consider installing python netifaces to ease local IP detection" ) if not local_ip: import socket @@ -714,12 +759,13 @@ # --- restore --- # TODO find out why odoo_help would stop us from restoring if start_postgresql and not odoo_help and restore_filename: + _logger.info("Creating database %s for %s", database, user) + pg.exec_run(["createdb", "-U", user, database]) + if start_postgresql and not odoo_help and restore_filename: restore_basename = os.path.basename(restore_filename) _logger.info("Copying dump file in docker") inside_restore_filename = "/tmp/{}".format(restore_basename) DockerClient.put_file(restore_filename, pg, inside_restore_filename) - _logger.info("Creating database %s for %s", database, user) - pg.exec_run(["createdb", "-U", user, database]) _logger.info("Restoring database %s", database) restore, output = pg.exec_run( [ @@ -738,10 +784,18 @@ pg.exec_run(["rm", inside_restore_filename]) if not start_postgresql and not odoo_help and restore_filename: - _logger.info("Creating database %s", database) - createdb = call(["createdb", "-U", user, database]) - if not createdb: - return 17 + with connect( + user=user, + password=password, + database="postgres", + host=db_host, + port=dbport, + ) as connection: + connection.autocommit = True + _logger.info("Creating database %s", database) + with connection.cursor() as cursor: + cursor.execute(f'CREATE DATABASE "{database}"') + if not start_postgresql and not odoo_help and restore_filename: _logger.info("Restoring database %s", database) restore = call( ["pg_restore", "-U", user, "-O", "-d", database, restore_filename] @@ -874,14 +928,76 @@ # TODO when testing base modules, coverage will not be shown source_files = ",".join( [ - "{}/{}".format(target_module_directory, module) - for module in nmspc.test.split(",") + f"{target_module_directory}/{module}" + for module in tested_modules.split(",") ] ) options["environment"]["COVERAGE_RUN_OPTIONS"] = ( "--source=" + source_files + " --omit=*/__manifest__.py --branch" ) + if marabunta: + marabunta_migration_file = c.marabunta_migration_file + # if nothing is set, set the value to marabunta.yaml if there is such a file + if not marabunta_migration_file: + if os.path.exists("marabunta.yaml"): + marabunta_migration_file = "marabunta.yaml" + else: + marabunta_migration_file = None + + if marabunta_migration_file is not None: + marabunta_container_path = "/etc/marabunta.yaml" + # add marabunta file to mounts + mounts.append( + Mount( + marabunta_container_path, + os.path.join(project_path, marabunta_migration_file), + "bind", + read_only=True, + ) + ) + # also mount songs for anthem to avoid having to rebuild the image when they + # are changed. + if os.path.exists("songs"): + for root, dirs, _files in os.walk("songs"): + for dir in dirs: + # with older odoo this will not be set + for python in pythons: + mounts.append( + Mount( + f"/usr/local/lib/{python}/dist-packages/{root}/{dir}", + os.path.join(project_path, root, dir), + "bind", + read_only=True, + ) + ) + + options["environment"].update( + { + "MARABUNTA_MIGRATION_FILE": marabunta_container_path, + "MARABUNTA_MODE": "local", + "MARABUNTA_ALLOW_SERIE": "on", + } + ) + if start_postgresql: + options["environment"]["MARABUNTA_DB_USER"] = ( + marabunta_db_user or "postgres" + ) + elif marabunta_db_user: + # only set if provided a value, otherwise the image will get the value from + # command line arguments or the configuration file (using the same user os + # the one used in odoo) + options["environment"]["MARABUNTA_DB_USER"] = marabunta_db_user + # marabunta is run inside the container by the start command + else: + # Only set to avoid problems with image that inherit another image using + # marabunta while not using marabunta themselves + options["environment"]["MARABUNTA_MIGRATION_FILE"] = "" + if marabunta_force_version: + options["environment"]["MARABUNTA_FORCE_VERSION"] = marabunta_force_version + if marabunta_db_password: + options["environment"]["MARABUNTA_DB_PASSWORD"] = marabunta_db_password + interactive = True if interactive: options["stdin_open"] = True diff --git a/odoo_scripts/odoo_conf_inject_env_var.py b/odoo_scripts/odoo_conf_inject_env_var.py --- a/odoo_scripts/odoo_conf_inject_env_var.py +++ b/odoo_scripts/odoo_conf_inject_env_var.py @@ -27,10 +27,18 @@ _logger = logging.getLogger(__name__) +class TargetExistsException(Exception): + """Exception when the target already exists""" + + pass + + def inject_env_var(source_path: str, target_path: str, overwrite: bool = False): - """Inject environment variables into an Odoo configuration file.""" + """Inject environment variables into an Odoo configuration file. + :raises TargetExistsException: when the target already exists + """ if os.path.exists(target_path) and not overwrite: - raise Exception(f"{target_path} already exists") + raise TargetExistsException(f"{target_path} already exists") config_parser = ConfigParser() config_parser.read(source_path) with open(target_path, "w", encoding="UTF-8") as file: diff --git a/tests/test_odoo_conf_inject_env_var.py b/tests/test_odoo_conf_inject_env_var.py --- a/tests/test_odoo_conf_inject_env_var.py +++ b/tests/test_odoo_conf_inject_env_var.py @@ -4,7 +4,11 @@ import unittest from configparser import ConfigParser -from odoo_scripts.odoo_conf_inject_env_var import inject_env_var, main +from odoo_scripts.odoo_conf_inject_env_var import ( + TargetExistsException, + inject_env_var, + main, +) def clean_up_env(): @@ -88,7 +92,7 @@ new_conf = os.path.join(tmpdirname, "odoo.conf") with open(new_conf, "w", encoding="UTF-8") as file: file.write("\n") - with self.assertRaises(Exception): + with self.assertRaises(TargetExistsException): inject_env_var(self.conf_path, new_conf) def test_no_options_section(self):