Skip to content

Commit 4a6e996

Browse files
authored
Merge pull request #74 from petrus-v/imp-v8-support
Support pytest > 8.0
2 parents c87b3f0 + afa9cd9 commit 4a6e996

File tree

4 files changed

+73
-103
lines changed

4 files changed

+73
-103
lines changed

README.rst

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,3 @@ You can use the ``ODOO_RC`` environment variable using an odoo configuration fil
5151
export ODOO_RC=/path/to/odoo/config.cfg
5252
pytest ...
5353

54-
55-
Known issues
56-
------------
57-
58-
Currently not compatible with pytest >= 8.0.0

pytest_odoo.py

Lines changed: 19 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
1-
# -*- coding: utf-8 -*-
21
# Copyright 2016 Camptocamp SA
32
# Copyright 2015 Odoo
3+
# @author Pierre Verkest <pierre@verkest.fr>
44
# License AGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
55

66

77
import ast
88
import os
99
import signal
10-
import sys
1110
import threading
1211
from pathlib import Path
1312
from typing import Optional
1413

1514
import _pytest
16-
import _pytest._py.error as error
1715
import _pytest.python
16+
import pytest
17+
1818
import odoo
1919
import odoo.tests
20-
import pytest
21-
from _pytest._code.code import ExceptionInfo
2220

2321

2422
def pytest_addoption(parser):
@@ -88,7 +86,7 @@ def pytest_cmdline_main(config):
8886
raise Exception(
8987
"please provide a database name in the Odoo configuration file"
9088
)
91-
89+
monkey_patch_resolve_pkg_root_and_module_name()
9290
odoo.service.server.start(preload=[], stop=True)
9391
# odoo.service.server.start() modifies the SIGINT signal by its own
9492
# one which in fact prevents us to stop anthem with Ctrl-c.
@@ -139,101 +137,31 @@ def enable_odoo_test_flag():
139137
yield
140138
odoo.tools.config['test_enable'] = False
141139

140+
def monkey_patch_resolve_pkg_root_and_module_name():
141+
original_resolve_pkg_root_and_module_name = _pytest.pathlib.resolve_pkg_root_and_module_name
142142

143-
# Original code of xmo-odoo:
144-
# https://github.com/odoo-dev/odoo/commit/95a131b7f4eebc6e2c623f936283153d62f9e70f
145-
class OdooTestModule(_pytest.python.Module):
146-
""" Should only be invoked for paths inside Odoo addons
147-
"""
148-
149-
def _importtestmodule(self):
150-
# copy/paste/modified from original: removed sys.path injection &
151-
# added Odoo module prefixing so import within modules is correct
152-
try:
153-
pypkgpath = self.fspath.pypkgpath()
154-
pkgroot = pypkgpath.dirpath()
155-
sep = self.fspath.sep
156-
names = self.fspath.new(ext="").relto(pkgroot).split(sep)
157-
if names[-1] == "__init__":
158-
names.pop()
159-
modname = ".".join(names)
160-
# for modules in odoo/addons, since there is a __init__ the
161-
# module name is already fully qualified (maybe?)
162-
if (not modname.startswith('odoo.addons.')
163-
and modname != 'odoo.addons'
164-
and modname != 'odoo'):
165-
modname = 'odoo.addons.' + modname
166-
167-
__import__(modname)
168-
mod = sys.modules[modname]
169-
if self.fspath.basename == "__init__.py":
170-
# we don't check anything as we might
171-
# we in a namespace package ... too icky to check
172-
return mod
173-
modfile = mod.__file__
174-
if modfile[-4:] in ('.pyc', '.pyo'):
175-
modfile = modfile[:-1]
176-
elif modfile.endswith('$py.class'):
177-
modfile = modfile[:-9] + '.py'
178-
if modfile.endswith(os.path.sep + "__init__.py"):
179-
if self.fspath.basename != "__init__.py":
180-
modfile = modfile[:-12]
181-
try:
182-
issame = self.fspath.samefile(modfile)
183-
except error.ENOENT:
184-
issame = False
185-
if not issame:
186-
raise self.fspath.ImportMismatchError(modname, modfile, self)
187-
except SyntaxError as e:
188-
raise self.CollectError(
189-
ExceptionInfo.from_current().getrepr(style="short")
190-
) from e
191-
except self.fspath.ImportMismatchError:
192-
e = sys.exc_info()[1]
193-
raise self.CollectError(
194-
"import file mismatch:\n"
195-
"imported module %r has this __file__ attribute:\n"
196-
" %s\n"
197-
"which is not the same as the test file we want to collect:\n"
198-
" %s\n"
199-
"HINT: remove __pycache__ / .pyc files and/or use a "
200-
"unique basename for your test file modules" % e.args
201-
)
202-
self.config.pluginmanager.consider_module(mod)
203-
return mod
204-
205-
def __repr__(self):
206-
return "<Module %r>" % (getattr(self, "name", None), )
207-
208-
209-
class OdooTestPackage(_pytest.python.Package, OdooTestModule):
210-
"""Package with odoo module lookup.
211-
212-
Any python module inside the package will be imported with
213-
the prefix `odoo.addons`.
143+
def resolve_pkg_root_and_module_name(
144+
path: Path, *, consider_namespace_packages: bool = False
145+
) -> "tuple[Path, str]":
146+
pkg_root, module_name = original_resolve_pkg_root_and_module_name(
147+
path, consider_namespace_packages=consider_namespace_packages
148+
)
214149

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

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

222-
223-
def pytest_pycollect_makemodule(module_path, path, parent):
224-
if not _find_manifest_path(module_path):
225-
return None
226-
if path.basename == "__init__.py":
227-
return OdooTestPackage.from_parent(parent, path=Path(path))
228-
else:
229-
return OdooTestModule.from_parent(parent, path=Path(path))
157+
_pytest.pathlib.resolve_pkg_root_and_module_name= resolve_pkg_root_and_module_name
230158

231159

232160
def _find_manifest_path(collection_path: Path) -> Path:
233161
"""Try to locate an Odoo manifest file in the collection path."""
234162
# check if collection_path is an addon directory
235163
path = collection_path
236-
for _ in range(0, 5):
164+
for _ in range(5):
237165
if (path.parent / "__manifest__.py").is_file():
238166
break
239167
path = path.parent

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
include_package_data=True,
1717
platforms='any',
1818
install_requires=[
19-
'pytest>=7.2.0,<8.0.0',
19+
"pytest>=8"
2020
],
2121
setup_requires=[
2222
'setuptools_scm',

tests/test_pytest_odoo.py

Lines changed: 53 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,39 @@
1-
from unittest import TestCase
21
import tempfile
32
from contextlib import contextmanager
4-
from pytest_odoo import _find_manifest_path
53
from pathlib import Path
4+
from unittest import TestCase
5+
6+
from _pytest import pathlib as pytest_pathlib
7+
from pytest_odoo import (
8+
_find_manifest_path,
9+
monkey_patch_resolve_pkg_root_and_module_name,
10+
)
11+
612

713
class TestPytestOdoo(TestCase):
814

915
@contextmanager
10-
def fake_module(self):
16+
def fake_module(self, with_manifest=True, using_addons_namespace=False):
1117
directory = tempfile.TemporaryDirectory()
1218
try:
1319
module_path = Path(directory.name)
14-
manifest_path = module_path / "__manifest__.py"
15-
manifest_path.touch()
20+
files = []
21+
if using_addons_namespace:
22+
files.append(module_path / "odoo" / "__init__.py")
23+
files.append(module_path / "odoo" / "addons" / "__init__.py")
24+
module_path = module_path / "odoo" / "addons" / "my_module"
25+
module_path.mkdir(parents=True, exist_ok=True)
26+
manifest_path = None
27+
if with_manifest:
28+
manifest_path = module_path / "__manifest__.py"
29+
files.append(manifest_path)
1630
test_path = module_path / "tests" / "test_module.py"
1731
test_path.parent.mkdir(parents=True, exist_ok=True)
18-
test_path.touch()
32+
files.append(test_path)
33+
files.append(module_path / "__init__.py")
34+
files.append(module_path / "tests" / "__init__.py")
35+
for file_path in files:
36+
file_path.touch()
1937
yield (module_path, manifest_path, test_path,)
2038
finally:
2139
directory.cleanup()
@@ -37,3 +55,32 @@ def test_find_manifest_path_from_brother(self):
3755
test = module_path / "test_something.py"
3856
test.touch()
3957
self.assertEqual(_find_manifest_path(test), manifest_path)
58+
59+
def test_resolve_pkg_root_and_module_name(self):
60+
monkey_patch_resolve_pkg_root_and_module_name()
61+
with self.fake_module() as (module_path, _, test_path):
62+
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
63+
self.assertEqual(
64+
module_name,
65+
f"odoo.addons.{module_path.name}.tests.test_module"
66+
)
67+
68+
def test_resolve_pkg_root_and_module_name_not_odoo_module(self):
69+
monkey_patch_resolve_pkg_root_and_module_name()
70+
71+
with self.fake_module(with_manifest=False) as (module_path, _, test_path):
72+
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
73+
self.assertEqual(
74+
module_name,
75+
f"{module_path.name}.tests.test_module"
76+
)
77+
78+
def test_resolve_pkg_root_and_module_name_namespace_ok(self):
79+
monkey_patch_resolve_pkg_root_and_module_name()
80+
81+
with self.fake_module(with_manifest=True, using_addons_namespace=True) as (module_path, _, test_path):
82+
pkg_root, module_name = pytest_pathlib.resolve_pkg_root_and_module_name(test_path)
83+
self.assertEqual(
84+
module_name,
85+
"odoo.addons.my_module.tests.test_module"
86+
)

0 commit comments

Comments
 (0)