diff options
| author | Raphael Collet <rco@odoo.com> | 2018-02-06 13:42:40 +0100 |
|---|---|---|
| committer | Raphael Collet <rco@odoo.com> | 2018-02-12 10:31:59 +0100 |
| commit | 7ea4f13f16671b4361a42d668fb81c941a552468 (patch) | |
| tree | 6c4049fb6ae0fa3044bb5f0ebf994b0e98aea1b2 /odoo | |
| parent | 459a53ddb5b64eb689a4546ed95cbbae9e802a92 (diff) | |
[REF] tests: `TestCursor` is now a proxy to a real `Cursor`
This allows rpc requests in `HttpCase` to use the cursor `self.cr`, which is
now shared between the Python test and the rpc requests. This simplifies code
to prepare a JS test, and code to check the result of a JS tour.
Fixes #12237
Diffstat (limited to 'odoo')
| -rw-r--r-- | odoo/addons/base/tests/test_ir_actions.py | 2 | ||||
| -rw-r--r-- | odoo/addons/test_assetsbundle/tests/test_assetsbundle.py | 31 | ||||
| -rw-r--r-- | odoo/modules/registry.py | 24 | ||||
| -rw-r--r-- | odoo/sql_db.py | 78 | ||||
| -rw-r--r-- | odoo/tests/common.py | 2 |
5 files changed, 78 insertions, 59 deletions
diff --git a/odoo/addons/base/tests/test_ir_actions.py b/odoo/addons/base/tests/test_ir_actions.py index 7c5836fa97e..355ddaa143d 100644 --- a/odoo/addons/base/tests/test_ir_actions.py +++ b/odoo/addons/base/tests/test_ir_actions.py @@ -242,7 +242,7 @@ class TestCustomFields(common.TransactionCase): super(TestCustomFields, self).setUp() # use a test cursor instead of a real cursor - self.registry.enter_test_mode() + self.registry.enter_test_mode(self.cr) self.addCleanup(self.registry.leave_test_mode) # do not reload the registry after removing a field diff --git a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py index c97ac8d0f95..3d372a6dcd2 100644 --- a/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py +++ b/odoo/addons/test_assetsbundle/tests/test_assetsbundle.py @@ -332,22 +332,21 @@ class TestAssetsBundleInBrowser(HttpCase): def test_02_js_interpretation_inline(self): """ Checks that the javascript of a bundle is correctly interpretet when mixed with inline. """ - with self.registry.cursor() as test_cursor: - view_arch = """ - <data> - <xpath expr="." position="inside"> - <script type="text/javascript"> - var d = 4; - </script> - </xpath> - </data> - """ - self.env(cr=test_cursor)['ir.ui.view'].create({ - 'name': 'test bundle inheritance inline js', - 'type': 'qweb', - 'arch': view_arch, - 'inherit_id': self.browse_ref('test_assetsbundle.bundle1').id, - }) + view_arch = """ + <data> + <xpath expr="." position="inside"> + <script type="text/javascript"> + var d = 4; + </script> + </xpath> + </data> + """ + self.env['ir.ui.view'].create({ + 'name': 'test bundle inheritance inline js', + 'type': 'qweb', + 'arch': view_arch, + 'inherit_id': self.browse_ref('test_assetsbundle.bundle1').id, + }) self.phantom_js( "/test_assetsbundle/js", diff --git a/odoo/modules/registry.py b/odoo/modules/registry.py index 1d6dbe69726..6dd6556298b 100644 --- a/odoo/modules/registry.py +++ b/odoo/modules/registry.py @@ -15,6 +15,7 @@ import threading import odoo from .. import SUPERUSER_ID +from odoo.sql_db import TestCursor from odoo.tools import (assertion_report, config, existing_tables, lazy_classproperty, lazy_property, table_exists, topological_sort, OrderedSet) @@ -123,8 +124,9 @@ class Registry(Mapping): self.db_name = db_name self._db = odoo.sql_db.db_connect(db_name) - # special cursor for test mode; None means "normal" mode + # cursor for test mode; None means "normal" mode self.test_cr = None + self.test_lock = None # Indicates that the registry is self.loaded = False # whether all modules are loaded @@ -443,10 +445,11 @@ class Registry(Mapping): """ Test whether the registry is in 'test' mode. """ return self.test_cr is not None - def enter_test_mode(self): + def enter_test_mode(self, cr): """ Enter the 'test' mode, where one cursor serves several requests. """ assert self.test_cr is None - self.test_cr = self._db.test_cursor() + self.test_cr = cr + self.test_lock = threading.RLock() assert Registry._saved_lock is None Registry._saved_lock = Registry._lock Registry._lock = DummyRLock() @@ -454,9 +457,8 @@ class Registry(Mapping): def leave_test_mode(self): """ Leave the test mode. """ assert self.test_cr is not None - self.clear_caches() - self.test_cr.force_close() self.test_cr = None + self.test_lock = None assert Registry._saved_lock is not None Registry._lock = Registry._saved_lock Registry._saved_lock = None @@ -465,14 +467,10 @@ class Registry(Mapping): """ Return a new cursor for the database. The cursor itself may be used as a context manager to commit/rollback and close automatically. """ - cr = self.test_cr - if cr is not None: - # While in test mode, we use one special cursor across requests. The - # test cursor uses a reentrant lock to serialize accesses. The lock - # is granted here by cursor(), and automatically released by the - # cursor itself in its method close(). - cr.acquire() - return cr + if self.test_cr is not None: + # When in test mode, we use a proxy object that uses 'self.test_cr' + # underneath. + return TestCursor(self.test_cr, self.test_lock) return self._db.cursor() diff --git a/odoo/sql_db.py b/odoo/sql_db.py index 50e4ac5dce7..8414dcf3dcc 100644 --- a/odoo/sql_db.py +++ b/odoo/sql_db.py @@ -10,6 +10,7 @@ the ORM does, in fact. from contextlib import contextmanager from functools import wraps +import itertools import logging import time import uuid @@ -425,42 +426,68 @@ class Cursor(object): def closed(self): return self._closed -class TestCursor(Cursor): - """ A cursor to be used for tests. It keeps the transaction open across - several requests, and simulates committing, rolling back, and closing. - """ - def __init__(self, *args, **kwargs): - super(TestCursor, self).__init__(*args, **kwargs) - # in order to simulate commit and rollback, the cursor maintains a - # savepoint at its last commit - self.execute("SAVEPOINT test_cursor") - # we use a lock to serialize concurrent requests - self._lock = threading.RLock() - def acquire(self): - self._lock.acquire() +class TestCursor(object): + """ A pseudo-cursor to be used for tests, on top of a real cursor. It keeps + the transaction open across requests, and simulates committing, rolling + back, and closing: + + test cursor | queries on actual cursor + ------------------------+--------------------------------------- + cr = TestCursor(...) | SAVEPOINT test_cursor_N + | + cr.execute(query) | query + | + cr.commit() | SAVEPOINT test_cursor_N + | + cr.rollback() | ROLLBACK TO SAVEPOINT test_cursor_N + | + cr.close() | ROLLBACK TO SAVEPOINT test_cursor_N + | - def release(self): - self._lock.release() + """ + _savepoint_seq = itertools.count() - def force_close(self): - super(TestCursor, self).close() + def __init__(self, cursor, lock): + self._closed = False + self._cursor = cursor + # we use a lock to serialize concurrent requests + self._lock = lock + self._lock.acquire() + # in order to simulate commit and rollback, the cursor maintains a + # savepoint at its last commit + self._savepoint = "test_cursor_%s" % next(self._savepoint_seq) + self._cursor.execute('SAVEPOINT "%s"' % self._savepoint) def close(self): if not self._closed: - self.rollback() # for stuff that has not been committed - self.release() + self._closed = True + self._cursor.execute('ROLLBACK TO SAVEPOINT "%s"' % self._savepoint) + self._lock.release() def autocommit(self, on): _logger.debug("TestCursor.autocommit(%r) does nothing", on) def commit(self): - self.execute("RELEASE SAVEPOINT test_cursor") - self.execute("SAVEPOINT test_cursor") + self._cursor.execute('SAVEPOINT "%s"' % self._savepoint) def rollback(self): - self.execute("ROLLBACK TO SAVEPOINT test_cursor") - self.execute("SAVEPOINT test_cursor") + self._cursor.execute('ROLLBACK TO SAVEPOINT "%s"' % self._savepoint) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + if exc_type is None: + self.commit() + self.close() + + def __getattr__(self, name): + value = getattr(self._cursor, name) + if callable(value) and self._closed: + raise psycopg2.OperationalError('Unable to use a closed cursor.') + return value + class LazyCursor(object): """ A proxy object to a cursor. The cursor itself is allocated only if it is @@ -633,11 +660,6 @@ class Connection(object): _logger.debug('create %scursor to %r', cursor_type, self.dsn) return Cursor(self.__pool, self.dbname, self.dsn, serialized=serialized) - def test_cursor(self, serialized=True): - cursor_type = serialized and 'serialized ' or '' - _logger.debug('create test %scursor to %r', cursor_type, self.dsn) - return TestCursor(self.__pool, self.dbname, self.dsn, serialized=serialized) - # serialized_cursor is deprecated - cursors are serialized by default serialized_cursor = cursor diff --git a/odoo/tests/common.py b/odoo/tests/common.py index 005b5a1cfcc..df4178ee0e9 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -326,7 +326,7 @@ class HttpCase(TransactionCase): def setUp(self): super(HttpCase, self).setUp() if self.registry_test_mode: - self.registry.enter_test_mode() + self.registry.enter_test_mode(self.cr) self.addCleanup(self.registry.leave_test_mode) # setup a magic session_id that will be rollbacked self.session = odoo.http.root.session_store.new() |
