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
5 changes: 3 additions & 2 deletions .coveragerc
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[run]
omit = tests/*
setup.py
omit =
tests/*
**/__manifest__.py
168 changes: 139 additions & 29 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,155 @@ on:
pull_request:

jobs:
# TODO
# pre-commit:
# runs-on: ubuntu-22.04
# steps:
# - uses: actions/checkout@v2
# - uses: actions/setup-python@v2
# with:
# python-version: "3.11"
# - uses: pre-commit/action@v2.0.0
test:
runs-on: ${{ matrix.os }}
runs-on: ubuntu-latest
services:
postgres:
image: postgres:14
env:
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

strategy:
fail-fast: false
matrix:
include:
- PYTHON_VERSION: "3.8"
os: ubuntu-latest
- PYTHON_VERSION: "3.9"
os: ubuntu-latest
- PYTHON_VERSION: "3.10"
os: ubuntu-latest
- PYTHON_VERSION: "3.11"
os: ubuntu-latest
- PYTHON_VERSION: "3.12"
os: ubuntu-latest
# Odoo 14
- python: "3.8"
tox_env: "py38-odoo14"
- python: "3.9"
tox_env: "py39-odoo14"
- python: "3.10"
tox_env: "py310-odoo14"

# Odoo 15
- python: "3.9"
tox_env: "py39-odoo15"
- python: "3.10"
tox_env: "py310-odoo15"
- python: "3.11"
tox_env: "py311-odoo15"

# Odoo 16
- python: "3.10"
tox_env: "py310-odoo16"
- python: "3.11"
tox_env: "py311-odoo16"
- python: "3.12"
tox_env: "py312-odoo16"

# Odoo 17
- python: "3.11"
tox_env: "py311-odoo17"
- python: "3.12"
tox_env: "py312-odoo17"
- python: "3.13"
tox_env: "py313-odoo17"

# Odoo 18
- python: "3.12"
tox_env: "py312-odoo18"
- python: "3.13"
tox_env: "py313-odoo18"

# Odoo 19
- python: "3.13"
tox_env: "py313-odoo19"

# Unit Tests
- python: "3.8"
tox_env: "py38-unit"
- python: "3.9"
tox_env: "py39-unit"
- python: "3.10"
tox_env: "py310-unit"
- python: "3.11"
tox_env: "py311-unit"
- python: "3.12"
tox_env: "py312-unit"
- python: "3.13"
tox_env: "py313-unit"

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v4
uses: actions/setup-python@v6
with:
python-version: ${{matrix.PYTHON_VERSION}}
- name: Configure TOX
python-version: ${{ matrix.python }}

- name: Install System Dependencies
run: |
pip install pip --upgrade
pip install tox codecov tox-gh-actions wheel
- name: Generate Report
sudo apt-get update
sudo apt-get install -y libldap2-dev libsasl2-dev libpq-dev

- name: Create Database
env:
PGPASSWORD: odoo
run: |
psql -h localhost -U odoo -d postgres -c "CREATE USER $(whoami) WITH SUPERUSER;"

- name: Install Tox
run: pip install tox

- name: Run Tests
env:
# Odoo requires these to connect to the service
PGHOST: localhost
PGPORT: 5432
PGUSER: odoo
PGPASSWORD: odoo
ODOO_DB_NAME: pytest_odoo_test
ODOO_RC: odoo.ci.conf
run: tox -e ${{ matrix.tox_env }}

- name: Rename Coverage File
run: mv .coverage .coverage.${{ matrix.tox_env }}

- name: Upload Coverage Artifact
uses: actions/upload-artifact@v6
with:
name: coverage-${{ matrix.tox_env }}
path: .coverage.${{ matrix.tox_env }}
if-no-files-found: ignore
include-hidden-files: true

coverage:
name: Coverage
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.10"

- name: Install Coverage
run: pip install coverage

- name: Download Artifacts
uses: actions/download-artifact@v7

- name: Combine Coverage
run: |
tox run
# Artifacts are downloaded to subdirectories. Flatten them.
find . -name '.coverage.*' -exec mv {} . \;
coverage combine
coverage report
coverage xml

- name: Upload Coverage to Codecov
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # required
token: ${{ secrets.CODECOV_TOKEN }}
file: ./coverage.xml
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,14 @@ 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.

Prerequisites
-------------

To run the integration tests (which install Odoo dependencies), you need to have the following system dependencies installed (Debian/Ubuntu):

.. code-block:: bash

sudo apt-get install python3-dev libxml2-dev libxslt1-dev libsasl2-dev libldap2-dev libssl-dev libpq-dev postgresql-client
5 changes: 5 additions & 0 deletions odoo.ci.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[options]
db_host = localhost
db_port = 5432
db_user = odoo
db_password = odoo
66 changes: 54 additions & 12 deletions pytest_odoo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,24 @@

import ast
import os
import socket
import signal
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
import pytest

import odoo
from odoo import release
if release.version_info >= (19,0):
import odoo.api, odoo.http, odoo.modules, odoo.release,odoo.tools, odoo.service, odoo.sql_db, odoo.tests


def pytest_addoption(parser):
Expand Down Expand Up @@ -88,16 +93,9 @@ 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)
# odoo.service.server.start() modifies the SIGINT signal by its own
# one which in fact prevents us to stop anthem with Ctrl-c.
# Restore the default one.
signal.signal(signal.SIGINT, signal.default_int_handler)

if odoo.release.version_info >= (18,):
odoo.modules.module.current_test = True

if odoo.release.version_info < (15,):
# Refactor in Odoo 15, not needed anymore
Expand All @@ -108,12 +106,30 @@ def pytest_cmdline_main(config):
else:
yield

def _get_available_random_port():
"""Get an available random port."""
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("127.0.0.1", 0))
return s.getsockname()[1]

@pytest.fixture(scope="module", autouse=True)
def load_http(request):
if request.config.getoption("--odoo-http"):
odoo.service.server.start(stop=True)
signal.signal(signal.SIGINT, signal.default_int_handler)
odoo.tools.config['http_port'] = _get_available_random_port()
if odoo.release.version_info >= (15,):
from odoo import http
from odoo.service import server
server.load_server_wide_modules()
server.server = server.ThreadedServer(http.root)
server.server.start(stop=False)
signal.signal(signal.SIGINT, signal.default_int_handler)
yield
server.server.stop()
else:
odoo.service.server.start(stop=True)
yield
else:
yield

@contextmanager
def _shared_filestore(original_db_name, db_name):
Expand Down Expand Up @@ -155,7 +171,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 @@ -204,6 +220,21 @@ 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
Expand Down Expand Up @@ -241,3 +272,14 @@ def pytest_ignore_collect(collection_path: Path) -> Optional[bool]:
# installable = False, do not collect this
return True
return None


def pytest_runtest_setup(item):
if hasattr(item, "instance"):
from odoo.tests.common import HttpCase
if isinstance(item.instance, HttpCase) and not item.config.getoption("--odoo-http"):
pytest.skip(f"Test {item.name} skipped because it is an HttpCase and --odoo-http is not set")

if release.version_info >= (19,0):
test_instance = item.instance
odoo.modules.module.current_test = test_instance
3 changes: 3 additions & 0 deletions tests/mock/odoo/odoo/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
from unittest.mock import MagicMock
modules = MagicMock()
tools = MagicMock()
release = MagicMock()
release.version_info = (16, 0)
service = MagicMock()
10 changes: 2 additions & 8 deletions tests/mock/odoo/odoo/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,2 @@
from unittest.mock import MagicMock
common = MagicMock()


class BaseCase:

def run(*args, **kwargs):
super().run(*args, **kwargs)
from . import case
from .common import BaseCase, HttpCase
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
18 changes: 18 additions & 0 deletions tests/mock/odoo/odoo/tests/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
from . import case
from unittest.mock import MagicMock

get_db_name = MagicMock()


class BaseCase(case.TestCase):

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

def _call_something(self):
pass


class HttpCase(BaseCase):
pass
Loading