From 908252ec88cd4516ee84fdf93c07d09f51a45413 Mon Sep 17 00:00:00 2001
From: Raphael Collet <rco@openerp.com>
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 366a17129785..9f6be133947e 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 74f85fa6d684..126a967ba484 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 da9e62f1549c..94ed98c12658 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 b3f6dbe81a36..db84855b36a7 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
-- 
GitLab