[IMP] tests: merge TransactionCase and SavepointCase
The now unique class behaves as the former class `SavepointCase`. It is now up to the developer to use `setUp` or `setUpClass` for preparing the tests.
This commit is contained in:
@@ -788,8 +788,8 @@ class TestExpression2(TransactionCase):
|
||||
|
||||
def test_long_table_alias(self):
|
||||
# To test the 64 characters limit for table aliases in PostgreSQL
|
||||
self.patch_order('res.users', 'partner_id')
|
||||
self.patch_order('res.partner', 'commercial_partner_id,company_id,name')
|
||||
self.patch(self.registry['res.users'], '_order', 'partner_id')
|
||||
self.patch(self.registry['res.partner'], '_order', 'commercial_partner_id,company_id,name')
|
||||
self.env['res.users'].search([('name', '=', 'test')])
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,9 @@ from odoo.tests.common import TransactionCase
|
||||
|
||||
class test_search(TransactionCase):
|
||||
|
||||
def patch_order(self, model, order):
|
||||
self.patch(self.registry[model], '_order', order)
|
||||
|
||||
def test_00_search_order(self):
|
||||
# Create 6 partners with a given name, and a given creation order to
|
||||
# ensure the order of their ID. Some are set as inactive to verify they
|
||||
|
||||
+99
-103
@@ -243,8 +243,23 @@ class OdooSuite(unittest.suite.TestSuite):
|
||||
info=exc)
|
||||
|
||||
|
||||
class TreeCase(unittest.TestCase):
|
||||
class MetaCase(type):
|
||||
""" Metaclass of test case classes to assign default 'test_tags':
|
||||
'standard', 'at_install' and the name of the module.
|
||||
"""
|
||||
def __init__(cls, name, bases, attrs):
|
||||
super(MetaCase, cls).__init__(name, bases, attrs)
|
||||
# assign default test tags
|
||||
if cls.__module__.startswith('odoo.addons.'):
|
||||
cls.test_tags = {'standard', 'at_install'}
|
||||
cls.test_module = cls.__module__.split('.')[2]
|
||||
cls.test_class = cls.__name__
|
||||
|
||||
|
||||
class BaseCase(unittest.TestCase, metaclass=MetaCase):
|
||||
""" Subclass of TestCase for Odoo-specific code. This class is abstract and
|
||||
expects self.registry, self.cr and self.uid to be initialized by subclasses.
|
||||
"""
|
||||
if sys.version_info < (3, 8):
|
||||
# Partial backport of bpo-24412, merged in CPython 3.8
|
||||
_class_cleanups = []
|
||||
@@ -267,49 +282,16 @@ class TreeCase(unittest.TestCase):
|
||||
except Exception as exc:
|
||||
cls.tearDown_exceptions.append(sys.exc_info())
|
||||
|
||||
longMessage = True # more verbose error message by default: https://www.odoo.com/r/Vmh
|
||||
warm = True # False during warm-up phase (see :func:`warmup`)
|
||||
|
||||
def __init__(self, methodName='runTest'):
|
||||
super(TreeCase, self).__init__(methodName)
|
||||
super().__init__(methodName)
|
||||
self.addTypeEqualityFunc(etree._Element, self.assertTreesEqual)
|
||||
self.addTypeEqualityFunc(html.HtmlElement, self.assertTreesEqual)
|
||||
|
||||
def assertTreesEqual(self, n1, n2, msg=None):
|
||||
self.assertIsNotNone(n1, msg)
|
||||
self.assertIsNotNone(n2, msg)
|
||||
self.assertEqual(n1.tag, n2.tag, msg)
|
||||
# Because lxml.attrib is an ordereddict for which order is important
|
||||
# to equality, even though *we* don't care
|
||||
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
||||
|
||||
self.assertEqual((n1.text or u'').strip(), (n2.text or u'').strip(), msg)
|
||||
self.assertEqual((n1.tail or u'').strip(), (n2.tail or u'').strip(), msg)
|
||||
|
||||
for c1, c2 in izip_longest(n1, n2):
|
||||
self.assertTreesEqual(c1, c2, msg)
|
||||
|
||||
|
||||
class MetaCase(type):
|
||||
""" Metaclass of test case classes to assign default 'test_tags':
|
||||
'standard', 'at_install' and the name of the module.
|
||||
"""
|
||||
def __init__(cls, name, bases, attrs):
|
||||
super(MetaCase, cls).__init__(name, bases, attrs)
|
||||
# assign default test tags
|
||||
if cls.__module__.startswith('odoo.addons.'):
|
||||
cls.test_tags = {'standard', 'at_install'}
|
||||
cls.test_module = cls.__module__.split('.')[2]
|
||||
cls.test_class = cls.__name__
|
||||
|
||||
|
||||
class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
|
||||
"""
|
||||
Subclass of TestCase for common OpenERP-specific code.
|
||||
|
||||
This class is abstract and expects self.registry, self.cr and self.uid to be
|
||||
initialized by subclasses.
|
||||
"""
|
||||
|
||||
longMessage = True # more verbose error message by default: https://www.odoo.com/r/Vmh
|
||||
warm = True # False during warm-up phase (see :func:`warmup`)
|
||||
def shortDescription(self):
|
||||
return None
|
||||
|
||||
def cursor(self):
|
||||
return self.registry.cursor()
|
||||
@@ -347,6 +329,12 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
|
||||
assert "." in xid, "this method requires a fully qualified parameter, in the following form: 'module.identifier'"
|
||||
return self.env.ref(xid)
|
||||
|
||||
def patch(self, obj, key, val):
|
||||
""" Do the patch ``setattr(obj, key, val)``, and prepare cleanup. """
|
||||
patcher = patch.object(obj, key, val) # this is unittest.mock.patch
|
||||
patcher.start()
|
||||
self.addCleanup(patcher.stop)
|
||||
|
||||
@contextmanager
|
||||
def with_user(self, login):
|
||||
""" Change user for a given test, like with self.with_user() ... """
|
||||
@@ -409,7 +397,9 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
|
||||
|
||||
self.assertEqual(
|
||||
len(actual_queries), len(expected),
|
||||
"%d queries done, %d expected" % (len(actual_queries), len(expected)),
|
||||
"\n---- actual queries:\n%s\n---- expected queries:\n%s" % (
|
||||
"\n".join(actual_queries), "\n".join(expected),
|
||||
)
|
||||
)
|
||||
for actual_query, expect_query in zip(actual_queries, expected):
|
||||
self.assertEqual(
|
||||
@@ -573,45 +563,80 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})):
|
||||
|
||||
self.fail('\n'.join(errors))
|
||||
|
||||
def shortDescription(self):
|
||||
return None
|
||||
|
||||
# turns out this thing may not be quite as useful as we thought...
|
||||
def assertItemsEqual(self, a, b, msg=None):
|
||||
self.assertCountEqual(a, b, msg=None)
|
||||
|
||||
def assertTreesEqual(self, n1, n2, msg=None):
|
||||
self.assertIsNotNone(n1, msg)
|
||||
self.assertIsNotNone(n2, msg)
|
||||
self.assertEqual(n1.tag, n2.tag, msg)
|
||||
# Because lxml.attrib is an ordereddict for which order is important
|
||||
# to equality, even though *we* don't care
|
||||
self.assertEqual(dict(n1.attrib), dict(n2.attrib), msg)
|
||||
|
||||
self.assertEqual((n1.text or u'').strip(), (n2.text or u'').strip(), msg)
|
||||
self.assertEqual((n1.tail or u'').strip(), (n2.tail or u'').strip(), msg)
|
||||
|
||||
for c1, c2 in izip_longest(n1, n2):
|
||||
self.assertTreesEqual(c1, c2, msg)
|
||||
|
||||
|
||||
savepoint_seq = itertools.count()
|
||||
|
||||
|
||||
class TransactionCase(BaseCase):
|
||||
""" TestCase in which each test method is run in its own transaction,
|
||||
and with its own cursor. The transaction is rolled back and the cursor
|
||||
is closed after each test.
|
||||
""" Test class in which all test methods are run in a single transaction,
|
||||
but each test method is run in a sub-transaction managed by a savepoint.
|
||||
The transaction's cursor is always closed without committing.
|
||||
|
||||
The data setup common to all methods should be done in the class method
|
||||
`setUpClass`, so that it is done once for all test methods. This is useful
|
||||
for test cases containing fast tests but with significant database setup
|
||||
common to all cases (complex in-db test data).
|
||||
|
||||
After being run, each test method cleans up the record cache and the
|
||||
registry cache. However, there is no cleanup of the registry models and
|
||||
fields. If a test modifies the registry (custom models and/or fields), it
|
||||
should prepare the necessary cleanup (`self.registry.reset_changes()`).
|
||||
"""
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.registry = odoo.registry(get_db_name())
|
||||
cls.addClassCleanup(cls.registry.reset_changes)
|
||||
cls.addClassCleanup(cls.registry.clear_caches)
|
||||
|
||||
cls.cr = cls.registry.cursor()
|
||||
cls.addClassCleanup(cls.cr.close)
|
||||
|
||||
cls.env = api.Environment(cls.cr, odoo.SUPERUSER_ID, {})
|
||||
cls.addClassCleanup(cls.env.reset)
|
||||
|
||||
def setUp(self):
|
||||
super(TransactionCase, self).setUp()
|
||||
self.registry = odoo.registry(get_db_name())
|
||||
self.addCleanup(self.registry.reset_changes)
|
||||
super().setUp()
|
||||
|
||||
# restore environments after the test to avoid invoking flush() with an
|
||||
# invalid environment (inexistent user id) from another test
|
||||
envs = self.env.all.envs
|
||||
self.addCleanup(envs.update, list(envs))
|
||||
self.addCleanup(envs.clear)
|
||||
|
||||
self.addCleanup(self.registry.clear_caches)
|
||||
self.addCleanup(self.env.clear)
|
||||
|
||||
#: current transaction's cursor
|
||||
self.cr = self.cursor()
|
||||
self.addCleanup(self.cr.close)
|
||||
# flush everything in setUpClass before introducing a savepoint
|
||||
self.env['base'].flush()
|
||||
|
||||
#: :class:`~odoo.api.Environment` for the current test case
|
||||
self.env = api.Environment(self.cr, odoo.SUPERUSER_ID, {})
|
||||
self.addCleanup(self.env.reset)
|
||||
self._savepoint_id = next(savepoint_seq)
|
||||
self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id)
|
||||
self.addCleanup(self.cr.execute, 'ROLLBACK TO SAVEPOINT test_%d' % self._savepoint_id)
|
||||
|
||||
self.patch(type(self.env['res.partner']), '_get_gravatar_image', lambda *a: False)
|
||||
self.patch(self.registry['res.partner'], '_get_gravatar_image', lambda *a: False)
|
||||
|
||||
def patch(self, obj, key, val):
|
||||
""" Do the patch ``setattr(obj, key, val)``, and prepare cleanup. """
|
||||
old = getattr(obj, key)
|
||||
setattr(obj, key, val)
|
||||
self.addCleanup(setattr, obj, key, old)
|
||||
|
||||
def patch_order(self, model, order):
|
||||
""" Patch the order of the given model (name), and prepare cleanup. """
|
||||
self.patch(type(self.env[model]), '_order', order)
|
||||
# kept for backward compatibility
|
||||
SavepointCase = TransactionCase
|
||||
|
||||
|
||||
class SingleTransactionCase(BaseCase):
|
||||
@@ -619,6 +644,11 @@ class SingleTransactionCase(BaseCase):
|
||||
the transaction is started with the first test method and rolled back at
|
||||
the end of the last.
|
||||
"""
|
||||
@classmethod
|
||||
def __init_subclass__(cls):
|
||||
super().__init_subclass__()
|
||||
if issubclass(cls, TransactionCase):
|
||||
_logger.warning("%s inherits from both TransactionCase and SingleTransactionCase")
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
@@ -638,35 +668,6 @@ class SingleTransactionCase(BaseCase):
|
||||
self.env.user.flush()
|
||||
|
||||
|
||||
savepoint_seq = itertools.count()
|
||||
class SavepointCase(SingleTransactionCase):
|
||||
""" Similar to :class:`SingleTransactionCase` in that all test methods
|
||||
are run in a single transaction *but* each test case is run inside a
|
||||
rollbacked savepoint (sub-transaction).
|
||||
|
||||
Useful for test cases containing fast tests but with significant database
|
||||
setup common to all cases (complex in-db test data): :meth:`~.setUpClass`
|
||||
can be used to generate db test data once, then all test cases use the
|
||||
same data without influencing one another but without having to recreate
|
||||
the test data either.
|
||||
"""
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# restore environments after the test to avoid invoking flush() with an
|
||||
# invalid environment (inexistent user id) from another test
|
||||
envs = self.env.all.envs
|
||||
self.addCleanup(envs.update, list(envs))
|
||||
self.addCleanup(envs.clear)
|
||||
|
||||
self.addCleanup(self.registry.clear_caches)
|
||||
self.addCleanup(self.env.clear)
|
||||
|
||||
self._savepoint_id = next(savepoint_seq)
|
||||
self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id)
|
||||
self.addCleanup(self.cr.execute, 'ROLLBACK TO SAVEPOINT test_%d' % self._savepoint_id)
|
||||
|
||||
|
||||
class ChromeBrowserException(Exception):
|
||||
pass
|
||||
|
||||
@@ -1244,7 +1245,8 @@ class ChromeBrowser():
|
||||
return replacer
|
||||
|
||||
|
||||
class HttpCaseCommon(BaseCase):
|
||||
class HttpCase(TransactionCase):
|
||||
""" Transactional HTTP TestCase with url_open and Chrome headless helpers. """
|
||||
registry_test_mode = True
|
||||
browser = None
|
||||
browser_size = '1366x768'
|
||||
@@ -1430,14 +1432,8 @@ class HttpCaseCommon(BaseCase):
|
||||
return res
|
||||
|
||||
|
||||
class HttpCase(HttpCaseCommon, TransactionCase):
|
||||
""" Transactional HTTP TestCase with url_open and Chrome headless helpers.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class HttpSavepointCase(HttpCaseCommon, SavepointCase):
|
||||
pass
|
||||
# kept for backward compatibility
|
||||
HttpSavepointCase = HttpCase
|
||||
|
||||
|
||||
def users(*logins):
|
||||
|
||||
Reference in New Issue
Block a user