-
-
Notifications
You must be signed in to change notification settings - Fork 30.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
mock.patch.stopall doesn't work with patch.dict #65799
Comments
It seems that stopall doesn't work when do start patch.dict to sys.modules. Using stopall test case seems to always refer the first mock foo object. $ cat test_sampmod.py
import foo def myfunc():
print "myfunc foo=%s" % foo
return foo
$ cat test_samp.py
import mock
import sys
import unittest
class SampTestCase(unittest.TestCase):
def setUp(self):
self.foo_mod = mock.Mock()
self.m = mock.patch.dict('sys.modules', {'foo': self.foo_mod})
self.p = self.m.start()
print "foo_mod=%s" % self.foo_mod
__import__('test_sampmod')
self.test_sampmod = sys.modules['test_sampmod']
def tearDown(self):
if len(sys.argv) > 1:
self.m.stop()
print ">>> stop patch"
else:
mock.patch.stopall()
print ">>> stopall patch"
def test_samp1(self):
self.assertEqual(self.foo_mod, self.test_sampmod.myfunc())
def test_samp2(self):
self.assertEqual(self.foo_mod, self.test_sampmod.myfunc())
def test_samp3(self):
self.assertEqual(self.foo_mod, self.test_sampmod.myfunc())
if __name__ == '__main__':
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(SampTestCase))
unittest.TextTestRunner(verbosity=2).run(suite)
$ python test_samp.py stop
test_samp1 (__main__.SampTestCase) ... foo_mod=<Mock id='41504336'>
myfunc foo=<Mock id='41504336'>
>>> stop patch
ok
test_samp2 (__main__.SampTestCase) ... foo_mod=<Mock id='41504464'>
myfunc foo=<Mock id='41504464'>
>>> stop patch
ok
test_samp3 (__main__.SampTestCase) ... foo_mod=<Mock id='41504720'>
myfunc foo=<Mock id='41504720'>
>>> stop patch
ok Ran 3 tests in 0.004s OK
$ python test_samp.py
test_samp1 (__main__.SampTestCase) ... foo_mod=<Mock id='19152464'>
myfunc foo=<Mock id='19152464'>
>>> stopall patch
ok
test_samp2 (__main__.SampTestCase) ... foo_mod=<Mock id='19152592'>
myfunc foo=<Mock id='19152464'>
FAIL
>>> stopall patch
test_samp3 (__main__.SampTestCase) ... foo_mod=<Mock id='19182096'>
myfunc foo=<Mock id='19152464'>
FAIL
>>> stopall patch ====================================================================== Traceback (most recent call last):
File "test_samp.py", line 27, in test_samp2
self.assertEqual(self.foo_mod, self.test_sampmod.myfunc())
AssertionError: <Mock id='19152592'> != <Mock id='19152464'> ====================================================================== Traceback (most recent call last):
File "test_samp.py", line 30, in test_samp3
self.assertEqual(self.foo_mod, self.test_sampmod.myfunc())
AssertionError: <Mock id='19182096'> != <Mock id='19152464'> Ran 3 tests in 0.003s FAILED (failures=2) |
Checking mock.py(version 1.0.1) it seems that patch.stopall does not support patch.dict. Does it have any problem to support ptch.dict by stopall. But I don't know why sys.modules refers the first mock object. $ python test_samp.py
test_samp1 (__main__.SampTestCase) ... foo_mod=<Mock id='140164117280080'>
myfunc foo=<Mock id='140164117280080'>
>>> stopall patch
ok
test_samp2 (__main__.SampTestCase) ... foo_mod=<Mock id='140164117280208'>
myfunc foo=<Mock id='140164117280208'>
>>> stopall patch
ok
test_samp3 (__main__.SampTestCase) ... foo_mod=<Mock id='140164117280464'>
myfunc foo=<Mock id='140164117280464'>
>>> stopall patch
ok Ran 3 tests in 0.001s OK |
Yep, patch.dict wasn't designed with stopall in mind so it needs adding. Thanks for pointing this out and your fix. Your patch isn't quite right, those operations shouldn't be inside the try excepts. (And there are no tests.) |
Thank you for your reply. |
That's better - thanks. Another minor tweak needed though. stopall should only stop patches that were started with "start", not those used as context managers or decorators (or they will be stopped twice!). See how the main patch object only adds to the set of active patches in the start method, not in __enter__ (and removes in stop rather than __exit__). |
Hi michael, |
That looks great - thanks! I'll get it committed shortly. |
Thank you in advance. |
What's the status on this? |
It just needs committing, I believe Kushal Das has volunteered to do it. |
I will do that. New job is taking time. |
FYI, you probably don't want to be patching out sys.modules. It isn't guaranteed that doing so will have the expected effect on import semantics. |
why not? |
Patching sys.modules is an idea I got from the official documentation https://docs.python.org/3/library/unittest.mock.html#patch-dict |
At least for CPython sys.modules is initially set to the modules dict on the interpreter struct. As of 3.4 the import system itself only cares about sys.modules (I'll have to double check on builtin___import__). However, if I recall correctly at least part of the import C-API interacts directly with the original interpreter copy of the modules dict. The catch is that setting sys.modules to something else does not also change the dict on the interpreter struct. So they will be out of sync. This may cause problems. In practice I don't think it's a big deal, but there is no guarantee that patching out sys.modules will give you the behavior that you expect. See also issue bpo-12633. |
Using patch.dict manipulates the contents of sys.modules, it doesn't replace sys.modules. |
Ah. Never mind then. :) |
Hi, It's been 3 years now since this issue was first raised. def tearDown():
patch.stopall()
def test123():
p=patch.dict(...)
p.start()
assert False
p.stop() While I could understand that this is just a design choice (which may seem unintuitive for me, but possibly perfectly good for others), however the official documentation [1] states:
And few lines below [2], we have:
The above is not true for Is there a possibility to fix this in some 3.5 maintenance release (preferably code, not docs :) )? If anyone else will have the same issue: for now a workaround would be to use [1] https://docs.python.org/3/library/unittest.mock.html#patch-methods-start-and-stop |
Hi, Any chance of getting this resolved 1 year later? :) BR |
This sounds like a good idea given the docs and the general agreement in the thread. I would like to target this for 3.9 . Differentiation between the normal patches and the ones from patch.dict is nice but given that docstring indicates LIFO for all active patches storing patch.dict items also in _patch.active_patches ensures we can do LIFO if there is a mixture of normal and dictionary patches started with start method. Adding other maintainers for thoughts. diff --git Lib/unittest/mock.py Lib/unittest/mock.py - start = __enter__
- stop = __exit__
+ def start(self):
+ """Activate a patch, returning any created mock."""
+ result = self.__enter__()
+ _patch._active_patches.append(self)
+ return result
+
+ def stop(self):
+ """Stop an active patch."""
+ try:
+ _patch._active_patches.remove(self)
+ except ValueError:
+ # If the patch hasn't been started this will fail
+ pass
+
+ return self.__exit__() |
Makes total sense, I think we should get this for 3.9. I'm happy to push a PR with the proposed change and some tests if you want @XTreak. This is quite a simple fix we can get through in a short time. |
Thanks Mario. I think the last patch attached to the issue is complete with tests and needs to be updated master. Feel free to raise a PR. |
mock.patch.dict
to be stopped withmock.patch.stopall
#17606Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: