Issue39485
Created on 2020-01-29 13:49 by Carl.Friedrich.Bolz, last changed 2022-04-11 14:59 by admin. This issue is now closed.
| Pull Requests | |||
|---|---|---|---|
| URL | Status | Linked | Edit |
| PR 18252 | merged | Carl.Friedrich.Bolz, 2020-01-29 14:09 | |
| PR 18255 | merged | miss-islington, 2020-01-29 15:44 | |
| PR 18256 | merged | miss-islington, 2020-01-29 15:44 | |
| Messages (5) | |||
|---|---|---|---|
| msg360961 - (view) | Author: Carl Friedrich Bolz-Tereick (Carl.Friedrich.Bolz) * | Date: 2020-01-29 13:49 | |
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.
The relevant part of the test looks like this:
from unittest.mock import create_autospec
class CrazyDescriptor(object):
def __get__(self, obj, type_):
if obj is None:
return lambda x: None
class MyClass(object):
some_attr = CrazyDescriptor()
mock = create_autospec(MyClass)
mock.some_attr(1)
On CPython this just works, on PyPy it fails with:
Traceback (most recent call last):
File "x.py", line 13, in <module>
mock.some_attr(1)
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/unittest/mock.py", line 938, in __call__
_mock_self._mock_check_sig(*args, **kwargs)
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/unittest/mock.py", line 101, in checksig
sig.bind(*args, **kwargs)
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/inspect.py", line 3034, in bind
return args[0]._bind(args[1:], kwargs)
File "/home/cfbolz/bin/.pyenv/versions/pypy3.6-7.2.0/lib-python/3/inspect.py", line 2955, in _bind
raise TypeError('too many positional arguments') from None
TypeError: 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)
return is_type
else:
return False
MethodWrapperTypes is defined as:
MethodWrapperTypes = (
type(ANY.__eq__.__get__),
)
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.
Will open a pull request.
|
|||
| msg360968 - (view) | Author: Chris Withers (cjw296) * | Date: 2020-01-29 15:43 | |
New changeset a327677905956ae0b239ff430a1346dfe265709e by Carl Friedrich Bolz-Tereick in branch 'master': bpo-39485: fix corner-case in method-detection of mock (GH-18252) https://github.com/python/cpython/commit/a327677905956ae0b239ff430a1346dfe265709e |
|||
| msg360969 - (view) | Author: Chris Withers (cjw296) * | Date: 2020-01-29 16:10 | |
New changeset cf0645a17acbc0c4dbbf82434e37637965748bbb by Miss Islington (bot) in branch '3.7': bpo-39485: fix corner-case in method-detection of mock (GH-18256) https://github.com/python/cpython/commit/cf0645a17acbc0c4dbbf82434e37637965748bbb |
|||
| msg360970 - (view) | Author: Chris Withers (cjw296) * | Date: 2020-01-29 16:15 | |
New changeset 696d2324cf2a54e20e8d6a6739fa97ba815a8be9 by Miss Islington (bot) in branch '3.8': bpo-39485: fix corner-case in method-detection of mock (GH-18255) https://github.com/python/cpython/commit/696d2324cf2a54e20e8d6a6739fa97ba815a8be9 |
|||
| msg360971 - (view) | Author: Chris Withers (cjw296) * | Date: 2020-01-29 16:20 | |
Thank you very much for this, that was a really good catch! |
|||
| History | |||
|---|---|---|---|
| Date | User | Action | Args |
| 2022-04-11 14:59:26 | admin | set | github: 83666 |
| 2020-01-29 16:20:19 | cjw296 | set | status: open -> closed resolution: fixed messages: + msg360971 stage: patch review -> resolved |
| 2020-01-29 16:15:39 | cjw296 | set | messages: + msg360970 |
| 2020-01-29 16:10:31 | cjw296 | set | messages: + msg360969 |
| 2020-01-29 15:44:36 | miss-islington | set | pull_requests: + pull_request17633 |
| 2020-01-29 15:44:19 | miss-islington | set | pull_requests: + pull_request17632 |
| 2020-01-29 15:43:54 | cjw296 | set | messages: + msg360968 |
| 2020-01-29 15:37:56 | cjw296 | set | assignee: cjw296 |
| 2020-01-29 14:09:08 | Carl.Friedrich.Bolz | set | keywords:
+ patch stage: patch review pull_requests: + pull_request17629 |
| 2020-01-29 13:49:55 | Carl.Friedrich.Bolz | create | |