From 1b60df85ba3ad442c2e4e7e52538e1b9a1bf9378 Mon Sep 17 00:00:00 2001 From: Kevin Benton Date: Thu, 25 Jun 2015 18:34:38 -0700 Subject: [PATCH] Add a double-mock guard to the base test case 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 --- neutron/tests/base.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/neutron/tests/base.py b/neutron/tests/base.py index 31068a977..28391d5db 100644 --- a/neutron/tests/base.py +++ b/neutron/tests/base.py @@ -154,6 +154,7 @@ class DietTestCase(testtools.TestCase): 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'): @@ -166,6 +167,34 @@ class DietTestCase(testtools.TestCase): 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: -- 2.45.2