[IMP] running speed of some tests & new testcase type

Some tests (e.g. mail) have expensive and significant DB setup for a
number of small and cheap tests. Using a TransactionCase, the DB setup
far dominates the tests themselves, by up to 10x (mail unit tests take
~130s on my machine, the tests themselves take ~15s).

The SavepointCase introduced here is an hybrid of SingleTransactionCase
and TransactionCase: it uses a single transaction for all tests in a
class, but each test case is isolated by a rollbacked savepoint. This
allows a common DB setup (via setUpClass) while keeping independent
tests.

TransactionCase should remain the primary test case superclass, but
SavepointCase can be a fair optimisation when setup costs far dominate.
This commit is contained in:
Xavier Morel 2015-06-23 13:14:07 +02:00
parent 386f76eb46
commit 11ba4689b1
15 changed files with 144 additions and 181 deletions

View File

@ -22,63 +22,66 @@
from openerp.tests import common
class TestMail(common.TransactionCase):
class TestMail(common.SavepointCase):
def _init_mock_build_email(self):
self._build_email_args_list = []
self._build_email_kwargs_list = []
@classmethod
def _init_mock_build_email(cls):
cls._build_email_args_list = []
cls._build_email_kwargs_list = []
def setUp(self):
super(TestMail, self).setUp()
cr, uid = self.cr, self.uid
self._build_email_args_list[:] = []
self._build_email_kwargs_list[:] = []
# Install mock SMTP gateway
test = self
@classmethod
def setUpClass(cls):
super(TestMail, cls).setUpClass()
cr, uid = cls.cr, cls.uid
def build_email(self, *args, **kwargs):
test._build_email_args_list.append(args)
test._build_email_kwargs_list.append(kwargs)
cls._build_email_args_list.append(args)
cls._build_email_kwargs_list.append(kwargs)
return build_email.origin(self, *args, **kwargs)
def send_email(self, cr, uid, message, *args, **kwargs):
return message['Message-Id']
self._init_mock_build_email()
self.registry('ir.mail_server')._patch_method('build_email', build_email)
self.registry('ir.mail_server')._patch_method('send_email', send_email)
cls._init_mock_build_email()
cls.registry('ir.mail_server')._patch_method('build_email', build_email)
cls.registry('ir.mail_server')._patch_method('send_email', send_email)
# Usefull models
self.ir_model = self.registry('ir.model')
self.ir_model_data = self.registry('ir.model.data')
self.ir_attachment = self.registry('ir.attachment')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
self.mail_group = self.registry('mail.group')
self.mail_mail = self.registry('mail.mail')
self.mail_message = self.registry('mail.message')
self.mail_notification = self.registry('mail.notification')
self.mail_followers = self.registry('mail.followers')
self.mail_message_subtype = self.registry('mail.message.subtype')
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
cls.ir_model = cls.registry('ir.model')
cls.ir_model_data = cls.registry('ir.model.data')
cls.ir_attachment = cls.registry('ir.attachment')
cls.mail_alias = cls.registry('mail.alias')
cls.mail_thread = cls.registry('mail.thread')
cls.mail_group = cls.registry('mail.group')
cls.mail_mail = cls.registry('mail.mail')
cls.mail_message = cls.registry('mail.message')
cls.mail_notification = cls.registry('mail.notification')
cls.mail_followers = cls.registry('mail.followers')
cls.mail_message_subtype = cls.registry('mail.message.subtype')
cls.res_users = cls.registry('res.users')
cls.res_partner = cls.registry('res.partner')
# Find Employee group
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
cls.group_employee_id = cls.env.ref('base.group_user').id or False
# Partner Data
# User Data: employee, noone
self.user_employee_id = self.res_users.create(cr, uid, {
cls.user_employee_id = cls.res_users.create(cr, uid, {
'name': 'Ernest Employee',
'login': 'ernest',
'alias_name': 'ernest',
'email': 'e.e@example.com',
'signature': '--\nErnest',
'notify_email': 'always',
'groups_id': [(6, 0, [self.group_employee_id])]
'groups_id': [(6, 0, [cls.group_employee_id])]
}, {'no_reset_password': True})
self.user_noone_id = self.res_users.create(cr, uid, {
cls.user_noone_id = cls.res_users.create(cr, uid, {
'name': 'Noemie NoOne',
'login': 'noemie',
'alias_name': 'noemie',
@ -89,16 +92,16 @@ class TestMail(common.TransactionCase):
}, {'no_reset_password': True})
# Test users to use through the various tests
self.res_users.write(cr, uid, uid, {'name': 'Administrator'})
self.user_raoul_id = self.res_users.create(cr, uid, {
cls.res_users.write(cr, uid, uid, {'name': 'Administrator'})
cls.user_raoul_id = cls.res_users.create(cr, uid, {
'name': 'Raoul Grosbedon',
'signature': 'SignRaoul',
'email': 'raoul@raoul.fr',
'login': 'raoul',
'alias_name': 'raoul',
'groups_id': [(6, 0, [self.group_employee_id])]
'groups_id': [(6, 0, [cls.group_employee_id])]
})
self.user_bert_id = self.res_users.create(cr, uid, {
cls.user_bert_id = cls.res_users.create(cr, uid, {
'name': 'Bert Tartignole',
'signature': 'SignBert',
'email': 'bert@bert.fr',
@ -106,27 +109,28 @@ class TestMail(common.TransactionCase):
'alias_name': 'bert',
'groups_id': [(6, 0, [])]
})
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
self.user_admin = self.res_users.browse(cr, uid, uid)
self.partner_admin_id = self.user_admin.partner_id.id
self.partner_raoul_id = self.user_raoul.partner_id.id
self.partner_bert_id = self.user_bert.partner_id.id
cls.user_raoul = cls.res_users.browse(cr, uid, cls.user_raoul_id)
cls.user_bert = cls.res_users.browse(cr, uid, cls.user_bert_id)
cls.user_admin = cls.res_users.browse(cr, uid, uid)
cls.partner_admin_id = cls.user_admin.partner_id.id
cls.partner_raoul_id = cls.user_raoul.partner_id.id
cls.partner_bert_id = cls.user_bert.partner_id.id
# Test 'pigs' group to use through the various tests
self.group_pigs_id = self.mail_group.create(
cls.group_pigs_id = cls.mail_group.create(
cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
{'mail_create_nolog': True}
)
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
cls.group_pigs = cls.mail_group.browse(cr, uid, cls.group_pigs_id)
# Test mail.group: public to provide access to everyone
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
cls.group_jobs_id = cls.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
# Test mail.group: private to restrict access
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
cls.group_priv_id = cls.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
def tearDown(self):
@classmethod
def tearDownClass(cls):
# Remove mocks
self.registry('ir.mail_server')._revert_method('build_email')
self.registry('ir.mail_server')._revert_method('send_email')
super(TestMail, self).tearDown()
cls.registry('ir.mail_server')._revert_method('build_email')
cls.registry('ir.mail_server')._revert_method('send_email')
super(TestMail, cls).tearDownClass()

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.mail.tests.common import TestMail
from .common import TestMail
class test_invite(TestMail):

View File

@ -19,9 +19,9 @@
#
##############################################################################
from openerp.addons.mail.mail_mail import mail_mail
from openerp.addons.mail.mail_thread import mail_thread
from openerp.addons.mail.tests.common import TestMail
from ..mail_mail import mail_mail
from ..mail_thread import mail_thread
from .common import TestMail
from openerp.tools import mute_logger, email_split, html2plaintext
from openerp.tools.mail import html_sanitize

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.mail.tests.common import TestMail
from .common import TestMail
from openerp.tools import mute_logger
import socket

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.mail.tests.common import TestMail
from .common import TestMail
from openerp.exceptions import AccessError
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.mail.tests.common import TestMail
from .common import TestMail
from openerp.exceptions import AccessError
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.mail.tests.common import TestMail
from .common import TestMail
class test_mail_access_rights(TestMail):

View File

@ -27,26 +27,26 @@ from openerp.tools.misc import mute_logger
class test_portal(TestMail):
def setUp(self):
super(test_portal, self).setUp()
cr, uid = self.cr, self.uid
@classmethod
def setUpClass(cls):
super(test_portal, cls).setUpClass()
cr, uid = cls.cr, cls.uid
# Find Portal group
group_portal = self.registry('ir.model.data').get_object(cr, uid, 'base', 'group_portal')
self.group_portal_id = group_portal.id
cls.group_portal_id = cls.env.ref('base.group_portal').id
# Create Chell (portal user)
self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'email': 'chell@gladys.portal', 'groups_id': [(6, 0, [self.group_portal_id])]})
self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id)
self.partner_chell_id = self.user_chell.partner_id.id
cls.user_chell_id = cls.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'email': 'chell@gladys.portal', 'groups_id': [(6, 0, [cls.group_portal_id])]})
cls.user_chell = cls.res_users.browse(cr, uid, cls.user_chell_id)
cls.partner_chell_id = cls.user_chell.partner_id.id
# Create a PigsPortal group
self.group_port_id = self.mail_group.create(cr, uid,
{'name': 'PigsPortal', 'public': 'groups', 'group_public_id': self.group_portal_id},
cls.group_port_id = cls.mail_group.create(cr, uid,
{'name': 'PigsPortal', 'public': 'groups', 'group_public_id': cls.group_portal_id},
{'mail_create_nolog': True})
# Set an email address for the user running the tests, used as Sender for outgoing mails
self.res_users.write(cr, uid, uid, {'email': 'test@localhost'})
cls.res_users.write(cr, uid, uid, {'email': 'test@localhost'})
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.models')
def test_00_mail_access_rights(self):

View File

@ -24,76 +24,75 @@ from openerp.addons.mail.tests.common import TestMail
class TestProjectBase(TestMail):
def setUp(self):
super(TestProjectBase, self).setUp()
cr, uid = self.cr, self.uid
@classmethod
def setUpClass(cls):
super(TestProjectBase, cls).setUpClass()
cr, uid = cls.cr, cls.uid
# Usefull models
self.project_project = self.registry('project.project')
self.project_task = self.registry('project.task')
self.project_task_delegate = self.registry('project.task.delegate')
cls.project_project = cls.registry('project.project')
cls.project_task = cls.registry('project.task')
cls.project_task_delegate = cls.registry('project.task.delegate')
# Find Project User group
group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
cls.group_project_user_id = cls.env.ref('project.group_project_user').id or False
# Find Project Manager group
group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
cls.group_project_manager_id = cls.env.ref('project.group_project_manager').id or False
# Test partners to use through the various tests
self.project_partner_id = self.res_partner.create(cr, uid, {
cls.project_partner_id = cls.res_partner.create(cr, uid, {
'name': 'Gertrude AgrolaitPartner',
'email': 'gertrude.partner@agrolait.com',
})
self.email_partner_id = self.res_partner.create(cr, uid, {
cls.email_partner_id = cls.res_partner.create(cr, uid, {
'name': 'Patrick Ratatouille',
'email': 'patrick.ratatouille@agrolait.com',
})
# Test users to use through the various tests
self.user_projectuser_id = self.res_users.create(cr, uid, {
cls.user_projectuser_id = cls.res_users.create(cr, uid, {
'name': 'Armande ProjectUser',
'login': 'Armande',
'alias_name': 'armande',
'email': 'armande.projectuser@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
'groups_id': [(6, 0, [cls.group_employee_id, cls.group_project_user_id])]
})
self.user_projectmanager_id = self.res_users.create(cr, uid, {
cls.user_projectmanager_id = cls.res_users.create(cr, uid, {
'name': 'Bastien ProjectManager',
'login': 'bastien',
'alias_name': 'bastien',
'email': 'bastien.projectmanager@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_manager_id])]
'groups_id': [(6, 0, [cls.group_employee_id, cls.group_project_manager_id])]
})
self.user_none_id = self.res_users.create(cr, uid, {
cls.user_none_id = cls.res_users.create(cr, uid, {
'name': 'Charlie Avotbonkeur',
'login': 'charlie',
'alias_name': 'charlie',
'email': 'charlie.noone@example.com',
'groups_id': [(6, 0, [])]
})
self.user_projectuser = self.res_users.browse(cr, uid, self.user_projectuser_id)
self.user_projectmanager = self.res_users.browse(cr, uid, self.user_projectmanager_id)
self.partner_projectuser_id = self.user_projectuser.partner_id.id
self.partner_projectmanager_id = self.user_projectmanager.partner_id.id
cls.user_projectuser = cls.res_users.browse(cr, uid, cls.user_projectuser_id)
cls.user_projectmanager = cls.res_users.browse(cr, uid, cls.user_projectmanager_id)
cls.partner_projectuser_id = cls.user_projectuser.partner_id.id
cls.partner_projectmanager_id = cls.user_projectmanager.partner_id.id
# Test 'Pigs' project
self.project_pigs_id = self.project_project.create(cr, uid, {
cls.project_pigs_id = cls.project_project.create(cr, uid, {
'name': 'Pigs',
'privacy_visibility': 'public',
'alias_name': 'project+pigs',
'partner_id': self.partner_raoul_id,
'partner_id': cls.partner_raoul_id,
}, {'mail_create_nolog': True})
# Already-existing tasks in Pigs
self.task_1_id = self.project_task.create(cr, uid, {
cls.task_1_id = cls.project_task.create(cr, uid, {
'name': 'Pigs UserTask',
'user_id': self.user_projectuser_id,
'project_id': self.project_pigs_id,
'user_id': cls.user_projectuser_id,
'project_id': cls.project_pigs_id,
}, {'mail_create_nolog': True})
self.task_2_id = self.project_task.create(cr, uid, {
cls.task_2_id = cls.project_task.create(cr, uid, {
'name': 'Pigs ManagerTask',
'user_id': self.user_projectmanager_id,
'project_id': self.project_pigs_id,
'user_id': cls.user_projectmanager_id,
'project_id': cls.project_pigs_id,
}, {'mail_create_nolog': True})

View File

@ -19,7 +19,7 @@
#
##############################################################################
from openerp.addons.project.tests.test_project_base import TestProjectBase
from .test_project_base import TestProjectBase
from openerp.exceptions import AccessError
from openerp.tools import mute_logger

View File

@ -1,23 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2012-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_survey

View File

@ -1,40 +0,0 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2014-TODAY OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.tests import common
class test_survey(common.TransactionCase):
def setUp(self):
super(test_survey, self).setUp()
cr, uid, context = self.cr, self.uid, {}
pass
def test_00_create_survey_and_questions(self):
cr, uid, context = self.cr, self.uid, {}
pass
def test_01_fill_survey(self):
pass
def test_02_answer_survey(self):
pass

View File

@ -15,20 +15,21 @@ KARMA = {
}
class TestForumCommon(common.TransactionCase):
class TestForumCommon(common.SavepointCase):
def setUp(self):
super(TestForumCommon, self).setUp()
@classmethod
def setUpClass(cls):
super(TestForumCommon, cls).setUpClass()
Forum = self.env['forum.forum']
Post = self.env['forum.post']
Forum = cls.env['forum.forum']
Post = cls.env['forum.post']
# Test users
TestUsersEnv = self.env['res.users'].with_context({'no_reset_password': True})
group_employee_id = self.ref('base.group_user')
group_portal_id = self.ref('base.group_portal')
group_public_id = self.ref('base.group_public')
self.user_employee = TestUsersEnv.create({
TestUsersEnv = cls.env['res.users'].with_context({'no_reset_password': True})
group_employee_id = cls.env.ref('base.group_user').id
group_portal_id = cls.env.ref('base.group_portal').id
group_public_id = cls.env.ref('base.group_public').id
cls.user_employee = TestUsersEnv.create({
'name': 'Armande Employee',
'login': 'Armande',
'alias_name': 'armande',
@ -36,7 +37,7 @@ class TestForumCommon(common.TransactionCase):
'karma': 0,
'groups_id': [(6, 0, [group_employee_id])]
})
self.user_portal = TestUsersEnv.create({
cls.user_portal = TestUsersEnv.create({
'name': 'Beatrice Portal',
'login': 'Beatrice',
'alias_name': 'beatrice',
@ -44,7 +45,7 @@ class TestForumCommon(common.TransactionCase):
'karma': 0,
'groups_id': [(6, 0, [group_portal_id])]
})
self.user_public = TestUsersEnv.create({
cls.user_public = TestUsersEnv.create({
'name': 'Cedric Public',
'login': 'Cedric',
'alias_name': 'cedric',
@ -54,7 +55,7 @@ class TestForumCommon(common.TransactionCase):
})
# Test forum
self.forum = Forum.create({
cls.forum = Forum.create({
'name': 'TestForum',
'karma_ask': KARMA['ask'],
'karma_answer': KARMA['ans'],
@ -79,15 +80,15 @@ class TestForumCommon(common.TransactionCase):
'karma_gen_answer_accept': 9999,
'karma_gen_answer_accepted': 9999,
})
self.post = Post.create({
cls.post = Post.create({
'name': 'TestQuestion',
'content': 'I am not a bird.',
'forum_id': self.forum.id,
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': self.forum.id})]
'forum_id': cls.forum.id,
'tag_ids': [(0, 0, {'name': 'Tag0', 'forum_id': cls.forum.id})]
})
self.answer = Post.create({
cls.answer = Post.create({
'name': 'TestAnswer',
'content': 'I am an anteater.',
'forum_id': self.forum.id,
'parent_id': self.post.id,
'forum_id': cls.forum.id,
'parent_id': cls.post.id,
})

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
from openerp.addons.website_forum.tests.common import KARMA, TestForumCommon
from openerp.addons.website_forum.models.forum import KarmaError
from .common import KARMA, TestForumCommon
from ..models.forum import KarmaError
from openerp.exceptions import Warning, AccessError
from openerp.tools import mute_logger

View File

@ -13,6 +13,7 @@ import select
import subprocess
import threading
import time
import itertools
import unittest2
import urllib2
import xmlrpclib
@ -171,6 +172,27 @@ class SingleTransactionCase(BaseCase):
cls.cr.close()
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):
self._savepoint_id = next(savepoint_seq)
self.cr.execute('SAVEPOINT test_%d' % self._savepoint_id)
def tearDown(self):
self.cr.execute('ROLLBACK TO SAVEPOINT test_%d' % self._savepoint_id)
self.env.clear()
self.registry.clear_caches()
class RedirectHandler(urllib2.HTTPRedirectHandler):
"""
HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and