Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit ebc1a30

Browse files
committed
Closes issue 21239. unittest.mock.patch.stopall() did not work deterministically when the same name was patched multiple times.
1 parent d943fde commit ebc1a30

3 files changed

Lines changed: 31 additions & 6 deletions

File tree

Lib/unittest/mock.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1050,7 +1050,7 @@ def _is_started(patcher):
10501050
class _patch(object):
10511051

10521052
attribute_name = None
1053-
_active_patches = set()
1053+
_active_patches = []
10541054

10551055
def __init__(
10561056
self, getter, attribute, new, spec, create,
@@ -1323,13 +1323,18 @@ def __exit__(self, *exc_info):
13231323
def start(self):
13241324
"""Activate a patch, returning any created mock."""
13251325
result = self.__enter__()
1326-
self._active_patches.add(self)
1326+
self._active_patches.append(self)
13271327
return result
13281328

13291329

13301330
def stop(self):
13311331
"""Stop an active patch."""
1332-
self._active_patches.discard(self)
1332+
try:
1333+
self._active_patches.remove(self)
1334+
except ValueError:
1335+
# If the patch hasn't been started this will fail
1336+
pass
1337+
13331338
return self.__exit__()
13341339

13351340

@@ -1622,8 +1627,8 @@ def _clear_dict(in_dict):
16221627

16231628

16241629
def _patch_stopall():
1625-
"""Stop all active patches."""
1626-
for patch in list(_patch._active_patches):
1630+
"""Stop all active patches. LIFO to unroll nested patches."""
1631+
for patch in reversed(_patch._active_patches):
16271632
patch.stop()
16281633

16291634

Lib/unittest/test/testmock/testpatch.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from unittest.mock import (
1313
NonCallableMock, CallableMixin, patch, sentinel,
1414
MagicMock, Mock, NonCallableMagicMock, patch, _patch,
15-
DEFAULT, call, _get_target
15+
DEFAULT, call, _get_target, _patch
1616
)
1717

1818

@@ -1779,6 +1779,23 @@ def patched(mock_path):
17791779
patched()
17801780
self.assertIs(os.path, path)
17811781

1782+
def test_stopall_lifo(self):
1783+
stopped = []
1784+
class thing(object):
1785+
one = two = three = None
1786+
1787+
def get_patch(attribute):
1788+
class mypatch(_patch):
1789+
def stop(self):
1790+
stopped.append(attribute)
1791+
return super(mypatch, self).stop()
1792+
return mypatch(lambda: thing, attribute, None, None,
1793+
False, None, None, None, {})
1794+
[get_patch(val).start() for val in ("one", "two", "three")]
1795+
patch.stopall()
1796+
1797+
self.assertEqual(stopped, ["three", "two", "one"])
1798+
17821799

17831800
if __name__ == '__main__':
17841801
unittest.main()

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ Core and Builtins
3737
Library
3838
-------
3939

40+
- Issue #21239: patch.stopall() didn't work deterministically when the same
41+
name was patched more than once.
42+
4043
- Issue #21222: Passing name keyword argument to mock.create_autospec now
4144
works.
4245

0 commit comments

Comments
 (0)