diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index de5b507..36afff6 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -17,7 +17,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: ${{ vars.PYTHON_VERSION }} + python-version: "2.7" - name: Install build tools run: | diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index bef4582..9bfff45 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -4,9 +4,6 @@ on: release: types: [published] -env: - DISABLE_PYTEST_ODOO: 1 - jobs: deploy: runs-on: ubuntu-latest @@ -15,11 +12,11 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: ${{ vars.PYTHON_VERSION }} + python-version: "2.7" - name: Install build tools run: | - python -m pip install --upgrade pip pre-commit setuptools tox twine wheel + python -m pip install --upgrade build pip pre-commit setuptools tox twine wheel - name: Run pre-commit run: | @@ -33,8 +30,8 @@ jobs: env: TWINE_REPOSITORY: ${{ secrets.PYPI_REPOSITORY }} TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} + TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN_LEGACY }} run: | - python setup.py bdist_wheel + python -m build -w twine check dist/* twine upload dist/* diff --git a/.gitignore b/.gitignore index dace6c1..740643f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.egg-info *.log +*.pyc .coverage .pytest_cache .tox/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e37e221..4c87915 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,31 +1,23 @@ repos: - - repo: https://github.com/psf/black - rev: 22.3.0 - hooks: - - id: black - - repo: https://github.com/PyCQA/flake8 rev: 3.8.3 hooks: - id: flake8 - additional_dependencies: [flake8-bugbear] - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.8.0 + rev: v4.2.5 hooks: - id: isort name: isort args: - --settings=. - - repo: https://github.com/pre-commit/mirrors-pylint - rev: v2.7.4 + - repo: https://github.com/OCA/pylint-odoo + rev: 7.0.2 hooks: - - id: pylint + - id: pylint_odoo name: pylint args: - --rcfile=.pylintrc - --exit-zero verbose: true - additional_dependencies: &pylint_deps - - pylint-odoo==6.2.0 diff --git a/README.md b/README.md index 63dfb24..92dd382 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ as core component to: ## Installation -```$ pip3 install doblib``` +```$ pip install doblib-legacy``` ## Usage diff --git a/pyproject.toml b/pyproject.toml index b8c2911..60e3c27 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,5 +4,5 @@ build-backend = "setuptools.build_meta" [tool.black] line-length = 88 -target-version = ['py36', 'py37', 'py38', 'py39', 'py310'] +target-version = ['py27'] include = '(\.pyi?$|src|tests)' diff --git a/setup.cfg b/setup.cfg index 663f023..d7a4d1d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [metadata] -name = doblib +name = doblib_legacy version = attr: doblib.VERSION author = initOS GmbH author_email = info@initos.com @@ -13,23 +13,24 @@ classifiers = Environment :: Console License :: OSI Approved :: Apache Software License Operating System :: OS Independent - Programming Language :: Python :: 3 + Programming Language :: Python :: 2 [options] include_package_data = True package_dir = = src packages = find: -python_requires = >=3.6 +python_requires = <3.0 install_requires = - black + # black coverage debugpy flake8 git-aggregator ipython - isort>=4.3.10 - pylint_odoo + isort + mock + pylint_odoo<2.0 python-dateutil pytest-cov pytest-odoo diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..6068493 --- /dev/null +++ b/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/src/doblib/__init__.py b/src/doblib/__init__.py index 751e48b..993fb33 100644 --- a/src/doblib/__init__.py +++ b/src/doblib/__init__.py @@ -1 +1,3 @@ -VERSION = "0.16.1" +# -*- coding: utf-8 -*- + +VERSION = "0.17.0" diff --git a/src/doblib/__main__.py b/src/doblib/__main__.py index 16c39e6..c98b45e 100644 --- a/src/doblib/__main__.py +++ b/src/doblib/__main__.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # © 2021 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). @@ -8,10 +9,16 @@ from . import utils from .action import ActionEnvironment from .aggregate import AggregateEnvironment -from .ci import CI, CIEnvironment +from .ci import ( + CI, + CIEnvironment, +) from .env import Environment from .freeze import FreezeEnvironment -from .migrate import MigrateEnvironment, load_migrate_arguments +from .migrate import ( + MigrateEnvironment, + load_migrate_arguments, +) from .module import ModuleEnvironment from .run import RunEnvironment from .utils import config_logger @@ -60,23 +67,24 @@ def load_arguments(args): "command", metavar="command", nargs="?", - help=f"Command to use. Possible choices: " - f"a(ction): Execute pre-defined actions on the database\n" - f"c(onfig): Output the aggregated configuration or parts of it\n" - f"f(reeze): Freeze the packages and repositories\n" - f"g(enerate): Generate the Odoo configuration. This is also part " - f"of `init` and `update`\n" - f"i(nit): Initialize the repositories\n" - f"m(igrate): Run OpenUpgrade to migrate to a new Odoo version\n" - f"r(un): Run the Odoo server\n" - f"s(hell): Enter the interactive python shell\n" - f"t(est): Execute the unittests\n" - f"u(pdate): Run the update and migration process\n" - f"{', '.join(CI)}: Run the specific CI tool\n" - f"show-all-prs: show GitHub pull requests in merge sections. Such " - f"pull requests are identified as having a github.com remote and " - f"a refs/pull/NNN/head ref in the merge section\n" - f"show-closed-prs: show pull requests that are not open anymore", + help="Command to use. Possible choices: " + "a(ction): Execute pre-defined actions on the database\n" + "c(onfig): Output the aggregated configuration or parts of it\n" + "f(reeze): Freeze the packages and repositories\n" + "g(enerate): Generate the Odoo configuration. This is also part " + "of `init` and `update`\n" + "i(nit): Initialize the repositories\n" + "m(igrate): Run OpenUpgrade to migrate to a new Odoo version\n" + "r(un): Run the Odoo server\n" + "s(hell): Enter the interactive python shell\n" + "t(est): Execute the unittests\n" + "u(pdate): Run the update and migration process\n" + "{ci}: Run the specific CI tool\n" + "show-all-prs: show GitHub pull requests in merge sections. Such " + "pull requests are identified as having a github.com remote and " + "a refs/pull/NNN/head ref in the merge section\n" + "show-closed-prs: show pull requests that are not open anymore" + "".format(ci=', '.join(CI)), choices=sorted(choices + CI), ) base.add_argument( @@ -143,7 +151,7 @@ def main(args=None): elif args.command in ("m", "migrate"): # Run Odoo migration using OpenUpgrade migrate_args, left = load_migrate_arguments(left) - migrate_cfg = f"odoo.migrate.{migrate_args.version[0]}.yaml" + migrate_cfg = "odoo.migrate.{}.yaml".format(migrate_args.version[0]) sys.exit(MigrateEnvironment(migrate_cfg).migrate(migrate_args)) elif args.command in ("show-all-prs", "show-closed-prs"): sys.exit(AggregateEnvironment(args.cfg).aggregate(args.command, left)) diff --git a/src/doblib/action.py b/src/doblib/action.py index 52a20a1..6867d98 100644 --- a/src/doblib/action.py +++ b/src/doblib/action.py @@ -1,14 +1,23 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import random import string import uuid -from datetime import date, datetime, timedelta +from datetime import ( + date, + datetime, + timedelta, +) from dateutil.relativedelta import relativedelta -from . import base, env, utils +from . import ( + base, + env, + utils, +) ALNUM = string.ascii_letters + string.digits @@ -19,7 +28,7 @@ def load_action_arguments(args, actions=None): "action", metavar="action", choices=actions or (), - help=f"Action to run. Possible choices: {','.join(actions)}", + help="Action to run. Possible choices: {}".format(",".join(actions)), ) parser.add_argument( "steps", @@ -201,12 +210,12 @@ def _text(self, rec, name, **kw): suffix = kw.get("suffix", "") field = kw.get("field", None) if isinstance(field, str): - return f"{prefix}{rec[field]}{suffix}" + return "{}{}{}".format(prefix, rec[field], suffix) # Randomize the value length = kw.get("length", None) if isinstance(length, int) and length > 0: - return prefix + "".join(random.choices(ALNUM, k=length)) + suffix + return prefix + "".join(utils.choices(ALNUM, k=length)) + suffix # Take a random value from the choices choices = kw.get("choices", None) @@ -334,39 +343,42 @@ def _replace_recursively(self, value, replace_dict): else: self._replace_recursively(value[index], replace_dict) - def _action_delete(self, env, model, domain, item): + def _action_delete(self, env, model, domain, item, dry_run=False): """Runs the delete action""" - if model in env: - references = item.get("references", {}) - chunk = item.get("chunk", None) - truncate = item.get("truncate", False) + if model not in env.registry: + return - if domain and truncate: - utils.warn( - "Setting a domain is not possible with truncate. Falling back" - ) + references = item.get("references", {}) + chunk = item.get("chunk", None) + truncate = item.get("truncate", False) - elif not domain and truncate: - table = env[model]._table + if domain and truncate: + utils.warn( + "Setting a domain is not possible with truncate. Falling back" + ) - env.cr.execute(f"TRUNCATE {table} CASCADE") - return + elif not domain and truncate: + table = env[model]._table - self._replace_references(env, references, domain) - records = env[model].with_context(active_test=False).search(domain) + env.cr.execute("TRUNCATE {} CASCADE".format(table)) + return + + self._replace_references(env, references, domain) + records = env[model].with_context(active_test=False).search(domain) - if records: - if chunk: - for i in range(0, len(records), chunk): - records[i : i + chunk].unlink() + if records: + if chunk: + for i in range(0, len(records), chunk): + records[i : i + chunk].unlink() + if not dry_run: env.cr.commit() - else: - records.unlink() + else: + records.unlink() - def _action_update(self, env, model, domain, item): + def _action_update(self, env, model, domain, item, dry_run=False): """Runs the update action""" values = item.get("values", {}) - if not values or model not in env: + if not values or model not in env.registry: return references = item.get("references", {}) @@ -395,7 +407,8 @@ def _action_update(self, env, model, domain, item): if chunk: for i in range(0, len(records), chunk): records[i : i + chunk].write(const) - env.cr.commit() + if not dry_run: + env.cr.commit() else: records.write(const) @@ -412,11 +425,15 @@ def _action_update(self, env, model, domain, item): if chunk and counter >= chunk: counter = 0 - env.cr.commit() + if not dry_run: + env.cr.commit() - def _action_insert(self, env, model, domain, item): + def _action_insert(self, env, model, domain, item, dry_run=False): values = item.get("values", {}) - if not domain or not values or model not in env or env[model].search(domain): + if not domain or not values or model not in env.registry: + return + + if env[model].search(domain): return references = item.get("references", {}) @@ -428,25 +445,31 @@ def _action_insert(self, env, model, domain, item): def apply_action(self, args=None): """Apply in the configuration defined actions on the database""" - actions = self.get("actions", default={}) + actions = self.get(["actions"], default={}) args, _ = load_action_arguments(args or [], list(actions)) if not self._init_odoo(): return # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo import api + from odoo.cli import server + from odoo.tools import config + except ImportError: + from openerp import api + from openerp.cli import server + from openerp.tools import config # Load the Odoo configuration config.parse_config(["-c", base.ODOO_CONFIG]) - odoo.cli.server.report_configuration() + server.report_configuration() db_name = config["db_name"] - utils.info(f"Running {args.action}") + utils.info("Running {}".format(args.action)) with self._manage(): - with self.env(db_name) as env: + with self.env(db_name, rollback=args.dry_run) as env: for name, item in actions[args.action].items(): if not item.get("enable", True): continue @@ -456,7 +479,7 @@ def apply_action(self, args=None): if steps and name not in steps: continue - utils.info(f"{args.action.capitalize()} {name}") + utils.info("{} {}".format(args.action.capitalize(), name)) model = item.get("model") if not isinstance(model, str): utils.error("Model must be string") @@ -469,14 +492,14 @@ def apply_action(self, args=None): ctx = env.context.copy() ctx.update(item.get("context") or {}) - action_env = odoo.api.Environment(env.cr, env.uid, ctx) + action_env = api.Environment(env.cr, env.uid, ctx) act = item.get("action", "update") if act == "update": - self._action_update(action_env, model, domain, item) + self._action_update(action_env, model, domain, item, dry_run=args.dry_run) elif act == "delete": - self._action_delete(action_env, model, domain, item) + self._action_delete(action_env, model, domain, item, dry_run=args.dry_run) elif act == "insert": - self._action_insert(action_env, model, domain, item) + self._action_insert(action_env, model, domain, item, dry_run=args.dry_run) else: - utils.error(f"Undefined action {act}") + utils.error("Undefined action {}".format(act)) diff --git a/src/doblib/aggregate.py b/src/doblib/aggregate.py index fd6d60c..ae52e34 100644 --- a/src/doblib/aggregate.py +++ b/src/doblib/aggregate.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). @@ -6,14 +7,21 @@ import threading import traceback from multiprocessing import cpu_count -from queue import Empty, Queue +from Queue import ( + Empty, + Queue, +) from git_aggregator.config import get_repos from git_aggregator.main import match_dir from git_aggregator.repo import Repo from git_aggregator.utils import ThreadNameKeeper -from . import base, env, utils +from . import ( + base, + env, + utils, +) def aggregate_repo(repo, args, sem, err_queue, mode=None): @@ -110,10 +118,10 @@ def _aggregator(self, args, mode=None): sem = threading.Semaphore(jobs) err_queue = Queue() - default = self.get(base.SECTION, "repo", default={}) + default = self.get([base.SECTION, "repo"], default={}) repos = { key: utils.merge(default, value, replace=["merges"]) - for key, value in self.get("repos", default={}).items() + for key, value in self.get(["repos"], default={}).items() } for repo_dict in get_repos(repos, args.force): if not err_queue.empty(): diff --git a/src/doblib/base.py b/src/doblib/base.py index 081485b..d04f560 100644 --- a/src/doblib/base.py +++ b/src/doblib/base.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). diff --git a/src/doblib/ci.py b/src/doblib/ci.py index 9fc9636..44c9413 100644 --- a/src/doblib/ci.py +++ b/src/doblib/ci.py @@ -1,18 +1,22 @@ +# -*- coding: utf-8 -*- # © 2021 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import glob import os -import shutil import sys from fnmatch import fnmatch import isort import pytest -from . import base, env, utils +from . import ( + base, + env, + utils, +) -CI = ("black", "eslint", "flake8", "isort", "prettier", "pylint") +CI = ("eslint", "flake8", "isort", "prettier", "pylint") def load_ci_arguments(args): @@ -29,37 +33,9 @@ def load_ci_arguments(args): class CIEnvironment(env.Environment): """Class to run tests on the environment""" - def _ci_black(self, options, args, paths, ignores): - """Run black""" - cmd = [sys.executable, "-m", "black"] - - # Replace pattern matching with regex - ignores = [pattern.replace("*", ".*").replace("?", ".") for pattern in ignores] - - # Append black default excludes - ignores = ignores + [ - "\\.git", - "\\.hg", - "\\.mypy_cache", - "\\.tox", - "\\.venv", - "_build", - "buck-out", - "build", - "dist", - ] - - exclude = "(" + "|".join(ignores) + ")" - cmd += ["--exclude", exclude] - - if not options.fix: - cmd += ["--check", "--diff"] - - return utils.call(*cmd, *args, *paths, pipe=False) - def _ci_eslint(self, options, args, paths, ignores): """Run eslint if tool is available""" - executable = shutil.which("eslint") + executable = utils.which("eslint") if not executable: utils.error("eslint is not installed") return 1 @@ -71,25 +47,27 @@ def _ci_eslint(self, options, args, paths, ignores): for pattern in ignores: cmd += ["--ignore-pattern", pattern] - return utils.call(*cmd, *args, *paths, pipe=False) + cmd.extend(args) + cmd.extend(paths) + return utils.call(cmd, pipe=False) - def _ci_flake8(self, options, left, paths, ignores): + def _ci_flake8(self, options, args, paths, ignores): """Run flake8 tests""" cmd = [sys.executable, "-m", "flake8"] if ignores: cmd.append("--extend-exclude=" + ",".join(ignores)) + cmd.extend(args) + cmd.extend(paths) return utils.call( - *cmd, - *left, - *paths, + cmd, pipe=False, ) def _ci_isort(self, options, args, paths, ignores): """Run isort""" - cmd = [sys.executable, "-m", "isort"] + cmd = ["isort"] if not options.fix: cmd.extend(("--check", "--diff")) @@ -97,32 +75,34 @@ def _ci_isort(self, options, args, paths, ignores): cmd.append("--recursive") for pattern in ignores: - cmd += ["--skip-glob", f"*/{pattern}"] - cmd += ["--skip-glob", f"*/{pattern}/*"] - cmd += ["--skip-glob", f"{pattern}/*"] + cmd += ["--skip-glob", "*/{}".format(pattern)] + cmd += ["--skip-glob", "*/{}/*".format(pattern)] + cmd += ["--skip-glob", "{}/*".format(pattern)] if ignores: cmd.append("--filter-files") - return utils.call(*cmd, *args, *paths, pipe=False) + cmd.extend(args) + cmd.extend(paths) + return utils.call(cmd, pipe=False) def _ci_prettier(self, options, args, paths, ignores): """ """ - executable = shutil.which("prettier") + executable = utils.which("prettier") if not executable: utils.error("prettier is not installed") return 1 files = [] for path in paths: - files.extend(glob.glob(f"{path}/**/*.js", recursive=True)) + files.extend(utils.recursive_glob(path, "*.js")) files = list( filter( lambda path: not any( - fnmatch(path, f"*/{pattern}") - or fnmatch(path, f"*/{pattern}/*") - or fnmatch(path, f"{pattern}/*") + fnmatch(path, "*/{}".format(pattern)) + or fnmatch(path, "*/{}/*".format(pattern)) + or fnmatch(path, "{}/*".format(pattern)) for pattern in ignores ), files, @@ -136,22 +116,24 @@ def _ci_prettier(self, options, args, paths, ignores): if options.fix: cmd.append("--write") - return utils.call(*cmd, *args, *files, pipe=False) + cmd.extend(args) + cmd.extend(files) + return utils.call(cmd, pipe=False) def _ci_pylint(self, options, args, paths, ignores): """Run pylint tests for Odoo""" files = [] for path in paths: - files.extend(glob.glob(f"{path}/**/*.csv", recursive=True)) - files.extend(glob.glob(f"{path}/**/*.py", recursive=True)) - files.extend(glob.glob(f"{path}/**/*.xml", recursive=True)) + files.extend(utils.recursive_glob(path, "*.csv")) + files.extend(utils.recursive_glob(path, "*.py")) + files.extend(utils.recursive_glob(path, "*.xml")) files = list( filter( lambda path: not any( - fnmatch(path, f"*/{pattern}") - or fnmatch(path, f"*/{pattern}/*") - or fnmatch(path, f"{pattern}/*") + fnmatch(path, "*/{}".format(pattern)) + or fnmatch(path, "*/{}/*".format(pattern)) + or fnmatch(path, "{}/*".format(pattern)) for pattern in ignores ), files, @@ -165,20 +147,22 @@ def _ci_pylint(self, options, args, paths, ignores): if os.path.isfile(".pylintrc"): cmd.append("--rcfile=.pylintrc") - return utils.call(*cmd, *args, *files, pipe=False) + cmd.extend(args) + cmd.extend(files) + return utils.call(cmd, pipe=False) def ci(self, ci, args=None): """Run CI tests""" args, left = load_ci_arguments(args or []) # Always include this script in the tests - paths = self.get("odoo", "addons_path", default=[]) - ignores = self.get("bootstrap", "blacklist", default=[]) - func = getattr(self, f"_ci_{ci}", None) + paths = self.get(["odoo", "addons_path"], default=[]) + ignores = self.get(["bootstrap", "blacklist"], default=[]) + func = getattr(self, "_ci_{}".format(ci), None) if ci in CI and callable(func): return func(args, left, paths, ignores) - utils.error(f"Unknown CI {ci}") + utils.error("Unknown CI {}".format(ci)) return 1 def test(self, args=None): @@ -190,24 +174,28 @@ def test(self, args=None): return False # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo.cli import server + from odoo.tools import config + except ImportError: + from openerp.cli import server + from openerp.tools import config # Append needed parameter if self.get(base.SECTION, "coverage"): - for path in self.get("odoo", "addons_path", default=[]): - args.extend([f"--cov={path}", path]) + for path in self.get(["odoo", "addons_path"], default=[]): + args.extend(["--cov={}".format(path), path]) args += ["--cov-report=html", "--cov-report=term"] # Load the odoo configuration with self._manage(): config.parse_config(["-c", base.ODOO_CONFIG]) - odoo.cli.server.report_configuration() + server.report_configuration() # Pass the arguments to pytest sys.argv = sys.argv[:1] + args result = pytest.main() - if result and result != pytest.ExitCode.NO_TESTS_COLLECTED: + if result and result != 5: return result return 0 diff --git a/src/doblib/env.py b/src/doblib/env.py index 8c32846..65e21ce 100644 --- a/src/doblib/env.py +++ b/src/doblib/env.py @@ -1,16 +1,23 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). -import configparser import os import re import shutil import sys -from contextlib import closing, contextmanager +from contextlib import ( + closing, + contextmanager, +) +import configparser import yaml -from . import base, utils +from . import ( + base, + utils, +) SubstituteRegex = re.compile(r"\$\{(?P(\w|:)+)\}") @@ -38,12 +45,12 @@ def _substitute(self, match, sub=True): if not all(var): raise SyntaxError() - result = self.get(*var) + result = self.get(var) return str(result) if sub else result def _substitute_string(self, line): """Substitute variables in strings""" - match = SubstituteRegex.fullmatch(line) + match = SubstituteRegex.match(r"^{}$".format(line)) if match: return self._substitute(match, False) return SubstituteRegex.sub(self._substitute, line) @@ -82,24 +89,24 @@ def _post_process_config(self): # Include environment variables first for later substitutions for env, keys in base.ENVIRONMENT.items(): if os.environ.get(env): - self.set(*keys, value=os.environ[env]) + self.set(keys, value=os.environ[env]) - options = self.get("odoo", "options", default={}) + options = self.get(["odoo", "options"], default={}) for key, value in options.items(): - options[key] = os.environ.get(f"ODOO_{key.upper()}") or value + options[key] = os.environ.get("ODOO_{}".format(key.upper())) or value # Run the substitution on the configuration self._config = self._substitute_dict(self._config) # Combine the addon paths - current = set(self.get("odoo", "addons_path", default=[])) + current = set(self.get(["odoo", "addons_path"], default=[])) current.add(base.ADDON_PATH) # Generate the addon paths current = set(map(os.path.abspath, current)) - self.set("odoo", "options", "addons_path", value=current) + self.set(["odoo", "options", "addons_path"], value=current) - def get(self, *key, default=None): + def get(self, key, default=None): """Get a specific value of the configuration""" data = self._config try: @@ -107,15 +114,15 @@ def get(self, *key, default=None): data = data[k] if data is None: return default - return data + return utils.yaml_bool(data) except KeyError: return default - def opt(self, *key, default=None): + def opt(self, key, default=None): """Short cut to directly access odoo options""" - return self.get("odoo", "options", *key, default=default) + return self.get(["odoo", "options"] + [key], default=default) - def set(self, *key, value=None): + def set(self, key, value=None): """Set a specific value of the configuration""" data = self._config for k in key[:-1]: @@ -126,11 +133,11 @@ def set(self, *key, value=None): def _load_config(self, cfg, raise_if_missing=True): """Load and process a configuration file""" if not os.path.isfile(cfg) and not raise_if_missing: - utils.warn(f" * {cfg}") + utils.warn(" * {}".format(cfg)) return - utils.info(f" * {cfg}") - with open(cfg, encoding="utf-8") as fp: + utils.info(" * {}".format(cfg)) + with open(cfg) as fp: options = yaml.load(fp, Loader=yaml.FullLoader) # Load all base configuration files first @@ -141,7 +148,7 @@ def _load_config(self, cfg, raise_if_missing=True): for e in extend: self._load_config(e) elif extend is not None: - raise TypeError(f"{base.SECTION}:extend must be str or list") + raise TypeError("{}:extend must be str or list".format(base.SECTION)) # Merge the configurations self._config = utils.merge(self._config, options, replace=["merges"]) @@ -149,10 +156,11 @@ def _load_config(self, cfg, raise_if_missing=True): def _link_modules(self): """Create symlinks to the modules to allow black-/whitelisting""" shutil.rmtree(base.ADDON_PATH, True) - os.makedirs(base.ADDON_PATH, exist_ok=True) + if not os.path.isdir(base.ADDON_PATH): + os.makedirs(base.ADDON_PATH) utils.info("Linking Odoo modules") - for repo_src, repo in self.get("repos", default={}).items(): + for repo_src, repo in self.get(["repos"], default={}).items(): target = os.path.abspath(repo.get("addon_path", repo_src)) modules = repo.get("modules", []) whitelist = {m for m in modules if not m.startswith("!")} @@ -161,7 +169,10 @@ def _link_modules(self): for module in os.listdir(target): path = os.path.join(target, module) # Check if module - if not os.path.isfile(os.path.join(path, "__manifest__.py")): + if not any( + os.path.isfile(os.path.join(path, manifest)) + for manifest in ("__manifest__.py", "__openerp__.py") + ): continue if utils.check_filters(module, whitelist, blacklist): @@ -169,9 +180,9 @@ def _link_modules(self): def _init_odoo(self): """Initialize Odoo to enable the module import""" - path = self.get(base.SECTION, "odoo") + path = self.get([base.SECTION, "odoo"]) if not path: - utils.error(f"No {base.SECTION}:odoo defined") + utils.error("No {}:odoo defined".format(base.SECTION)) return False path = os.path.abspath(path) @@ -189,12 +200,15 @@ def _init_odoo(self): def env(self, db_name, rollback=False): """Create an environment from a registry""" # pylint: disable=C0415,E0401 - import odoo + try: + from odoo import SUPERUSER_ID, api, registry + except ImportError: + from openerp import SUPERUSER_ID, api, registry # Get all installed modules - reg = odoo.registry(db_name) + reg = registry(db_name) with closing(reg.cursor()) as cr: - yield odoo.api.Environment(cr, odoo.SUPERUSER_ID, {}) + yield api.Environment(cr, SUPERUSER_ID, {}) if rollback: cr.rollback() @@ -205,13 +219,15 @@ def env(self, db_name, rollback=False): def _manage(self): """Wrap the manage to resolve version differrences""" # pylint: disable=import-outside-toplevel - import odoo - import odoo.release + try: + from odoo import api, release + except ImportError: + from openerp import api, release - if odoo.release.version_info >= (15,): + if release.version_info >= (15,): yield else: - with odoo.api.Environment.manage(): + with api.Environment.manage(): yield def generate_config(self): @@ -220,7 +236,7 @@ def generate_config(self): cp = configparser.ConfigParser() # Generate the configuration with the sections - options = self.get("odoo", "options", default={}) + options = self.get(["odoo", "options"], default={}) for key, value in sorted(options.items()): if key == "load_language": continue @@ -240,9 +256,11 @@ def generate_config(self): else: cp.set(sec, key, str(value)) - os.makedirs(os.path.dirname(base.ODOO_CONFIG), exist_ok=True) + directory = os.path.dirname(base.ODOO_CONFIG) + if not os.path.isdir(directory): + os.makedirs(directory) # Write the configuration - with open(base.ODOO_CONFIG, "w+", encoding="utf-8") as fp: + with open(base.ODOO_CONFIG, "w+") as fp: cp.write(fp) def config(self, args=None): @@ -250,6 +268,6 @@ def config(self, args=None): args, _ = load_config_arguments(args or []) if args.option: - return yaml.dump(self.get(*args.option.split(":"))) + return yaml.dump(self.get(args.option.split(":"))) return yaml.dump(self._config) diff --git a/src/doblib/freeze.py b/src/doblib/freeze.py index 275892c..cea6c99 100644 --- a/src/doblib/freeze.py +++ b/src/doblib/freeze.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). @@ -7,7 +8,11 @@ import yaml -from . import base, env, utils +from . import ( + base, + env, + utils, +) def load_freeze_arguments(args): @@ -59,7 +64,7 @@ def _freeze_mode(self, file, mode="ask"): return False if mode == "ask": - answer = input(f"Do you want to overwrite the {file}? [y/N] ") + answer = raw_input("Do you want to overwrite the {}? [y/N] ".format(file)) if answer.lower() != "y": return False @@ -69,31 +74,26 @@ def _freeze_packages(self, file, mode="ask"): """Freeze the python packages in the versions.txt""" if self._freeze_mode(file, mode): utils.info("Freezing packages") - versions = utils.call(sys.executable, "-m", "pip", "freeze") - with open(file, "w+", encoding="utf-8") as fp: + versions = utils.call([sys.executable, "-m", "pip", "freeze"]) + with open(file, "w+") as fp: fp.write(versions) def _freeze_repositories(self, file, mode="ask"): """Freeze the repositories""" # Get the default merges dict from the configuration - version = self.get(base.SECTION, "version", default="0.0") + version = self.get([base.SECTION, "version"], default="0.0") default_merges = self.get( - base.SECTION, - "repo", - "merges", - default=[f"origin {version}"], + [base.SECTION, "repo", "merges"], + default=["origin {}".format(version)], ) # Get the used remotes and commits from the repositoriey commits = {} - for path, repo in self.get("repos", default={}).items(): + for path, repo in self.get(["repos"], default={}).items(): # This will return all branches with " " syntax output = utils.call( - "git", - "branch", - "-va", - "--format=%(refname) %(objectname)", + ["git", "branch", "-va", "--format=%(refname) %(objectname)"], cwd=path, ) remotes = dict(line.split() for line in output.splitlines()) @@ -101,9 +101,9 @@ def _freeze_repositories(self, file, mode="ask"): # Aggregate the used commits from each specified merge tmp = [] for entry in repo.get("merges", default_merges): - name = f"refs/remotes/{entry.replace(' ', '/')}" + name = "refs/remotes/{}".format(entry.replace(' ', '/')) if name in remotes: - tmp.append(f"{entry.split()[0]} {remotes[name]}") + tmp.append("{} {}".format(entry.split()[0], remotes[name])) else: tmp.append(entry) @@ -116,7 +116,7 @@ def _freeze_repositories(self, file, mode="ask"): # Output the suggestion in a proper format to allow copy & paste if self._freeze_mode(file, mode): utils.info("Freezing repositories") - with open(file, "w+", encoding="utf-8") as fp: + with open(file, "w+") as fp: fp.write(yaml.dump({"repos": commits})) def freeze(self, args=None): diff --git a/src/doblib/migrate.py b/src/doblib/migrate.py index 74f8afb..63e6630 100644 --- a/src/doblib/migrate.py +++ b/src/doblib/migrate.py @@ -1,9 +1,13 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Ruben Ortlam (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). from contextlib import closing -from . import base, utils +from . import ( + base, + utils, +) from .aggregate import AggregateEnvironment from .module import ModuleEnvironment from .run import RunEnvironment @@ -40,39 +44,46 @@ def migrate(self, args): version = args.version self.generate_config() - utils.info(f"Checkout Odoo {version} repos") + utils.info("Checkout Odoo {} repos".format(version)) retval = self.init() if retval: - utils.error(f"Init step failed: {retval}") + utils.error("Init step failed: {}".format(retval)) return retval if not self._init_odoo(): return # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo import modules, sql_db + from odoo.cli import server + from odoo.tools import config + except ImportError: + from openerp import modules, sql_db + from openerp.cli import server + from openerp.tools import config # Load the Odoo configuration config.parse_config(["-c", base.ODOO_CONFIG]) - odoo.cli.server.report_configuration() + server.report_configuration() db_name = config["db_name"] with self._manage(): # Ensure that the database is initialized - db = odoo.sql_db.db_connect(db_name) + db = sql_db.db_connect(db_name) with closing(db.cursor()) as cr: - if not odoo.modules.db.is_initialized(cr): + if not modules.db.is_initialized(cr): utils.error("Odoo database not initialized") return -1 + major = version[0] if not args.skip_premigrate: utils.info("Run pre-migration scripts") - self._run_migration_sql(db_name, f"pre_migrate_{version[0]}.sql") - self._run_migration(db_name, f"pre_migrate_{version[0]}") + self._run_migration_sql(db_name, "pre_migrate_{}.sql".format(major)) + self._run_migration(db_name, "pre_migrate_{}".format(major)) if not args.skip_migrate: - utils.info(f"Running OpenUpgrade migration to Odoo {version}") + utils.info("Running OpenUpgrade migration to Odoo {}".format(version)) open_upgrade_args = [ "--update", "all", @@ -84,11 +95,11 @@ def migrate(self, args): retval = self.start(open_upgrade_args) if retval: - utils.error(f"Upgrade step failed: {retval}") + utils.error("Upgrade step failed: {}".format(retval)) return retval if not args.skip_postmigrate: utils.info("Run post-migration scripts") - self._run_migration_sql(db_name, f"post_migrate_{version[0]}.sql") - self._run_migration(db_name, f"post_migrate_{version[0]}") + self._run_migration_sql(db_name, "post_migrate_{}.sql".format(major)) + self._run_migration(db_name, "post_migrate_{}".format(major)) return 0 diff --git a/src/doblib/module.py b/src/doblib/module.py index d10a77d..2fbde68 100644 --- a/src/doblib/module.py +++ b/src/doblib/module.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). @@ -7,7 +8,11 @@ import sys from contextlib import closing -from . import base, env, utils +from . import ( + base, + env, + utils, +) def no_flags(x): @@ -60,7 +65,7 @@ def _run_migration(self, db_name, script_name): finally: sys.path = path - utils.info(f"Executing {script.__name__.replace('_', ' ')} script") + utils.info("Executing {} script".format(script.__name__.replace('_', ' '))) with self.env(db_name) as env: version = utils.Version( env["ir.config_parameter"].get_param("db_version", False) @@ -73,21 +78,24 @@ def _run_migration_sql(self, db_name, script_name): return # pylint: disable=C0415,E0401 - import odoo + try: + from odoo import sql_db + except ImportError: + from openerp import sql_db - utils.info(f"Executing {script_name} script") + utils.info("Executing {} script".format(script_name)) # Ensure that the database is initialized - db = odoo.sql_db.db_connect(db_name) + db = sql_db.db_connect(db_name) with closing(db.cursor()) as cr, open(script_name, "r") as f: cr.execute(f.read()) def _get_modules(self): """Return the list of modules""" - modes = self.get(base.SECTION, "mode", default=[]) + modes = self.get([base.SECTION, "mode"], default=[]) modes = set(modes.split(",") if isinstance(modes, str) else modes) modules = set() - for module in self.get("modules", default=[]): + for module in self.get(["modules"], default=[]): if isinstance(module, str): modules.add(module) elif isinstance(module, dict) and len(module) == 1: @@ -111,8 +119,12 @@ def _get_installed_modules(self, db_name): def install_all(self, db_name, modules): """Install all modules""" # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo.modules import registry + from odoo.tools import config + except ImportError: + from openerp.modules import registry + from openerp.tools import config config["init"] = dict.fromkeys(modules, 1) config["update"] = {} @@ -124,19 +136,23 @@ def install_all(self, db_name, modules): elif languages: config["load_language"] = languages - odoo.modules.registry.Registry.new( - db_name, - update_module=True, - force_demo=not without_demo, - ) + kwargs = {"update_module": True, "force_demo": not without_demo} + if hasattr(registry.Registry, "new"): + registry.Registry.new(db_name, **kwargs) + else: + registry.RegistryManager.new(db_name, **kwargs) def update_specific( self, db_name, whitelist=None, blacklist=None, installed=False, listed=False ): """Update all modules""" # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo.modules import registry + from odoo.tools import config + except ImportError: + from openerp.modules import registry + from openerp.tools import config whitelist = set(whitelist or []) @@ -156,7 +172,11 @@ def update_specific( config["init"] = {} config["update"] = dict.fromkeys(modules, 1) config["overwrite_existing_translations"] = True - odoo.modules.registry.Registry.new(db_name, update_module=True) + kwargs = {"update_module": True} + if hasattr(registry.Registry, "new"): + registry.Registry.new(db_name, **kwargs) + else: + registry.RegistryManager.new(db_name, **kwargs) def update_changed(self, db_name, blacklist=None): """Update only changed modules""" @@ -169,10 +189,11 @@ def update_changed(self, db_name, blacklist=None): # `assert_log_admin_access`. Exceptions occur if an existing module # adds a new field to `res.users` which gets loaded inside of python # but doesn't link to a column in the database - utils.info("Initializing the `res.users` models") - env.registry.init_models( - env.cr, ["res.partner", "res.users"], env.context - ) + if hasattr(env.registry, "init_models"): + utils.info("Initializing the `res.users` models") + env.registry.init_models( + env.cr, ["res.partner", "res.users"], env.context + ) model.upgrade_changed_checksum(True) return @@ -190,22 +211,30 @@ def update(self, args=None): return # pylint: disable=C0415,E0401 - import odoo - from odoo.tools import config + try: + from odoo import sql_db + from odoo.cli import server + from odoo.modules.db import initialize, is_initialized + from odoo.tools import config + except ImportError: + from openerp import sql_db + from openerp.cli import server + from openerp.modules.db import initialize, is_initialized + from openerp.tools import config # Load the Odoo configuration config.parse_config(["-c", base.ODOO_CONFIG]) - odoo.cli.server.report_configuration() + server.report_configuration() db_name = config["db_name"] with self._manage(): # Ensure that the database is initialized - db = odoo.sql_db.db_connect(db_name) + db = sql_db.db_connect(db_name) initialized = False with closing(db.cursor()) as cr: - if not odoo.modules.db.is_initialized(cr): + if not is_initialized(cr): utils.info("Initializing the database") - odoo.modules.db.initialize(cr) + initialize(cr) cr.commit() initialized = True @@ -246,7 +275,7 @@ def update(self, args=None): # Finish everything with self.env(db_name) as env: # Set the user passwords if previously initialized - users = self.get("odoo", "users", default={}) + users = self.get(["odoo", "users"], default={}) if (initialized or args.passwords) and users: utils.info("Setting user passwords") model = env["res.users"] @@ -256,5 +285,5 @@ def update(self, args=None): # Write the version into the database utils.info("Setting database version") - version = self.get(base.SECTION, "version", default="0.0") + version = self.get([base.SECTION, "version"], default="0.0") env["ir.config_parameter"].set_param("db_version", version) diff --git a/src/doblib/run.py b/src/doblib/run.py index e0c36b8..a4c7478 100644 --- a/src/doblib/run.py +++ b/src/doblib/run.py @@ -1,10 +1,16 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import argparse +import os import sys -from . import base, env, utils +from . import ( + base, + env, + utils, +) def load_shell_arguments(args): @@ -25,6 +31,41 @@ def load_shell_arguments(args): class RunEnvironment(env.Environment): """Class to the environment""" + def _shell(self, args): + try: + import odoo + from odoo.cli import server + from odoo.tools import config + except ImportError: + import openerp as odoo + from openerp.cli import server + from openerp.tools import config + + config.parse_config(["-c", base.ODOO_CONFIG, "--no-xmlrpc"]) + server.report_configuration() + + db_name = config["db_name"] + with self._manage(), self.env(db_name, rollback=True) as env: + local_vars = { + "odoo": odoo, + "openerp": odoo, + "self": env.user, + "env": env, + } + + if args.file: + local_vars["__name__"] = "__main__" + with open(args.file, "r") as fp: + exec(fp.read(), local_vars) + else: + from IPython import start_ipython + for name, value in sorted(local_vars.items()): + print("{}: {}".format(name, value)) + + start_ipython(argv=[], user_ns=local_vars) + + return 0 + def shell(self, args=None): """Start an Odoo shell""" args, left = load_shell_arguments(args or []) @@ -32,16 +73,19 @@ def shell(self, args=None): return False # pylint: disable=C0415,E0401 - from odoo.cli.shell import Shell + try: + from odoo.cli.shell import Shell + except ImportError: + return self._shell(args) if args.file: - sys.stdin = open(args.file, "r", encoding="utf-8") + sys.stdin = open(args.file, "r") sys.argv = [args.file] + left else: sys.argv = [""] shell = Shell() - return shell.run(["-c", base.ODOO_CONFIG, "--no-http"]) + return shell.run(["-c", base.ODOO_CONFIG, "--no-xmlrpc"]) def start(self, args=None): """Start Odoo without wrapper""" @@ -52,20 +96,20 @@ def start(self, args=None): if not path: return False - debugger = self.get(base.SECTION, "debugger") + debugger = self.get([base.SECTION, "debugger"]) debug_cmd = () if debugger == "debugpy": - utils.info(f"Starting with debugger {debugger}") + utils.info("Starting with debugger %s", debugger) debug_cmd = "-m", "debugpy", "--listen", "0.0.0.0:5678", "--wait-for-client" elif debugger == "dev": args += ("--dev=all",) + if os.path.isfile(os.path.join(path, "odoo-bin")): + cmd = ["odoo-bin", "-c", base.ODOO_CONFIG] + else: + cmd = ["openerp-server", "-c", base.ODOO_CONFIG] + return utils.call( - sys.executable, - *debug_cmd, - "odoo-bin", - "-c", - base.ODOO_CONFIG, - *args, + [sys.executable] + list(debug_cmd) + cmd + list(args), cwd=path, ) diff --git a/src/doblib/utils.py b/src/doblib/utils.py index 68b84f5..600760e 100644 --- a/src/doblib/utils.py +++ b/src/doblib/utils.py @@ -1,11 +1,17 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import argparse +import fnmatch import logging import os -from fnmatch import fnmatch -from subprocess import PIPE, Popen +import random +import sys +from subprocess import ( + PIPE, + Popen, +) _logger = logging.getLogger(__name__) @@ -19,42 +25,79 @@ def get_config_file(): return None -def call(*cmd, cwd=None, pipe=True): +def call(cmd, cwd=None, pipe=True): """Call a subprocess and return the stdout""" - with Popen( + proc = Popen( cmd, cwd=cwd, stdout=PIPE if pipe else None, universal_newlines=True, - ) as proc: - output = proc.communicate()[0] - if pipe: - return output.strip() if output else "" - return proc.returncode + ) + output = proc.communicate()[0] + if pipe: + return output.strip() if output else "" + return proc.returncode + + +def which(cmd): + return call(["which", cmd]) + + +def choices(seq, k): + return [random.choice(seq) for _ in range(k)] + + +def recursive_glob(path, pattern): + files = [] + for root, _, filenames in os.walk(path): + for filename in fnmatch.filter(filenames, pattern): + files.append(os.path.join(root, filename)) + return files + + +def module_mock(mock, submodules): + for submodule in submodules: + parts = submodule.split(".") + tmp, path = mock, parts[:1] + for sub in submodule.split(".")[1:]: + tmp = getattr(tmp, sub) + path.append(sub) + sys.modules[".".join(path)] = tmp + + +def yaml_bool(x): + if not isinstance(x, str): + return x + + if x.lower() in ("y", "yes", "true", "on"): + return True + if x.lower() in ("n", "no", "false", "off"): + return False + return x def info(msg, *args): """Output a green colored info message""" - _logger.info(f"\x1b[32m{msg % args}\x1b[0m") + _logger.info("\x1b[32m{}\x1b[0m".format(msg % args)) def warn(msg, *args): """Output a yellow colored warning message""" - _logger.warning(f"\x1b[33m{msg % args}\x1b[0m") + _logger.warning("\x1b[33m{}\x1b[0m".format(msg % args)) def error(msg, *args): """Output a red colored error""" - _logger.error(f"\x1b[31m{msg % args}\x1b[0m") + _logger.error("\x1b[31m{}\x1b[0m".format(msg % args)) def check_filters(name, whitelist=None, blacklist=None): """Check the name against the whitelist and blacklist""" - if whitelist and not any(fnmatch(name, pat) for pat in whitelist): + if whitelist and not any(fnmatch.fnmatch(name, pat) for pat in whitelist): return False - if blacklist and any(fnmatch(name, pat) for pat in blacklist): + if blacklist and any(fnmatch.fnmatch(name, pat) for pat in blacklist): return False return True @@ -63,7 +106,7 @@ def check_filters(name, whitelist=None, blacklist=None): def default_parser(command): """Return the common parser options""" parser = argparse.ArgumentParser( - usage=f"%(prog)s {command} [options]", + usage="%(prog)s {} [options]".format(command), formatter_class=argparse.RawTextHelpFormatter, ) parser.add_argument( @@ -75,7 +118,7 @@ def default_parser(command): return parser -def merge(a, b, *, replace=None): +def merge(a, b, replace=None): """Merges dicts and lists from the configuration structure""" if isinstance(a, dict) and isinstance(b, dict): if not replace: diff --git a/tests/environment/post_update.py b/tests/environment/post_update.py index f427b12..6257b5e 100644 --- a/tests/environment/post_update.py +++ b/tests/environment/post_update.py @@ -1,2 +1,4 @@ +# -*- coding: utf-8 -*- + def migrate(env, db_version): env.check(str(db_version)) diff --git a/tests/test_action.py b/tests/test_action.py index bd04b49..53936a7 100644 --- a/tests/test_action.py +++ b/tests/test_action.py @@ -1,14 +1,21 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os import sys -from datetime import date, datetime -from unittest import mock +from datetime import ( + date, + datetime, +) +import mock import pytest - -from doblib.action import ALNUM, ActionEnvironment +from doblib import utils +from doblib.action import ( + ALNUM, + ActionEnvironment, +) @pytest.fixture @@ -33,6 +40,7 @@ def odoo_env(module): odoo_env_dict = {"test": module} odoo_env.__getitem__.side_effect = odoo_env_dict.__getitem__ odoo_env.__contains__.side_effect = odoo_env_dict.__contains__ + odoo_env.registry = odoo_env ref_mock = mock.MagicMock() ref_mock.id = 5 @@ -111,7 +119,7 @@ def test_text(env): with pytest.raises(KeyError): env._text({}, name="name", field="test") - with mock.patch("random.choices", return_value="abc") as choices: + with mock.patch("doblib.utils.choices", return_value="abc") as choices: assert ( env._text({}, name="test", length=5, prefix="0", suffix="def") == "0abcdef" ) @@ -233,6 +241,15 @@ def test_action_delete(call_mock, env, odoo_env, module): records.unlink.assert_called_once() odoo_env.cr.commit.assert_called_once() + search.reset_mock() + odoo_env.reset_mock() + module.with_context.reset_mock() + env._action_delete(odoo_env, "test", domain, {"chunk": 1000}, dry_run=True) + module.with_context.assert_called_once_with(active_test=False) + search.assert_called_once_with(domain) + records.unlink.assert_called_once() + odoo_env.cr.commit.assert_not_called() + search.reset_mock() odoo_env.reset_mock() module.with_context.reset_mock() @@ -291,15 +308,15 @@ def test_action_update(env, odoo_env, module): const_model.type = "integer" records._fields = {"test": test_model, "const": const_model} records.__len__.return_value = 2 - records.__bool__.return_value = False records.__getitem__.return_value = records + records.__nonzero__.return_value = False env._action_update( odoo_env, "test", [], {"values": {"test": 42, "unknown": 42}, "chunk": 1000} ) records.write.assert_not_called() - records.__bool__.return_value = True + records.__nonzero__.return_value = True env._action_update( odoo_env, "test", [], {"values": {"test": 42, "unknown": 42}, "chunk": 1000} ) @@ -336,6 +353,18 @@ def test_action_update(env, odoo_env, module): assert records.write.call_count == 4 assert odoo_env.cr.commit.call_count == 4 + records.write.reset_mock() + odoo_env.reset_mock() + env._action_update( + odoo_env, + "test", + [], + {"values": {"test": {"lower": 5, "upper": 5}, "const": 2}, "chunk": 1}, + dry_run=True, + ) + # For each record (2) const and dynamic commit + odoo_env.cr.commit.assert_not_called() + def test_action_insert(env, odoo_env, module): create = module.with_context.return_value.create @@ -404,8 +433,7 @@ def test_apply_action(env): env.apply_action(["action"]) odoo = sys.modules["odoo"] = mock.MagicMock() - sys.modules["odoo.tools"] = mock.MagicMock() - sys.modules["odoo.release"] = odoo.release + utils.module_mock(odoo, ["odoo.api", "odoo.cli", "odoo.release", "odoo.tools"]) odoo.release.version_info = (14, 0) env._init_odoo.return_value = True diff --git a/tests/test_aggregate.py b/tests/test_aggregate.py index 989d271..39f5271 100644 --- a/tests/test_aggregate.py +++ b/tests/test_aggregate.py @@ -1,13 +1,16 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os -from queue import Empty -from unittest import mock +from Queue import Empty +import mock import pytest - -from doblib.aggregate import AggregateEnvironment, aggregate_repo +from doblib.aggregate import ( + AggregateEnvironment, + aggregate_repo, +) def aggregate_exception(repo, args, sem, err_queue): diff --git a/tests/test_ci.py b/tests/test_ci.py index a64c51d..9bd593d 100644 --- a/tests/test_ci.py +++ b/tests/test_ci.py @@ -1,13 +1,16 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os import sys -from unittest import mock +import mock import pytest - -from doblib import base +from doblib import ( + base, + utils, +) from doblib.ci import CIEnvironment @@ -25,8 +28,8 @@ def env(): @mock.patch("pytest.main", return_value=42) def test_test(pytest_mock, env): odoo = sys.modules["odoo"] = mock.MagicMock() - tools = sys.modules["odoo.tools"] = mock.MagicMock() - sys.modules["odoo.release"] = odoo.release + utils.module_mock(odoo, ["odoo.cli", "odoo.release", "odoo.tools"]) + odoo.release.version_info = (14, 0) assert env.test() is False @@ -35,10 +38,10 @@ def test_test(pytest_mock, env): env._init_odoo = mock.MagicMock(return_value=True) assert env.test() == 42 - tools.config.parse_config.assert_called_once_with(["-c", base.ODOO_CONFIG]) + odoo.tools.config.parse_config.assert_called_once_with(["-c", base.ODOO_CONFIG]) pytest_mock.assert_called_once() - pytest_mock.return_value = pytest.ExitCode.NO_TESTS_COLLECTED + pytest_mock.return_value = 5 assert env.test() == 0 @@ -51,24 +54,7 @@ def test_ci(env): @mock.patch("doblib.utils.call", return_value=42) -def test_ci_black(call, env): - assert env.ci("black") == 42 - call.assert_called_once_with( - sys.executable, - "-m", - "black", - "--exclude", - "(test1.*|test3|\\.git|\\.hg|\\.mypy_cache|" - "\\.tox|\\.venv|_build|buck-out|build|dist)", - "--check", - "--diff", - "addons", - pipe=False, - ) - - -@mock.patch("doblib.utils.call", return_value=42) -@mock.patch("shutil.which", return_value=False) +@mock.patch("doblib.utils.which", return_value=False) def test_ci_eslint(which, call, env): assert env.ci("eslint") == 1 call.assert_not_called() @@ -76,14 +62,16 @@ def test_ci_eslint(which, call, env): which.return_value = "/usr/bin/eslint" assert env.ci("eslint", ["--fix"]) == 42 call.assert_called_once_with( - "eslint", - "--no-error-on-unmatched-pattern", - "--fix", - "--ignore-pattern", - "test1*", - "--ignore-pattern", - "test3", - "addons", + [ + "eslint", + "--no-error-on-unmatched-pattern", + "--fix", + "--ignore-pattern", + "test1*", + "--ignore-pattern", + "test3", + "addons", + ], pipe=False, ) @@ -92,11 +80,13 @@ def test_ci_eslint(which, call, env): def test_ci_flake8(call, env): assert env.ci("flake8") == 42 call.assert_called_once_with( - sys.executable, - "-m", - "flake8", - "--extend-exclude=test1*,test3", - "addons", + [ + sys.executable, + "-m", + "flake8", + "--extend-exclude=test1*,test3", + "addons", + ], pipe=False, ) @@ -105,31 +95,32 @@ def test_ci_flake8(call, env): def test_ci_isort(call, env): assert env.ci("isort") == 42 call.assert_called_once_with( - sys.executable, - "-m", - "isort", - "--check", - "--diff", - "--skip-glob", - "*/test1*", - "--skip-glob", - "*/test1*/*", - "--skip-glob", - "test1*/*", - "--skip-glob", - "*/test3", - "--skip-glob", - "*/test3/*", - "--skip-glob", - "test3/*", - "--filter-files", - "addons", + [ + "isort", + "--check", + "--diff", + "--recursive", + "--skip-glob", + "*/test1*", + "--skip-glob", + "*/test1*/*", + "--skip-glob", + "test1*/*", + "--skip-glob", + "*/test3", + "--skip-glob", + "*/test3/*", + "--skip-glob", + "test3/*", + "--filter-files", + "addons", + ], pipe=False, ) @mock.patch("doblib.utils.call", return_value=42) -@mock.patch("shutil.which", return_value=False) +@mock.patch("doblib.utils.which", return_value=False) def test_ci_prettier(which, call, env): assert env.ci("prettier") == 1 call.assert_not_called() @@ -141,25 +132,27 @@ def test_ci_prettier(which, call, env): call.assert_not_called() with mock.patch( - "glob.glob", + "doblib.utils.recursive_glob", return_value=[ - "test15/path/file.py", - "folder/test123/file.py", - "folder/path/test196.py", - "test2/path/file.py", + "test15/path/file.js", + "folder/test123/file.js", + "folder/path/test196.js", + "test2/path/file.js", ], ): assert env.ci("prettier", ["--fix"]) == 42 call.assert_called_once_with( - "prettier", - "--write", - "test2/path/file.py", + [ + "prettier", + "--write", + "test2/path/file.js", + ], pipe=False, ) @mock.patch( - "glob.glob", + "doblib.utils.recursive_glob", return_value=[ "test15/path/file.py", "folder/test123/file.py", @@ -171,26 +164,30 @@ def test_ci_prettier(which, call, env): def test_ci_pylint(call, glob, env): assert env.ci("pylint") == 42 call.assert_called_once_with( - sys.executable, - "-m", - "pylint", - "--rcfile=.pylintrc", - "test2/path/file.py", - "test2/path/file.py", - "test2/path/file.py", + [ + sys.executable, + "-m", + "pylint", + "--rcfile=.pylintrc", + "test2/path/file.py", + "test2/path/file.py", + "test2/path/file.py", + ], pipe=False, ) call.reset_mock() assert env.ci("pylint") == 42 call.assert_called_once_with( - sys.executable, - "-m", - "pylint", - "--rcfile=.pylintrc", - "test2/path/file.py", - "test2/path/file.py", - "test2/path/file.py", + [ + sys.executable, + "-m", + "pylint", + "--rcfile=.pylintrc", + "test2/path/file.py", + "test2/path/file.py", + "test2/path/file.py", + ], pipe=False, ) diff --git a/tests/test_environment.py b/tests/test_environment.py index 2391d9e..664fe16 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -1,16 +1,22 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os import sys -from configparser import ConfigParser -from tempfile import NamedTemporaryFile, TemporaryDirectory -from unittest import mock +from tempfile import ( + NamedTemporaryFile, + mkdtemp, +) +import mock import pytest - +from configparser import ConfigParser from doblib import base -from doblib.env import Environment, load_config_arguments +from doblib.env import ( + Environment, + load_config_arguments, +) @pytest.fixture @@ -31,19 +37,19 @@ def test_arguments(): def test_configuration(env): - assert env.get("local") is True - assert env.get("project") is True - assert env.get("default") is True - assert env.get("main") is True + assert env.get(["local"]) is True + assert env.get(["project"]) is True + assert env.get(["default"]) is True + assert env.get(["main"]) is True # Test if the environment variable is used - assert env.get("odoo", "version") == "1x.0" + assert env.get(["odoo", "version"]) == "1x.0" # Test some substitutions - assert env.get("substring") == "0.1.2.3.4" - assert env.get("dict of lists") == ["1.2.3", {"a": "1.2.3"}, None] - assert env.get("list of dicts") == [{3: "1.2.3.4"}, {2: "0.1.2.3"}] - assert env.get("list of lists") == [["1.2.3.1.2.3"]] + assert env.get(["substring"]) == "0.1.2.3.4" + assert env.get(["dict of lists"]) == ["1.2.3", {"a": "1.2.3"}, None] + assert env.get(["list of dicts"]) == [{3: "1.2.3.4"}, {2: "0.1.2.3"}] + assert env.get(["list of lists"]) == [["1.2.3.1.2.3"]] # Test the options assert env.opt("testing") == "1.2.3" @@ -58,16 +64,16 @@ def test_configuration_output(env): def test_configuration_generation(env): - with TemporaryDirectory() as dir_name: - base.ODOO_CONFIG = f"{dir_name}/odoo.cfg" - env.generate_config() + dir_name = mkdtemp() + base.ODOO_CONFIG = "{}/odoo.cfg".format(dir_name) + env.generate_config() - cp = ConfigParser() - cp.read(base.ODOO_CONFIG) + cp = ConfigParser() + cp.read(base.ODOO_CONFIG) - assert cp.get("options", "testing") == "1.2.3" - assert cp.get("options", "to_none") == "" - assert cp.get("additional", "key") == "value" + assert cp.get("options", "testing") == "1.2.3" + assert cp.get("options", "to_none") == "" + assert cp.get("additional", "key") == "value" def test_invalid_extend(): @@ -90,17 +96,17 @@ def test_init_odoo(env): assert env._init_odoo() is False env._link_modules.assert_not_called() - with TemporaryDirectory() as dir_name: - # Not a dir - env.set("bootstrap", "odoo", value=f"{dir_name}/unknown") - assert env._init_odoo() is False - env._link_modules.assert_not_called() + dir_name = mkdtemp() + # Not a dir + env.set(["bootstrap", "odoo"], value="{}/unknown".format(dir_name)) + assert env._init_odoo() is False + env._link_modules.assert_not_called() - env.set("bootstrap", "odoo", value=dir_name) - assert dir_name not in sys.path - assert env._init_odoo() == dir_name - assert dir_name in sys.path - env._link_modules.assert_called_once() + env.set(["bootstrap", "odoo"], value=dir_name) + assert dir_name not in sys.path + assert env._init_odoo() == dir_name + assert dir_name in sys.path + env._link_modules.assert_called_once() def test_env(env): @@ -132,25 +138,25 @@ def test_env(env): def test_link_modules(env): - with TemporaryDirectory() as dir_name: - os.makedirs(f"{dir_name}/abc") - link_path = os.path.join(base.ADDON_PATH, "abc") + dir_name = mkdtemp() + os.makedirs("{}/abc".format(dir_name)) + link_path = os.path.join(base.ADDON_PATH, "abc") - repo = {} - env._config = {"repos": {dir_name: repo}} - env._link_modules() - assert not os.path.islink(link_path) + repo = {} + env._config = {"repos": {dir_name: repo}} + env._link_modules() + assert not os.path.islink(link_path) - with open(f"{dir_name}/abc/__manifest__.py", "w+", encoding="utf-8"): - pass + with open("{}/abc/__manifest__.py".format(dir_name), "w+"): + pass - env._link_modules() - assert os.path.islink(link_path) + env._link_modules() + assert os.path.islink(link_path) - repo["modules"] = ["abc"] - env._link_modules() - assert os.path.islink(link_path) + repo["modules"] = ["abc"] + env._link_modules() + assert os.path.islink(link_path) - repo["modules"] = ["!abc"] - env._link_modules() - assert not os.path.islink(link_path) + repo["modules"] = ["!abc"] + env._link_modules() + assert not os.path.islink(link_path) diff --git a/tests/test_freeze.py b/tests/test_freeze.py index a1988a5..ad4c5ed 100644 --- a/tests/test_freeze.py +++ b/tests/test_freeze.py @@ -1,12 +1,12 @@ +# -*- coding: utf-8 -*- # © 2021 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os from tempfile import NamedTemporaryFile -from unittest import mock +import mock import pytest - from doblib.freeze import FreezeEnvironment @@ -42,29 +42,6 @@ def test_freeze(env): repo_mock.assert_called() -@mock.patch("builtins.input") -def test_mode(input_mock, env): - input_mock.return_value = "n" - - assert env._freeze_mode("unknown") is True - input_mock.assert_not_called() - - with NamedTemporaryFile() as fp: - assert env._freeze_mode(fp.name, mode="skip") is False - input_mock.assert_not_called() - - assert env._freeze_mode(fp.name, "all") is True - input_mock.assert_not_called() - - assert env._freeze_mode(fp.name, "ask") is False - input_mock.assert_called() - - input_mock.reset_mock() - input_mock.return_value = "Y" - assert env._freeze_mode(fp.name, "ask") is True - input_mock.assert_called() - - def test_freeze_packages(env): env._freeze_mode = mock.MagicMock(return_value=True) with NamedTemporaryFile() as fp: @@ -85,7 +62,7 @@ def test_freeze_repositories(call_mock, env): assert fp.read() with NamedTemporaryFile() as fp: - env.set("repos", value={}) + env.set(["repos"], value={}) env._freeze_repositories(fp.name) fp.seek(0) assert not fp.read() diff --git a/tests/test_main.py b/tests/test_main.py index 1b69c09..a1903bd 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,11 +1,15 @@ +# -*- coding: utf-8 -*- # © 2021 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import sys from tempfile import NamedTemporaryFile -from unittest.mock import MagicMock, patch from doblib.__main__ import main +from mock import ( + MagicMock, + patch, +) @patch("sys.exit") @@ -49,11 +53,6 @@ def test_config(config_mock, exit_mock): mock.assert_called_once_with(fp.name) mock.return_value.ci.assert_called_once_with("flake8", ["additional"]) - mock.reset_mock() - main(["eslint", "additional"]) - mock.assert_called_once_with(fp.name) - mock.return_value.ci.assert_called_once_with("eslint", ["additional"]) - mock.reset_mock() main(["pylint", "additional"]) mock.assert_called_once_with(fp.name) @@ -83,7 +82,7 @@ def test_config(config_mock, exit_mock): "show-closed-prs", ["additional"] ) - assert exit_mock.call_count == 12 + assert exit_mock.call_count == 11 @patch("doblib.__main__.load_arguments") diff --git a/tests/test_migrate.py b/tests/test_migrate.py index 129082a..8ce6860 100644 --- a/tests/test_migrate.py +++ b/tests/test_migrate.py @@ -1,12 +1,12 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Ruben Ortlam (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os import sys -from unittest import mock +import mock import pytest - from doblib import utils from doblib.migrate import MigrateEnvironment @@ -23,9 +23,10 @@ def env(): @mock.patch("doblib.aggregate.get_repos", return_value=[{"cwd": "unknown"}]) def test_migrate(repos, env): odoo = sys.modules["odoo"] = mock.MagicMock() - tools = sys.modules["odoo.tools"] = mock.MagicMock() - tools.config.__getitem__.return_value = "odoo" + utils.module_mock(odoo, ["odoo.cli", "odoo.tools"]) sys.modules["odoo.release"] = odoo.release + + odoo.tools.config.__getitem__.return_value = "odoo" odoo.release.version_info = (14, 0) env.generate_config = mock.MagicMock() env._init_odoo = mock.MagicMock(return_value=False) diff --git a/tests/test_module.py b/tests/test_module.py index a970420..3373e0a 100644 --- a/tests/test_module.py +++ b/tests/test_module.py @@ -1,15 +1,21 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import argparse import os import sys -from unittest import mock +import mock import pytest - -from doblib import base -from doblib.module import ModuleEnvironment, no_flags +from doblib import ( + base, + utils, +) +from doblib.module import ( + ModuleEnvironment, + no_flags, +) @pytest.fixture @@ -61,19 +67,19 @@ def test_run_migration_sql(env): def test_get_modules(env): - env.set(base.SECTION, "mode", value="prod") + env.set([base.SECTION, "mode"], value="prod") assert env._get_modules() == {"normal"} - env.set(base.SECTION, "mode", value="staging") + env.set([base.SECTION, "mode"], value="staging") assert env._get_modules() == {"normal", "staging", "dev_staging"} - env.set(base.SECTION, "mode", value="dev") + env.set([base.SECTION, "mode"], value="dev") assert env._get_modules() == {"normal", "dev", "dev_staging"} - env.set(base.SECTION, "mode", value="dev,staging") + env.set([base.SECTION, "mode"], value="dev,staging") assert env._get_modules() == {"normal", "dev", "dev_staging", "staging"} - env.set("modules", value=[{}]) + env.set(["modules"], value=[{}]) with pytest.raises(TypeError): env._get_modules() @@ -85,7 +91,7 @@ def test_get_installed_modules(env): def test_install_all(env): odoo = sys.modules["odoo"] = mock.MagicMock() - sys.modules["odoo.tools"] = mock.MagicMock() + utils.module_mock(odoo, ["odoo.modules", "odoo.tools"]) env.install_all("odoo", ["module"]) odoo.modules.registry.Registry.new.assert_called_once_with( @@ -94,13 +100,13 @@ def test_install_all(env): force_demo=False, ) - env.set("odoo", "options", "load_language", value=["en_US"]) + env.set(["odoo", "options", "load_language"], value=["en_US"]) env.install_all("odoo", ["module"]) def test_update_all(env): odoo = sys.modules["odoo"] = mock.MagicMock() - sys.modules["odoo.tools"] = mock.MagicMock() + utils.module_mock(odoo, ["odoo.modules", "odoo.tools"]) env.update_specific("odoo", installed=True) odoo.modules.registry.Registry.new.assert_called_once_with( @@ -111,7 +117,8 @@ def test_update_all(env): def test_update_listed(env): odoo = sys.modules["odoo"] = mock.MagicMock() - sys.modules["odoo.tools"] = mock.MagicMock() + utils.module_mock(odoo, ["odoo.modules", "odoo.tools"]) + env._get_modules = mock.MagicMock() env.update_specific("odoo", listed=True) @@ -142,9 +149,12 @@ def test_update_changed(env): def test_update(env): # Quite complex and we have to mock plenty of stuff odoo = sys.modules["odoo"] = mock.MagicMock() - tools = sys.modules["odoo.tools"] = mock.MagicMock() - sys.modules["odoo.release"] = odoo.release - tools.config.__getitem__.return_value = "odoo" + utils.module_mock( + odoo, + ["odoo.cli", "odoo.modules.db", "odoo.release", "odoo.sql_db", "odoo.tools"], + ) + + odoo.tools.config.__getitem__.return_value = "odoo" odoo.release.version_info = (14, 0) env.generate_config = mock.MagicMock() env._get_installed_modules = mock.MagicMock() diff --git a/tests/test_run.py b/tests/test_run.py index ef55b38..f960f47 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -1,13 +1,13 @@ +# -*- coding: utf-8 -*- # © 2021 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import os import sys from tempfile import NamedTemporaryFile -from unittest import mock +import mock import pytest - from doblib import base from doblib.run import RunEnvironment @@ -20,20 +20,24 @@ def env(): cur = os.getcwd() os.chdir("tests/environment/") - env = RunEnvironment("odoo.local.yaml") - os.chdir(cur) + try: + env = RunEnvironment("odoo.local.yaml") + finally: + os.chdir(cur) return env def test_shell(env): - shell = sys.modules["odoo.cli.shell"] = mock.MagicMock() + module = sys.modules["odoo"] = mock.MagicMock() + sys.modules["odoo.cli"] = module.cli + shell = sys.modules["odoo.cli.shell"] = module.cli.shell assert env.shell() is False shell.Shell.assert_not_called() shell.Shell.return_value.run.return_value = 42 - env._init_odoo = mock.MagicMock(return_value=True) + env._init_odoo = mock.MagicMock(return_value="/tmp/path") assert env.shell() == 42 shell.Shell.assert_called_once() assert sys.argv == [""] @@ -49,29 +53,29 @@ def test_shell(env): def test_start(call_mock, env): assert env.start() is False - env._init_odoo = mock.MagicMock(return_value=True) + env._init_odoo = mock.MagicMock(return_value="/tmp/path") assert env.start() == 42 call_mock.assert_called_once() @mock.patch("doblib.utils.call") def test_start_with_debugger(call_mock, env): - def check_debugger(debugger, *args, **kwargs): + def check_debugger(debugger, args, **kwargs): if debugger == "dev" and "--dev=all" not in args: raise ValueError("Missing dev=all") - elif debugger in DEBUGGERS and args[1:3] != ("-m", debugger): + elif debugger in DEBUGGERS and args[1:3] != ["-m", debugger]: raise ValueError("Missing debugpy integration") return 128 - env._init_odoo = mock.MagicMock(return_value=True) + env._init_odoo = mock.MagicMock(return_value="/tmp/path") call_mock.side_effect = lambda *a, **kw: check_debugger("debugpy", *a, **kw) - env.set(base.SECTION, "debugger", value="debugpy") + env.set([base.SECTION, "debugger"], value="debugpy") assert env.start() == 128 call_mock.assert_called_once() call_mock.reset_mock() call_mock.side_effect = lambda *a, **kw: check_debugger("dev", *a, **kw) - env.set(base.SECTION, "debugger", value="dev") + env.set([base.SECTION, "debugger"], value="dev") assert env.start() == 128 call_mock.assert_called_once() diff --git a/tests/test_utils.py b/tests/test_utils.py index 50851d2..00e97ae 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,12 +1,12 @@ +# -*- coding: utf-8 -*- # © 2021-2022 Florian Kantelberg (initOS GmbH) # License Apache-2.0 (http://www.apache.org/licenses/). import argparse -from unittest.mock import patch import pytest - from doblib import utils +from mock import patch def test_merge(): @@ -73,8 +73,8 @@ def test_config_file(mock): def test_call(): - output = utils.call("ls") + output = utils.call(["ls"]) assert isinstance(output, str) and output - output = utils.call("ls", pipe=False) + output = utils.call(["ls"], pipe=False) assert output == 0 diff --git a/tox.ini b/tox.ini index 6e6f491..408c517 100644 --- a/tox.ini +++ b/tox.ini @@ -1,16 +1,16 @@ [tox] -envlist = clean,py3,report +envlist = clean,py27,report +isolated_build = True [testenv] deps = pytest pytest-cov - coverage + coverage<6.0 + mock depends: - py3: clean - report: py3 + py27: clean + report: py27 commands = pytest --cov {envsitepackagesdir}/doblib --cov-append -p no:odoo -setenv = - DISABLE_PYTEST_ODOO = 1 [testenv:report] skip_install = true