You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
title='Bug in mock running on PyPy3'updated_at=<Date2020-01-29.16:20:19.066>user='https://github.com/cfbolz'
One of the new-in-3.8 tests for unittest.mock, test_spec_has_descriptor_returning_function, is failing on PyPy. This exposes a bug in unittest.mock. The bug is most noticeable on PyPy, where it can be triggered by simply writing a slightly weird descriptor (CrazyDescriptor in the test). Getting it to trigger on CPython would be possible too, by implementing the same descriptor in C, but I did not actually do that.
On CPython this just works, on PyPy it fails with:
Traceback (most recent call last):
File "x.py", line 13, in <module>
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/unittest/mock.py", line 938, in __call__
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/unittest/mock.py", line 101, in checksig
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/inspect.py", line 3034, in bindreturn args._bind(args[1:], kwargs)
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/inspect.py", line 2955, in _bindraiseTypeError('too many positional arguments') fromNoneTypeError: too many positional arguments
The reason for this problem is that mock deduced that MyClass.some_attr is a method on PyPy. Since mock thinks the lambda returned by the descriptor is a method, it adds self as an argument, which leads to the TypeError.
Checking whether something is a method is done by _must_skip in mock.py. The relevant condition is this one:
elif isinstance(getattr(result, '__get__', None), MethodWrapperTypes):
# Normal method => skip if looked up on type
# (if looked up on instance, self is already skipped)
MethodWrapperTypes is defined as:
which is just types.MethodType on PyPy, because there is no such thing as a method wrapper (the builtin types look pretty much like python-defined types in PyPy).
On PyPy the condition isinstance(getattr...) is thus True for all descriptors! so as soon as result has a __get__, it counts as a method, even in the above case where it's a custom descriptor.
Now even on CPython the condition makes no sense to me. It would be True for a C-defined version of CrazyDescriptor, it's just not a good way to check whether result is a method.
I would propose to replace the condition with the much more straightforward check:
elif isinstance(result, FunctionTypes):
something is a method if it's a function on the class. Doing that change makes the test pass on PyPy, and doesn't introduce any test failures on CPython either.