Skip to content
Open
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
os: ubuntu-latest
- PYTHON_VERSION: "3.12"
os: ubuntu-latest
- PYTHON_VERSION: "3.13"
os: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
Expand Down
2 changes: 2 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,5 @@ You can use the ``ODOO_RC`` environment variable using an odoo configuration fil

The plugin is also compatible with distributed run provided by the `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_ library. When tests are distributed, a copy of the database is created for each worker at the start of the test session.
This is useful to avoid concurrent access to the same database, which can lead to deadlocks. The provided database is therefore used only as template. At the end of the tests, all the created databases are dropped.

The plugin is also compatible with `pytest-subtests <https://pypi.org/project/pytest-subtests/>`_ library. When test use the `subTest` context manager you'll get a nice output for each sub-tests failing.
24 changes: 21 additions & 3 deletions pytest_odoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
import subprocess
import threading
from contextlib import contextmanager
from unittest import mock
from pathlib import Path
from typing import Optional
from unittest import TestCase as UnitTestTestCase
from unittest import mock

import _pytest
import _pytest.python
Expand Down Expand Up @@ -88,6 +89,7 @@ def pytest_cmdline_main(config):
raise Exception(
"please provide a database name in the Odoo configuration file"
)
support_subtest()
disable_odoo_test_retry()
monkey_patch_resolve_pkg_root_and_module_name()
odoo.service.server.start(preload=[], stop=True)
Expand All @@ -105,7 +107,6 @@ def pytest_cmdline_main(config):
else:
yield


@pytest.fixture(scope="module", autouse=True)
def load_http(request):
if request.config.getoption("--odoo-http"):
Expand Down Expand Up @@ -152,7 +153,7 @@ def _worker_db_name():
odoo.tools.config["db_name"] = original_db_name
odoo.tools.config["dbfilter"] = f"^{original_db_name}$"


@pytest.fixture(scope='session', autouse=True)
def load_registry():
# Initialize the registry before running tests.
Expand Down Expand Up @@ -202,6 +203,22 @@ def resolve_pkg_root_and_module_name(
_pytest.pathlib.resolve_pkg_root_and_module_name= resolve_pkg_root_and_module_name


def support_subtest():
"""Odoo from version 16.0 re-define its own TestCase.subTest context manager

Odoo assume the usage of OdooTestResult which is not our case
using with pytest-odoo. So this fallback to the unitest.TestCase.subTest
Context manager
"""
try:
from odoo.tests.case import TestCase
TestCase.subTest = UnitTestTestCase.subTest
TestCase.run = UnitTestTestCase.run
except ImportError:
# Odoo <= 15.0
pass


def disable_odoo_test_retry():
"""Odoo BaseCase.run method overload TestCase.run and manage
a retry mechanism that breaks using pytest launcher.
Expand All @@ -215,6 +232,7 @@ def disable_odoo_test_retry():
# Odoo <= 15.0
pass


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
Expand Down
11 changes: 8 additions & 3 deletions tests/mock/odoo/odoo/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from . import case

from unittest.mock import MagicMock
common = MagicMock()

class BaseCase(case.TestCase):

class BaseCase:

def run(*args, **kwargs):
def run(self, *args, **kwargs):
super().run(*args, **kwargs)
self._call_something()

def _call_something(self):
pass
14 changes: 14 additions & 0 deletions tests/mock/odoo/odoo/tests/case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import contextlib


class TestCase:

@contextlib.contextmanager
def subTest(self, **kwargs):
"""Simulate odoo TestCase.subTest from version 15.0"""

def run(self, *args, **kwargs):
self._call_a_method()

def _call_a_method(self):
pass
62 changes: 49 additions & 13 deletions tests/test_pytest_odoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@
from contextlib import contextmanager
from pathlib import Path
from unittest import TestCase
from unittest.mock import patch

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


Expand Down Expand Up @@ -93,11 +95,13 @@ def test_disable_odoo_test_retry(self):

def restore_basecase_run():
BaseCase.run = original_basecase_run

self.addCleanup(restore_basecase_run)

disable_odoo_test_retry()
self.assertFalse(hasattr(BaseCase, "run"))
with patch("odoo.tests.BaseCase._call_something") as mock:
BaseCase().run()
mock.assert_not_called()


def test_disable_odoo_test_retry_ignore_run_doesnt_exists(self):
Expand All @@ -107,25 +111,57 @@ def test_disable_odoo_test_retry_ignore_run_doesnt_exists(self):

def restore_basecase_run():
BaseCase.run = original_basecase_run

self.addCleanup(restore_basecase_run)

del BaseCase.run

disable_odoo_test_retry()
self.assertFalse(hasattr(BaseCase, "run"))

disable_odoo_test_retry()

with patch("odoo.tests.BaseCase._call_something") as mock:
BaseCase().run()
mock.assert_not_called()

def test_import_error(self):
from odoo import tests
from odoo import tests

original_BaseCase = tests.BaseCase

def restore_basecase():
tests.BaseCase = original_BaseCase

self.addCleanup(restore_basecase)


del tests.BaseCase
disable_odoo_test_retry()


def test_support_subtest(self):
from odoo.tests import case

original_test_case = case.TestCase

def restore():
case.TestCase = original_test_case

self.addCleanup(restore)
support_subtest()

from odoo.tests import BaseCase
from odoo.tests.case import TestCase as OdooTestCase

self.assertTrue(OdooTestCase.subTest is TestCase.subTest)
self.assertTrue(BaseCase.subTest is TestCase.subTest)
self.assertTrue(OdooTestCase.run is TestCase.run)

def test_support_subtest_import_error(self):
from odoo.tests import case

original_odoo_test_case = case.TestCase

def restore_testcase():
case.TestCase = original_odoo_test_case

self.addCleanup(restore_testcase)

del case.TestCase
support_subtest()