From 81192d66d9f4a5e4847e4ad511110567f7d530ee Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 31 Aug 2017 10:39:49 +0200 Subject: [PATCH] [ADD] test_performance: check the performance of CRUD operations --- odoo/addons/test_performance/__init__.py | 3 + odoo/addons/test_performance/__manifest__.py | 13 ++ odoo/addons/test_performance/demo/demo.xml | 56 ++++++ .../test_performance/models/__init__.py | 3 + odoo/addons/test_performance/models/models.py | 49 ++++++ .../security/ir.model.access.csv | 4 + .../addons/test_performance/tests/__init__.py | 2 + .../tests/test_performance.py | 161 ++++++++++++++++++ 8 files changed, 291 insertions(+) create mode 100644 odoo/addons/test_performance/__init__.py create mode 100644 odoo/addons/test_performance/__manifest__.py create mode 100644 odoo/addons/test_performance/demo/demo.xml create mode 100644 odoo/addons/test_performance/models/__init__.py create mode 100644 odoo/addons/test_performance/models/models.py create mode 100644 odoo/addons/test_performance/security/ir.model.access.csv create mode 100644 odoo/addons/test_performance/tests/__init__.py create mode 100644 odoo/addons/test_performance/tests/test_performance.py diff --git a/odoo/addons/test_performance/__init__.py b/odoo/addons/test_performance/__init__.py new file mode 100644 index 00000000000..cde864bae21 --- /dev/null +++ b/odoo/addons/test_performance/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/odoo/addons/test_performance/__manifest__.py b/odoo/addons/test_performance/__manifest__.py new file mode 100644 index 00000000000..fb1f3f0a719 --- /dev/null +++ b/odoo/addons/test_performance/__manifest__.py @@ -0,0 +1,13 @@ +# -*- coding: utf-8 -*- +{ + 'name': "Test Performance", + 'version': "1.0", + 'category': "Hidden", + 'depends': ['base', 'mail'], + 'data': [ + 'security/ir.model.access.csv', + ], + 'demo': [ + 'demo/demo.xml', + ], +} diff --git a/odoo/addons/test_performance/demo/demo.xml b/odoo/addons/test_performance/demo/demo.xml new file mode 100644 index 00000000000..8f83c69a309 --- /dev/null +++ b/odoo/addons/test_performance/demo/demo.xml @@ -0,0 +1,56 @@ + + + + + Object 0 + 0 + + + + Object 1 + 10 + + + + Object 2 + 20 + + + + Object 3 + 30 + + + + Object 4 + 40 + + + + + Object 0 + 0 + + + + Object 1 + 10 + + + + Object 2 + 20 + + + + Object 3 + 30 + + + + Object 4 + 40 + + + + diff --git a/odoo/addons/test_performance/models/__init__.py b/odoo/addons/test_performance/models/__init__.py new file mode 100644 index 00000000000..cde864bae21 --- /dev/null +++ b/odoo/addons/test_performance/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/odoo/addons/test_performance/models/models.py b/odoo/addons/test_performance/models/models.py new file mode 100644 index 00000000000..97e02ad26c0 --- /dev/null +++ b/odoo/addons/test_performance/models/models.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo import models, fields, api + + +class BaseModel(models.Model): + _name = 'test_performance.base' + + name = fields.Char() + value = fields.Integer() + value_pc = fields.Float(compute="_value_pc", store=True) + partner_id = fields.Many2one('res.partner', string='Customer') + + line_ids = fields.One2many('test_performance.line', 'base_id') + total = fields.Integer(compute="_total", store=True) + + @api.depends('value') + def _value_pc(self): + for record in self: + record.value_pc = float(record.value) / 100 + + @api.depends('line_ids.value') + def _total(self): + for record in self: + record.total = sum(line.value for line in record.line_ids) + + +class LineModel(models.Model): + _name = 'test_performance.line' + + base_id = fields.Many2one('test_performance.base', required=True, ondelete='cascade') + value = fields.Integer() + + +class MailModel(models.Model): + _name = 'test_performance.mail' + _inherit = 'mail.thread' + + name = fields.Char() + value = fields.Integer() + value_pc = fields.Float(compute="_value_pc", store=True) + track = fields.Char(default='test', track_visibility="onchange") + partner_id = fields.Many2one('res.partner', string='Customer') + + @api.depends('value') + def _value_pc(self): + for record in self: + record.value_pc = float(record.value) / 100 diff --git a/odoo/addons/test_performance/security/ir.model.access.csv b/odoo/addons/test_performance/security/ir.model.access.csv new file mode 100644 index 00000000000..f3efebaea6e --- /dev/null +++ b/odoo/addons/test_performance/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_test_performance_base,access_test_performance_base,model_test_performance_base,,1,1,1,1 +access_test_performance_line,access_test_performance_line,model_test_performance_line,,1,1,1,1 +access_test_performance_mail,access_test_performance_mail,model_test_performance_mail,,1,1,1,1 diff --git a/odoo/addons/test_performance/tests/__init__.py b/odoo/addons/test_performance/tests/__init__.py new file mode 100644 index 00000000000..54c286c2e5e --- /dev/null +++ b/odoo/addons/test_performance/tests/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import test_performance diff --git a/odoo/addons/test_performance/tests/test_performance.py b/odoo/addons/test_performance/tests/test_performance.py new file mode 100644 index 00000000000..5bc55179cb0 --- /dev/null +++ b/odoo/addons/test_performance/tests/test_performance.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from contextlib import contextmanager +import logging + +from odoo.tests.common import TransactionCase + +sql_logger = logging.getLogger('odoo.sql_db') + + +class TestPerformance(TransactionCase): + @contextmanager + def assertMaxQueries(self, message, count): + """ Check the number of queries made in this scope. """ + count0 = self.cr.sql_log_count + yield + count1 = self.cr.sql_log_count + self.assertLessEqual(count1 - count0, count, message) + + @contextmanager + def logQueries(self): + """ Log the queries that are made in this scope. """ + sql_log, level = self.cr.sql_log, sql_logger.getEffectiveLevel() + try: + sql_logger.setLevel(logging.DEBUG) + self.cr.sql_log = True + yield + finally: + self.cr.sql_log = sql_log + sql_logger.setLevel(level) + + def test_read_base(self): + """ Check reading records. """ + records = self.env['test_performance.base'].search([]) + self.assertEqual(len(records), 5) + + # without cache + with self.assertMaxQueries("Prefetch records", 3): + for record in records: + record.partner_id.country_id.name + + # with cache + with self.assertMaxQueries("Read prefetched records", 0): + for record in records: + record.partner_id.country_id.name + + # value_pc must have been prefetched, too + with self.assertMaxQueries("Read another field on prefetched records", 0): + for record in records: + record.value_pc + + def test_read_mail(self): + """ Check reading records inheriting from 'mail.thread'. """ + records = self.env['test_performance.mail'].search([]) + self.assertEqual(len(records), 5) + + # without cache + with self.assertMaxQueries("Prefetch records", 3): + for record in records: + record.partner_id.country_id.name + + # with cache + with self.assertMaxQueries("Read prefetched records", 0): + for record in records: + record.partner_id.country_id.name + + # value_pc must have been prefetched, too + with self.assertMaxQueries("Read another field on prefetched records", 0): + for record in records: + record.value_pc + + def test_write_base(self): + """ Check writing records. """ + records = self.env['test_performance.base'].search([]) + self.assertEqual(len(records), 5) + + # warm up the caches + records.write({'name': 'Start', 'value': 10}) + + # write in batch, without recomputation + with self.assertMaxQueries("Write in batch, no recomputation", 1): + records.write({'name': 'X'}) + + # write one by one, without recomputation + with self.assertMaxQueries("Write one by one, no recomputation", 5): + for record in records: + record.name = 'Y' + + # write in batch, with recomputation + with self.assertMaxQueries("Write in batch, with recomputation", 3): + records.write({'value': 20}) + + # write one by one, with recomputation + with self.assertMaxQueries("Write one by one, with recomputation", 15): + for record in records: + record.value = 30 + + def test_write_mail(self): + """ Check writing records inheriting from 'mail.thread'. """ + records = self.env['test_performance.mail'].search([]) + self.assertEqual(len(records), 5) + + # warm up the caches + records.write({'value': 0}) + records.write({'name': '0'}) + + # write in batch, without recomputation + with self.assertMaxQueries("Write in batch, no recomputation", 3): + records.write({'name': 'X'}) + + # write one by one, without recomputation + with self.assertMaxQueries("Write one by one, no recomputation", 15): + for record in records: + record.name = 'Y' + + # write in batch, with recomputation + with self.assertMaxQueries("Write in batch, with recomputation", 5): + records.write({'value': 20}) + + # write one by one, with recomputation + with self.assertMaxQueries("Write one by one, with recomputation", 25): + for record in records: + record.value = 30 + + # write on a tracked field + with self.assertMaxQueries("Write on a tracked field", 49): + records[0].track = 'X' + + def test_create_base(self): + """ Check creating records. """ + model = self.env['test_performance.base'] + + # warm up caches + model.create({'name': 'X'}) + + # create record without lines + with self.assertMaxQueries("Create record without lines", 6): + model.create({'name': 'X'}) + + # create record with lines + with self.assertMaxQueries("Create record with lines", 39): + model.create({ + 'name': 'X', + 'line_ids': [(0, 0, {'value': val}) for val in range(10)], + }) + + def test_create_mail(self): + """ Check creating records inheriting from 'mail.thread'. """ + model = self.env['test_performance.mail'] + + # warm up caches + model.create({'name': 'X'}) + + # create without tracking changes + with self.assertMaxQueries("Create record without tracking fields", 3): + model.with_context(tracking_disable=True).create({'name': 'X'}) + + # create with tracking changes + with self.assertMaxQueries("Create record with tracking fields", 88): + model.create({'name': 'X'})