150 lines
4.9 KiB
Python
Executable File
150 lines
4.9 KiB
Python
Executable File
# Copyright 2019 Camptocamp
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
import doctest
|
|
import logging
|
|
import sys
|
|
from contextlib import contextmanager
|
|
|
|
import mock
|
|
|
|
from ..job import Job
|
|
|
|
|
|
class JobCounter:
|
|
def __init__(self, env):
|
|
super().__init__()
|
|
self.env = env
|
|
self.existing = self.search_all()
|
|
|
|
def count_all(self):
|
|
return len(self.search_all())
|
|
|
|
def count_created(self):
|
|
return len(self.search_created())
|
|
|
|
def count_existing(self):
|
|
return len(self.existing)
|
|
|
|
def search_created(self):
|
|
return self.search_all() - self.existing
|
|
|
|
def search_all(self):
|
|
return self.env["queue.job"].search([])
|
|
|
|
|
|
class JobMixin:
|
|
def job_counter(self):
|
|
return JobCounter(self.env)
|
|
|
|
def perform_jobs(self, jobs):
|
|
for job in jobs.search_created():
|
|
Job.load(self.env, job.uuid).perform()
|
|
|
|
|
|
@contextmanager
|
|
def mock_with_delay():
|
|
"""Context Manager mocking ``with_delay()``
|
|
|
|
Mocking this method means we can decorrelate the tests in:
|
|
|
|
* the part that delay the job with the expected arguments
|
|
* the execution of the job itself
|
|
|
|
The first kind of test does not need to actually create the jobs in the
|
|
database, as we can inspect how the Mocks were called.
|
|
|
|
The second kind of test calls directly the method decorated by ``@job``
|
|
with the arguments that we want to test.
|
|
|
|
The context manager returns 2 mocks:
|
|
* the first allow to check that with_delay() was called and with which
|
|
arguments
|
|
* the second to check which job method was called and with which arguments.
|
|
|
|
Example of test::
|
|
|
|
def test_export(self):
|
|
with mock_with_delay() as (delayable_cls, delayable):
|
|
# inside this method, there is a call
|
|
# partner.with_delay(priority=15).export_record('test')
|
|
self.record.run_export()
|
|
|
|
# check 'with_delay()' part:
|
|
self.assertEqual(delayable_cls.call_count, 1)
|
|
# arguments passed in 'with_delay()'
|
|
delay_args, delay_kwargs = delayable_cls.call_args
|
|
self.assertEqual(
|
|
delay_args, (self.env['res.partner'],)
|
|
)
|
|
self.assertDictEqual(delay_kwargs, {priority: 15})
|
|
|
|
# check what's passed to the job method 'export_record'
|
|
self.assertEqual(delayable.export_record.call_count, 1)
|
|
delay_args, delay_kwargs = delayable.export_record.call_args
|
|
self.assertEqual(delay_args, ('test',))
|
|
self.assertDictEqual(delay_kwargs, {})
|
|
|
|
An example of the first kind of test:
|
|
https://github.com/camptocamp/connector-jira/blob/0ca4261b3920d5e8c2ae4bb0fc352ea3f6e9d2cd/connector_jira/tests/test_batch_timestamp_import.py#L43-L76 # noqa
|
|
And the second kind:
|
|
https://github.com/camptocamp/connector-jira/blob/0ca4261b3920d5e8c2ae4bb0fc352ea3f6e9d2cd/connector_jira/tests/test_import_task.py#L34-L46 # noqa
|
|
|
|
"""
|
|
with mock.patch(
|
|
"odoo.addons.queue_job.models.base.DelayableRecordset",
|
|
name="DelayableRecordset",
|
|
spec=True,
|
|
) as delayable_cls:
|
|
# prepare the mocks
|
|
delayable = mock.MagicMock(name="DelayableBinding")
|
|
delayable_cls.return_value = delayable
|
|
yield delayable_cls, delayable
|
|
|
|
|
|
class OdooDocTestCase(doctest.DocTestCase):
|
|
"""
|
|
We need a custom DocTestCase class in order to:
|
|
- define test_tags to run as part of standard tests
|
|
- output a more meaningful test name than default "DocTestCase.runTest"
|
|
"""
|
|
|
|
def __init__(self, doctest, optionflags=0, setUp=None, tearDown=None, checker=None):
|
|
super().__init__(
|
|
doctest._dt_test,
|
|
optionflags=optionflags,
|
|
setUp=setUp,
|
|
tearDown=tearDown,
|
|
checker=checker,
|
|
)
|
|
|
|
def setUp(self):
|
|
"""Log an extra statement which test is started."""
|
|
super(OdooDocTestCase, self).setUp()
|
|
logging.getLogger(__name__).info("Running tests for %s", self._dt_test.name)
|
|
|
|
|
|
def load_doctests(module):
|
|
"""
|
|
Generates a tests loading method for the doctests of the given module
|
|
https://docs.python.org/3/library/unittest.html#load-tests-protocol
|
|
"""
|
|
|
|
def load_tests(loader, tests, ignore):
|
|
"""
|
|
Apply the 'test_tags' attribute to each DocTestCase found by the DocTestSuite.
|
|
Also extend the DocTestCase class trivially to fit the class teardown
|
|
that Odoo backported for its own test classes from Python 3.8.
|
|
"""
|
|
if sys.version_info < (3, 8):
|
|
doctest.DocTestCase.doClassCleanups = lambda: None
|
|
doctest.DocTestCase.tearDown_exceptions = []
|
|
|
|
for test in doctest.DocTestSuite(module):
|
|
odoo_test = OdooDocTestCase(test)
|
|
odoo_test.test_tags = {"standard", "at_install", "queue_job", "doctest"}
|
|
tests.addTest(odoo_test)
|
|
|
|
return tests
|
|
|
|
return load_tests
|