Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pytest > 8.0 #74

Merged
merged 1 commit into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,3 @@ You can use the ``ODOO_RC`` environment variable using an odoo configuration fil
export ODOO_RC=/path/to/odoo/config.cfg
pytest ...


Known issues
------------

Currently not compatible with pytest >= 8.0.0
110 changes: 19 additions & 91 deletions pytest_odoo.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
# -*- coding: utf-8 -*-
# Copyright 2016 Camptocamp SA
# Copyright 2015 Odoo
# @author Pierre Verkest <pierre@verkest.fr>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)


import ast
import os
import signal
import sys
import threading
from pathlib import Path
from typing import Optional

import _pytest
import _pytest._py.error as error
import _pytest.python
import pytest

import odoo
import odoo.tests
import pytest
from _pytest._code.code import ExceptionInfo


def pytest_addoption(parser):
Expand Down Expand Up @@ -88,7 +86,7 @@ def pytest_cmdline_main(config):
raise Exception(
"please provide a database name in the Odoo configuration file"
)

monkey_patch_resolve_pkg_root_and_module_name()
odoo.service.server.start(preload=[], stop=True)
# odoo.service.server.start() modifies the SIGINT signal by its own
# one which in fact prevents us to stop anthem with Ctrl-c.
Expand Down Expand Up @@ -139,101 +137,31 @@ def enable_odoo_test_flag():
yield
odoo.tools.config['test_enable'] = False

def monkey_patch_resolve_pkg_root_and_module_name():
original_resolve_pkg_root_and_module_name = _pytest.pathlib.resolve_pkg_root_and_module_name

# Original code of xmo-odoo:
# https://github.com/odoo-dev/odoo/commit/95a131b7f4eebc6e2c623f936283153d62f9e70f
class OdooTestModule(_pytest.python.Module):
""" Should only be invoked for paths inside Odoo addons
"""

def _importtestmodule(self):
# copy/paste/modified from original: removed sys.path injection &
# added Odoo module prefixing so import within modules is correct
try:
pypkgpath = self.fspath.pypkgpath()
pkgroot = pypkgpath.dirpath()
sep = self.fspath.sep
names = self.fspath.new(ext="").relto(pkgroot).split(sep)
if names[-1] == "__init__":
names.pop()
modname = ".".join(names)
# for modules in odoo/addons, since there is a __init__ the
# module name is already fully qualified (maybe?)
if (not modname.startswith('odoo.addons.')
and modname != 'odoo.addons'
and modname != 'odoo'):
modname = 'odoo.addons.' + modname

__import__(modname)
mod = sys.modules[modname]
if self.fspath.basename == "__init__.py":
# we don't check anything as we might
# we in a namespace package ... too icky to check
return mod
modfile = mod.__file__
if modfile[-4:] in ('.pyc', '.pyo'):
modfile = modfile[:-1]
elif modfile.endswith('$py.class'):
modfile = modfile[:-9] + '.py'
if modfile.endswith(os.path.sep + "__init__.py"):
if self.fspath.basename != "__init__.py":
modfile = modfile[:-12]
try:
issame = self.fspath.samefile(modfile)
except error.ENOENT:
issame = False
if not issame:
raise self.fspath.ImportMismatchError(modname, modfile, self)
except SyntaxError as e:
raise self.CollectError(
ExceptionInfo.from_current().getrepr(style="short")
) from e
except self.fspath.ImportMismatchError:
e = sys.exc_info()[1]
raise self.CollectError(
"import file mismatch:\n"
"imported module %r has this __file__ attribute:\n"
" %s\n"
"which is not the same as the test file we want to collect:\n"
" %s\n"
"HINT: remove __pycache__ / .pyc files and/or use a "
"unique basename for your test file modules" % e.args
)
self.config.pluginmanager.consider_module(mod)
return mod

def __repr__(self):
return "<Module %r>" % (getattr(self, "name", None), )


class OdooTestPackage(_pytest.python.Package, OdooTestModule):
"""Package with odoo module lookup.

Any python module inside the package will be imported with
the prefix `odoo.addons`.
def resolve_pkg_root_and_module_name(
path: Path, *, consider_namespace_packages: bool = False
) -> "tuple[Path, str]":
pkg_root, module_name = original_resolve_pkg_root_and_module_name(
path, consider_namespace_packages=consider_namespace_packages
)

This class is used to prevent loading odoo modules in duplicate,
which happens if a module is loaded with and without the prefix.
"""
if not module_name.startswith("odoo.addons"):
manifest = _find_manifest_path(path)
if manifest and manifest.parent.name == module_name.split(".",1)[0]:
module_name = "odoo.addons." + module_name
return pkg_root, module_name

def __repr__(self):
return "<Package %r>" % (getattr(self, "name", None), )


def pytest_pycollect_makemodule(module_path, path, parent):
if not _find_manifest_path(module_path):
return None
if path.basename == "__init__.py":
return OdooTestPackage.from_parent(parent, path=Path(path))
else:
return OdooTestModule.from_parent(parent, path=Path(path))
_pytest.pathlib.resolve_pkg_root_and_module_name= resolve_pkg_root_and_module_name


def _find_manifest_path(collection_path: Path) -> Path:
"""Try to locate an Odoo manifest file in the collection path."""
# check if collection_path is an addon directory
path = collection_path
for _ in range(0, 5):
for _ in range(5):
if (path.parent / "__manifest__.py").is_file():
break
path = path.parent
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
include_package_data=True,
platforms='any',
install_requires=[
'pytest>=7.2.0,<8.0.0',
"pytest>=8"
],
setup_requires=[
'setuptools_scm',
Expand Down
59 changes: 53 additions & 6 deletions tests/test_pytest_odoo.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
from unittest import TestCase
import tempfile
from contextlib import contextmanager
from pytest_odoo import _find_manifest_path
from pathlib import Path
from unittest import TestCase

from _pytest import pathlib as pytest_pathlib
from pytest_odoo import (
_find_manifest_path,
monkey_patch_resolve_pkg_root_and_module_name,
)


class TestPytestOdoo(TestCase):

@contextmanager
def fake_module(self):
def fake_module(self, with_manifest=True, using_addons_namespace=False):
directory = tempfile.TemporaryDirectory()
try:
module_path = Path(directory.name)
manifest_path = module_path / "__manifest__.py"
manifest_path.touch()
files = []
if using_addons_namespace:
files.append(module_path / "odoo" / "__init__.py")
files.append(module_path / "odoo" / "addons" / "__init__.py")
module_path = module_path / "odoo" / "addons" / "my_module"
module_path.mkdir(parents=True, exist_ok=True)
manifest_path = None
if with_manifest:
manifest_path = module_path / "__manifest__.py"
files.append(manifest_path)
test_path = module_path / "tests" / "test_module.py"
test_path.parent.mkdir(parents=True, exist_ok=True)
test_path.touch()
files.append(test_path)
files.append(module_path / "__init__.py")
files.append(module_path / "tests" / "__init__.py")
for file_path in files:
file_path.touch()
yield (module_path, manifest_path, test_path,)
finally:
directory.cleanup()
Expand All @@ -37,3 +55,32 @@ def test_find_manifest_path_from_brother(self):
test = module_path / "test_something.py"
test.touch()
self.assertEqual(_find_manifest_path(test), manifest_path)

def test_resolve_pkg_root_and_module_name(self):
monkey_patch_resolve_pkg_root_and_module_name()
with self.fake_module() as (module_path, _, test_path):
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
self.assertEqual(
module_name,
f"odoo.addons.{module_path.name}.tests.test_module"
)

def test_resolve_pkg_root_and_module_name_not_odoo_module(self):
monkey_patch_resolve_pkg_root_and_module_name()

with self.fake_module(with_manifest=False) as (module_path, _, test_path):
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
self.assertEqual(
module_name,
f"{module_path.name}.tests.test_module"
)

def test_resolve_pkg_root_and_module_name_namespace_ok(self):
monkey_patch_resolve_pkg_root_and_module_name()

with self.fake_module(with_manifest=True, using_addons_namespace=True) as (module_path, _, test_path):
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
self.assertEqual(
module_name,
"odoo.addons.my_module.tests.test_module"
)
Loading