diff options
| author | Raphael Collet <rco@odoo.com> | 2019-08-14 07:35:49 +0000 |
|---|---|---|
| committer | Denis Ledoux <dle@odoo.com> | 2019-08-20 12:43:59 +0000 |
| commit | 9920f20e4c7753bc17bea71dea3a90f7de687196 (patch) | |
| tree | 6a3d0258c10a8ccb1d85949ddafdeb070de78121 /odoo/tests | |
| parent | e28d3aa4d3851d72deba08c236072d002f83216a (diff) | |
[IMP] models: ORM speedup
This branch is the combination of several optimizations in the ORM:
* store field values once in the cache: the cache reflects more
faithfully the database, only fields that explicitly depend on the
context have an extra indirection in the cache;
* delay recomputations by default: use method `recompute` to explicitly
flush out pending recomputations;
* delay updates in method `write`: updates are stored in a data
structure that can be flushed efficiently to the database with method
`flush` (which also flush out recomputations);
* make method `modified` take advantage of inverse fields to inverse
dependencies;
* filter records by evaluating a domain on records in Python;
* a computed field with `readonly=False` behaves like a normal field
with an onchange method;
* computed fields are computed in superuser mode by default.
Work done by Toufik Ben Jaa, Raphael Collet, Denis Ledoux and Fabien
Pinckaers.
closes odoo/odoo#35659
Signed-off-by: Denis Ledoux <beledouxdenis@users.noreply.github.com>
Diffstat (limited to 'odoo/tests')
| -rw-r--r-- | odoo/tests/common.py | 45 |
1 files changed, 39 insertions, 6 deletions
diff --git a/odoo/tests/common.py b/odoo/tests/common.py index e0370cc0f51..0dd6162c320 100644 --- a/odoo/tests/common.py +++ b/odoo/tests/common.py @@ -33,7 +33,7 @@ from lxml import etree, html from odoo.models import BaseModel from odoo.osv.expression import normalize_domain -from odoo.tools import single_email_re +from odoo.tools import float_compare, single_email_re from odoo.tools.misc import find_in_path from odoo.tools.safe_eval import safe_eval @@ -259,7 +259,7 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): return self._assertRaises(exception) @contextmanager - def assertQueryCount(self, default=0, **counters): + def assertQueryCount(self, default=0, flush=True, **counters): """ Context manager that counts queries. It may be invoked either with one value, or with a set of named arguments like ``login=value``:: @@ -276,8 +276,12 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): with self.subTest(), patch('random.random', lambda: 1): login = self.env.user.login expected = counters.get(login, default) + if flush: + self.env.user.flush() count0 = self.cr.sql_log_count yield + if flush: + self.env.user.flush() count = self.cr.sql_log_count - count0 if count != expected: # add some info on caller to allow semi-automatic update of query count @@ -293,6 +297,8 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): logger.info(msg, login, count, expected, funcname, filename, linenum) else: yield + if flush: + self.env.user.flush() def assertRecordValues(self, records, expected_values): ''' Compare a recordset with a list of dictionaries representing the expected results. @@ -314,7 +320,8 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): for field_name in candidate.keys(): record_value = record[field_name] candidate_value = candidate[field_name] - field_type = record._fields[field_name].type + field = record._fields[field_name] + field_type = field.type if field_type == 'monetary': # Compare monetary field. currency_field_name = record._fields[field_name].currency_field @@ -322,6 +329,8 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): if record_currency.compare_amounts(candidate_value, record_value)\ if record_currency else candidate_value != record_value: return False + elif field_type == 'float' and field.get_digits(record.env): + return not float_compare(candidate_value, record_value, precision_digits=field.get_digits(record.env)[1]) elif field_type in ('one2many', 'many2many'): # Compare x2many relational fields. # Empty comparison must be an empty list to be True. @@ -363,8 +372,13 @@ class BaseCase(TreeCase, MetaCase('DummyCase', (object,), {})): msg = 'Wrong number of records to compare: %d != %d.\n\n' % (len(records), len(expected_values)) self.fail(msg + _format_message(records, expected_values)) + candidates = list(expected_values) for index, record in enumerate(records): - if not _compare_candidate(record, expected_values[index]): + for candidate_index, candidate in enumerate(candidates): + if _compare_candidate(record, candidate): + candidates.pop(candidate_index) + break + else: msg = 'Record doesn\'t match expected values at index %d.\n\n' % index self.fail(msg + _format_message(records, expected_values)) @@ -425,6 +439,10 @@ class SingleTransactionCase(BaseCase): cls.cr = cls.registry.cursor() cls.env = api.Environment(cls.cr, odoo.SUPERUSER_ID, {}) + def setUp(self): + super(SingleTransactionCase, self).setUp() + self.env.user.flush() + @classmethod def tearDownClass(cls): # rollback and close the cursor, and reset the environments @@ -885,6 +903,7 @@ class HttpCase(TransactionCase): self.opener.cookies['session_id'] = self.session_id def url_open(self, url, data=None, files=None, timeout=10, headers=None): + self.env['base'].flush() if url.startswith('/'): url = "http://%s:%s%s" % (HOST, PORT, url) if data or files: @@ -965,6 +984,9 @@ class HttpCase(TransactionCase): base_url = "http://%s:%s" % (HOST, PORT) ICP = self.env['ir.config_parameter'] ICP.set_param('web.base.url', base_url) + # flush updates to the database before launching the client side, + # otherwise they simply won't be visible + ICP.flush() url = "%s%s" % (base_url, url_path or '/') self._logger.info('Open "%s" in browser', url) @@ -1003,7 +1025,12 @@ class HttpCase(TransactionCase): step_delay = ', %s' % step_delay if step_delay else '' code = kwargs.pop('code', "odoo.startTour('%s'%s)" % (tour_name, step_delay)) ready = kwargs.pop('ready', "odoo.__DEBUG__.services['web_tour.tour'].tours.%s.ready" % tour_name) - return self.browser_js(url_path=url_path, code=code, ready=ready, **kwargs) + res = self.browser_js(url_path=url_path, code=code, ready=ready, **kwargs) + # some tests read the result after the tour, and as the tour does not + # use this environment's cache, invalidate it to fetch the data from the + # database + self.env.cache.invalidate() + return res phantom_js = browser_js @@ -1026,6 +1053,9 @@ def users(*logins): # switch user and execute func self.uid = user_id[login] func(*args, **kwargs) + # Invalidate the cache between subtests, in order to not reuse + # the former user's cache (`test_read_mail`, `test_write_mail`) + self.env.cache.invalidate() finally: self.uid = old_uid @@ -1040,13 +1070,16 @@ def warmup(func, *args, **kwargs): effects of the warmup phase are rolled back thanks to a savepoint. """ self = args[0] + self.env['base'].flush() + self.env.cache.invalidate() # run once to warm up the caches self.warm = False self.cr.execute('SAVEPOINT test_warmup') func(*args, **kwargs) + self.env['base'].flush() + # run once for real self.cr.execute('ROLLBACK TO SAVEPOINT test_warmup') self.env.cache.invalidate() - # run once for real self.warm = True func(*args, **kwargs) |
