[Python-checkins] cpython (merge 3.4 -> default): Merge

michael.foord python-checkins at python.org
Tue Apr 15 23:22:35 CEST 2014


http://hg.python.org/cpython/rev/42d59de284e2
changeset:   90339:42d59de284e2
parent:      90336:69d479e9aa82
parent:      90338:727b7e9c40e3
user:        Michael Foord <michael at voidspace.org.uk>
date:        Tue Apr 15 17:22:22 2014 -0400
summary:
  Merge

files:
  Lib/unittest/mock.py                    |  15 ++++++---
  Lib/unittest/test/testmock/testpatch.py |  19 ++++++++++++-
  Misc/NEWS                               |   5 ++-
  3 files changed, 32 insertions(+), 7 deletions(-)


diff --git a/Lib/unittest/mock.py b/Lib/unittest/mock.py
--- a/Lib/unittest/mock.py
+++ b/Lib/unittest/mock.py
@@ -1054,7 +1054,7 @@
 class _patch(object):
 
     attribute_name = None
-    _active_patches = set()
+    _active_patches = []
 
     def __init__(
             self, getter, attribute, new, spec, create,
@@ -1330,13 +1330,18 @@
     def start(self):
         """Activate a patch, returning any created mock."""
         result = self.__enter__()
-        self._active_patches.add(self)
+        self._active_patches.append(self)
         return result
 
 
     def stop(self):
         """Stop an active patch."""
-        self._active_patches.discard(self)
+        try:
+            self._active_patches.remove(self)
+        except ValueError:
+            # If the patch hasn't been started this will fail
+            pass
+
         return self.__exit__()
 
 
@@ -1629,8 +1634,8 @@
 
 
 def _patch_stopall():
-    """Stop all active patches."""
-    for patch in list(_patch._active_patches):
+    """Stop all active patches. LIFO to unroll nested patches."""
+    for patch in reversed(_patch._active_patches):
         patch.stop()
 
 
diff --git a/Lib/unittest/test/testmock/testpatch.py b/Lib/unittest/test/testmock/testpatch.py
--- a/Lib/unittest/test/testmock/testpatch.py
+++ b/Lib/unittest/test/testmock/testpatch.py
@@ -12,7 +12,7 @@
 from unittest.mock import (
     NonCallableMock, CallableMixin, patch, sentinel,
     MagicMock, Mock, NonCallableMagicMock, patch, _patch,
-    DEFAULT, call, _get_target
+    DEFAULT, call, _get_target, _patch
 )
 
 
@@ -1799,6 +1799,23 @@
         patched()
         self.assertIs(os.path, path)
 
+    def test_stopall_lifo(self):
+        stopped = []
+        class thing(object):
+            one = two = three = None
+
+        def get_patch(attribute):
+            class mypatch(_patch):
+                def stop(self):
+                    stopped.append(attribute)
+                    return super(mypatch, self).stop()
+            return mypatch(lambda: thing, attribute, None, None,
+                           False, None, None, None, {})
+        [get_patch(val).start() for val in ("one", "two", "three")]
+        patch.stopall()
+
+        self.assertEqual(stopped, ["three", "two", "one"])
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -46,9 +46,12 @@
 Library
 -------
 
+- Issue #21239: patch.stopall() didn't work deterministically when the same
+  name was patched more than once.
+
 - Issue #21203: Updated fileConfig and dictConfig to remove inconsistencies.
   Thanks to Jure Koren for the patch.
-  
+
 - Issue #21222: Passing name keyword argument to mock.create_autospec now
   works.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list