263 lines
9.5 KiB
Python
263 lines
9.5 KiB
Python
# Copyright 2017 Camptocamp SA
|
|
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl.html)
|
|
|
|
import copy
|
|
import sys
|
|
import unittest
|
|
from contextlib import contextmanager
|
|
|
|
import odoo
|
|
from odoo import api
|
|
from odoo.tests import common
|
|
|
|
from odoo.addons.component.core import ComponentRegistry, MetaComponent, _get_addon_name
|
|
|
|
|
|
@contextmanager
|
|
def new_rollbacked_env():
|
|
registry = odoo.registry(common.get_db_name())
|
|
uid = odoo.SUPERUSER_ID
|
|
cr = registry.cursor()
|
|
try:
|
|
yield api.Environment(cr, uid, {})
|
|
finally:
|
|
cr.rollback() # we shouldn't have to commit anything
|
|
cr.close()
|
|
|
|
|
|
class ComponentMixin:
|
|
@classmethod
|
|
def setUpComponent(cls):
|
|
with new_rollbacked_env() as env:
|
|
builder = env["component.builder"]
|
|
# build the components of every installed addons
|
|
comp_registry = builder._init_global_registry()
|
|
cls._components_registry = comp_registry
|
|
# ensure that we load only the components of the 'installed'
|
|
# modules, not 'to install', which means we load only the
|
|
# dependencies of the tested addons, not the siblings or
|
|
# chilren addons
|
|
builder.build_registry(comp_registry, states=("installed",))
|
|
# build the components of the current tested addon
|
|
current_addon = _get_addon_name(cls.__module__)
|
|
env["component.builder"].load_components(current_addon)
|
|
if hasattr(cls, "env"):
|
|
cls.env.context = dict(
|
|
cls.env.context, components_registry=cls._components_registry
|
|
)
|
|
|
|
# pylint: disable=W8106
|
|
def setUp(self):
|
|
# should be ready only during tests, never during installation
|
|
# of addons
|
|
self._components_registry.ready = True
|
|
|
|
@self.addCleanup
|
|
def notready():
|
|
self._components_registry.ready = False
|
|
|
|
|
|
class TransactionComponentCase(common.TransactionCase, ComponentMixin):
|
|
"""A TransactionCase that loads all the components
|
|
|
|
It it used like an usual Odoo's TransactionCase, but it ensures
|
|
that all the components of the current addon and its dependencies
|
|
are loaded.
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.setUpComponent()
|
|
|
|
# pylint: disable=W8106
|
|
def setUp(self):
|
|
# resolve an inheritance issue (common.TransactionCase does not call
|
|
# super)
|
|
common.TransactionCase.setUp(self)
|
|
ComponentMixin.setUp(self)
|
|
# There's no env on setUpClass of TransactionCase, must do it here.
|
|
self.env.context = dict(
|
|
self.env.context, components_registry=self._components_registry
|
|
)
|
|
|
|
|
|
class SavepointComponentCase(common.SavepointCase, ComponentMixin):
|
|
"""A SavepointCase that loads all the components
|
|
|
|
It it used like an usual Odoo's SavepointCase, but it ensures
|
|
that all the components of the current addon and its dependencies
|
|
are loaded.
|
|
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.setUpComponent()
|
|
|
|
# pylint: disable=W8106
|
|
def setUp(self):
|
|
# resolve an inheritance issue (common.SavepointCase does not call
|
|
# super)
|
|
common.SavepointCase.setUp(self)
|
|
ComponentMixin.setUp(self)
|
|
|
|
|
|
class ComponentRegistryCase(unittest.TestCase, common.MetaCase("DummyCase", (), {})):
|
|
"""This test case can be used as a base for writings tests on components
|
|
|
|
This test case is meant to test components in a special component registry,
|
|
where you want to have maximum control on which components are loaded
|
|
or not, or when you want to create additional components in your tests.
|
|
|
|
If you only want to *use* the components of the tested addon in your tests,
|
|
then consider using one of:
|
|
|
|
* :class:`TransactionComponentCase`
|
|
* :class:`SavepointComponentCase`
|
|
|
|
This test case creates a special
|
|
:class:`odoo.addons.component.core.ComponentRegistry` for the purpose of
|
|
the tests. By default, it loads all the components of the dependencies, but
|
|
not the components of the current addon (which you have to handle
|
|
manually). In your tests, you can add more components in 2 manners.
|
|
|
|
All the components of an Odoo module::
|
|
|
|
self._load_module_components('connector')
|
|
|
|
Only specific components::
|
|
|
|
self._build_components(MyComponent1, MyComponent2)
|
|
|
|
Note: for the lookups of the components, the default component
|
|
registry is a global registry for the database. Here, you will
|
|
need to explicitly pass ``self.comp_registry`` in the
|
|
:class:`~odoo.addons.component.core.WorkContext`::
|
|
|
|
work = WorkContext(model_name='res.users',
|
|
collection='my.collection',
|
|
components_registry=self.comp_registry)
|
|
|
|
Or::
|
|
|
|
collection_record = self.env['my.collection'].browse(1)
|
|
with collection_record.work_on(
|
|
'res.partner',
|
|
components_registry=self.comp_registry) as work:
|
|
|
|
"""
|
|
|
|
if sys.version_info < (3, 8):
|
|
# Copy/paste from
|
|
# https://github.com/odoo/odoo/blob/0218d870d319af4f263d5e9aa324990f7cc90139/
|
|
# odoo/tests/common.py#L248-L268
|
|
# Partial backport of bpo-24412, merged in CPython 3.8
|
|
_class_cleanups = []
|
|
|
|
@classmethod
|
|
def addClassCleanup(cls, function, *args, **kwargs):
|
|
"""Same as addCleanup, except the cleanup items are called even if
|
|
setUpClass fails (unlike tearDownClass). Backport of bpo-24412."""
|
|
cls._class_cleanups.append((function, args, kwargs))
|
|
|
|
@classmethod
|
|
def doClassCleanups(cls):
|
|
"""Execute all class cleanup functions.
|
|
Normally called for you after tearDownClass.
|
|
Backport of bpo-24412."""
|
|
cls.tearDown_exceptions = []
|
|
while cls._class_cleanups:
|
|
function, args, kwargs = cls._class_cleanups.pop()
|
|
try:
|
|
function(*args, **kwargs)
|
|
except Exception:
|
|
cls.tearDown_exceptions.append(sys.exc_info())
|
|
|
|
@staticmethod
|
|
def _setup_registry(class_or_instance):
|
|
# keep the original classes registered by the metaclass
|
|
# so we'll restore them at the end of the tests, it avoid
|
|
# to pollute it with Stub / Test components
|
|
class_or_instance._original_components = copy.deepcopy(
|
|
MetaComponent._modules_components
|
|
)
|
|
|
|
# it will be our temporary component registry for our test session
|
|
class_or_instance.comp_registry = ComponentRegistry()
|
|
|
|
# it builds the 'final component' for every component of the
|
|
# 'component' addon and push them in the component registry
|
|
class_or_instance.comp_registry.load_components("component")
|
|
# build the components of every installed addons already installed
|
|
# but the current addon (when running with pytest/nosetest, we
|
|
# simulate the --test-enable behavior by excluding the current addon
|
|
# which is in 'to install' / 'to upgrade' with --test-enable).
|
|
current_addon = _get_addon_name(class_or_instance.__module__)
|
|
with new_rollbacked_env() as env:
|
|
env["component.builder"].build_registry(
|
|
class_or_instance.comp_registry,
|
|
states=("installed",),
|
|
exclude_addons=[current_addon],
|
|
)
|
|
|
|
# Fake that we are ready to work with the registry
|
|
# normally, it is set to True and the end of the build
|
|
# of the components. Here, we'll add components later in
|
|
# the components registry, but we don't mind for the tests.
|
|
class_or_instance.comp_registry.ready = True
|
|
if hasattr(class_or_instance, "env"):
|
|
# let it propagate via ctx
|
|
class_or_instance.env.context = dict(
|
|
class_or_instance.env.context,
|
|
components_registry=class_or_instance.comp_registry,
|
|
)
|
|
|
|
@staticmethod
|
|
def _teardown_registry(class_or_instance):
|
|
# restore the original metaclass' classes
|
|
MetaComponent._modules_components = class_or_instance._original_components
|
|
|
|
def _load_module_components(self, module):
|
|
self.comp_registry.load_components(module)
|
|
|
|
def _build_components(self, *classes):
|
|
for cls in classes:
|
|
cls._build_component(self.comp_registry)
|
|
|
|
|
|
class TransactionComponentRegistryCase(common.TransactionCase, ComponentRegistryCase):
|
|
"""Adds Odoo Transaction in the base Component TestCase"""
|
|
|
|
# pylint: disable=W8106
|
|
def setUp(self):
|
|
# resolve an inheritance issue (common.TransactionCase does not use
|
|
# super)
|
|
common.TransactionCase.setUp(self)
|
|
ComponentRegistryCase._setup_registry(self)
|
|
self.collection = self.env["collection.base"]
|
|
|
|
def tearDown(self):
|
|
ComponentRegistryCase._teardown_registry(self)
|
|
common.TransactionCase.tearDown(self)
|
|
|
|
|
|
class SavepointComponentRegistryCase(common.SavepointCase, ComponentRegistryCase):
|
|
"""Adds Odoo Transaction with Savepoint in the base Component TestCase"""
|
|
|
|
# pylint: disable=W8106
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
# resolve an inheritance issue (common.SavepointCase does not use
|
|
# super)
|
|
common.SavepointCase.setUpClass()
|
|
ComponentRegistryCase._setup_registry(cls)
|
|
cls.collection = cls.env["collection.base"]
|
|
|
|
@classmethod
|
|
def tearDownClass(cls):
|
|
ComponentRegistryCase._teardown_registry(cls)
|
|
common.SavepointCase.tearDownClass()
|