Skip to content

Commit

Permalink
Add a double-mock guard to the base test case
Browse files Browse the repository at this point in the history
Use mock to patch mock with a check to prevent multiple active
patches to the same target. Multiple patches to the same target
result in non-deterministic behavior when stopall() tries to
undo the patches.[1]

1. http://bugs.python.org/issue21239

Change-Id: I3dd3d561a0267d80f464c15d69a4258b0a5e8aba
Closes-Bug: #1468998
  • Loading branch information
kevinbenton authored and Kevin Benton committed Jun 26, 2015
1 parent 78700ba commit 1b60df8
Showing 1 changed file with 29 additions and 0 deletions.
29 changes: 29 additions & 0 deletions neutron/tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ def setUp(self):
self.useFixture(fixtures.NestedTempfile())
self.useFixture(fixtures.TempHomeDir())

self.setup_double_mock_guard()
self.addCleanup(mock.patch.stopall)

if bool_from_env('OS_STDOUT_CAPTURE'):
Expand All @@ -166,6 +167,34 @@ def setUp(self):
self.addOnException(self.check_for_systemexit)
self.orig_pid = os.getpid()

def setup_double_mock_guard(self):
# mock.patch.stopall() uses a set in python < 3.4 so patches may not
# be unwound in the same order they were applied. This can leak mocks
# and cause tests down the line to fail.
# More info: http://bugs.python.org/issue21239
#
# Use mock to patch mock.patch.start to check if a target has already
# been patched and fail if it has.
self.first_traceback = {}
orig_start = mock._patch.start

def new_start(mself):
mytarget = mself.getter()
myattr = mself.attribute
for patch in mself._active_patches:
if (mytarget, myattr) == (patch.target, patch.attribute):
key = str((patch.target, patch.attribute))
self.fail("mock.patch was setup on an already patched "
"target %s.%s. Stop the original patch before "
"starting a new one. Traceback of 1st patch: %s"
% (mytarget, myattr,
''.join(self.first_traceback.get(key, []))))
self.first_traceback[
str((mytarget, myattr))] = traceback.format_stack()[:-2]
return orig_start(mself)

mock.patch('mock._patch.start', new=new_start).start()

def check_for_systemexit(self, exc_info):
if isinstance(exc_info[1], SystemExit):
if os.getpid() != self.orig_pid:
Expand Down

0 comments on commit 1b60df8

Please sign in to comment.