From 908252ec88cd4516ee84fdf93c07d09f51a45413 Mon Sep 17 00:00:00 2001 From: Raphael Collet Date: Thu, 13 Nov 2014 17:40:41 +0100 Subject: [PATCH] [FIX] tests: make sure that a failed tests does not leave the environment dirty When a failure occurs, or when exiting an assertRaises(), the environment should not contain fields to recompute. --- openerp/addons/base/tests/test_acl.py | 4 +--- .../test_new_api/tests/test_new_fields.py | 3 +++ openerp/api.py | 18 +++++++++++++++++ openerp/tests/common.py | 20 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) diff --git a/openerp/addons/base/tests/test_acl.py b/openerp/addons/base/tests/test_acl.py index 366a1712978..9f6be133947 100644 --- a/openerp/addons/base/tests/test_acl.py +++ b/openerp/addons/base/tests/test_acl.py @@ -111,12 +111,10 @@ class TestACL(common.TransactionCase): # accessing fields must no raise exceptions... part.name # ... except if they are restricted - with self.assertRaises(openerp.osv.orm.except_orm) as cm: + with self.assertRaises(openerp.exceptions.AccessError): with mute_logger('openerp.models'): part.email - self.assertEqual(cm.exception.args[0], 'AccessError') - if __name__ == '__main__': unittest2.main() diff --git a/openerp/addons/test_new_api/tests/test_new_fields.py b/openerp/addons/test_new_api/tests/test_new_fields.py index 74f85fa6d68..126a967ba48 100644 --- a/openerp/addons/test_new_api/tests/test_new_fields.py +++ b/openerp/addons/test_new_api/tests/test_new_fields.py @@ -185,6 +185,9 @@ class TestNewFields(common.TransactionCase): with self.assertRaises(Exception): self.env['test_new_api.message'].create({'discussion': discussion.id, 'body': 'Whatever'}) + # make sure that assertRaises() does not leave fields to recompute + self.assertFalse(self.env.has_todo()) + # put back oneself into discussion participants: now we can create # messages in discussion discussion.participants += self.env.user diff --git a/openerp/api.py b/openerp/api.py index da9e62f1549..94ed98c1265 100644 --- a/openerp/api.py +++ b/openerp/api.py @@ -815,6 +815,24 @@ class Environment(object): env.computed.clear() env.dirty.clear() + def clear(self): + """ Clear all record caches, and discard all fields to recompute. + This may be useful when recovering from a failed ORM operation. + """ + self.invalidate_all() + self.all.todo.clear() + + @contextmanager + def clear_upon_failure(self): + """ Context manager that clears the environments (caches and fields to + recompute) upon exception. + """ + try: + yield + except Exception: + self.clear() + raise + def field_todo(self, field): """ Check whether `field` must be recomputed, and returns a recordset with all records to recompute for `field`. diff --git a/openerp/tests/common.py b/openerp/tests/common.py index b3f6dbe81a3..db84855b36a 100644 --- a/openerp/tests/common.py +++ b/openerp/tests/common.py @@ -16,6 +16,7 @@ import time import unittest2 import urllib2 import xmlrpclib +from contextlib import contextmanager from datetime import datetime, timedelta import werkzeug @@ -104,6 +105,20 @@ class BaseCase(unittest2.TestCase): module, xid = xid.split('.') return self.registry('ir.model.data').get_object(self.cr, self.uid, module, xid) + @contextmanager + def _assertRaises(self, exception): + """ Context manager that clears the environment upon failure. """ + with super(BaseCase, self).assertRaises(exception): + with self.env.clear_upon_failure(): + yield + + def assertRaises(self, exception, func=None, *args, **kwargs): + if func: + with self._assertRaises(exception): + func(*args, **kwargs) + else: + return self._assertRaises(exception) + class TransactionCase(BaseCase): """ TestCase in which each test method is run in its own transaction, @@ -120,6 +135,8 @@ class TransactionCase(BaseCase): self.env = api.Environment(self.cr, self.uid, {}) def tearDown(self): + # rollback and close the cursor, and reset the environments + self.env.reset() self.cr.rollback() self.cr.close() @@ -139,9 +156,12 @@ class SingleTransactionCase(BaseCase): @classmethod def tearDownClass(cls): + # rollback and close the cursor, and reset the environments + cls.env.reset() cls.cr.rollback() cls.cr.close() + class RedirectHandler(urllib2.HTTPRedirectHandler): """ HTTPRedirectHandler is predicated upon HTTPErrorProcessor being used and