summaryrefslogtreecommitdiff
path: root/odoo
diff options
context:
space:
mode:
authorRaphael Collet <rco@odoo.com>2018-02-06 13:42:40 +0100
committerRaphael Collet <rco@odoo.com>2018-02-12 10:31:59 +0100
commit7ea4f13f16671b4361a42d668fb81c941a552468 (patch)
tree6c4049fb6ae0fa3044bb5f0ebf994b0e98aea1b2 /odoo
parent459a53ddb5b64eb689a4546ed95cbbae9e802a92 (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.py2
-rw-r--r--odoo/addons/test_assetsbundle/tests/test_assetsbundle.py31
-rw-r--r--odoo/modules/registry.py24
-rw-r--r--odoo/sql_db.py78
-rw-r--r--odoo/tests/common.py2
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()