"TypeError: too many positional arguments" when using create_autospec on pypy3
Closed this issue · 6 comments
Once all the backports are up to date, this fails with:
__________ SpecSignatureTest.test_autospec_on_bound_builtin_function ___________
self = <mock.tests.testhelpers.SpecSignatureTest testMethod=test_autospec_on_bound_builtin_function>
def test_autospec_on_bound_builtin_function(self):
meth = six.create_bound_method(time.ctime, time.time())
self.assertIsInstance(meth(), str)
mocked = create_autospec(meth)
# no signature, so no spec to check against
mocked()
mocked.assert_called_once_with()
mocked.reset_mock()
> mocked(4, 5, 6)
mock/tests/testhelpers.py:989:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
<string>:2: in ctime
???
mock/mock.py:279: in checksig
sig.bind(*args, **kwargs)
/opt/python/pypy3.6-7.1.1/lib-python/3/inspect.py:2970: in bind
return args[0]._bind(args[1:], kwargs)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
self = <Signature ()>, args = (4, 5, 6), kwargs = {}
def _bind(self, args, kwargs, *, partial=False):
"""Private method. Don't use directly."""
arguments = OrderedDict()
parameters = iter(self.parameters.values())
parameters_ex = ()
arg_vals = iter(args)0m
while True:
# Let's iterate through the positional arguments and corresponding
# parameters
try:
arg_val = next(arg_vals)
except StopIteration:
# No more positional arguments
try:
param = next(parameters)
except StopIteration:
# No more parameters. That's it. Just need to check that
# we have no `kwargs` after this while loop
break
else:
if param.kind == _VAR_POSITIONAL:
# That's OK, just empty *args. Let's start parsing
# kwargs
break
elif param.name in kwargs:
if param.kind == _POSITIONAL_ONLY:
msg = '{arg!r} parameter is positional only, ' \
'but was passed as a keyword'
msg = msg.format(arg=param.name)
raise TypeError(msg) from None
parameters_ex = (param,)
break
elif (param.kind == _VAR_KEYWORD or
param.default is not _empty):
# That's fine too - we have a default value for this
# parameter. So, lets start parsing `kwargs`, starting
# with the current parameter
parameters_ex = (param,)
break
else:
# No default, not VAR_KEYWORD, not VAR_POSITIONAL,
# not in `kwargs`
if partial:
parameters_ex = (param,)
break
else:
msg = 'missing a required argument: {arg!r}'
msg = msg.format(arg=param.name)
raise TypeError(msg) from None
else:
# We have a positional argument to process
try:
param = next(parameters)
except StopIteration:
> raise TypeError('too many positional arguments') from None
E TypeError: too many positional arguments
Here's the full build log:
https://travis-ci.org/testing-cabal/mock/jobs/526308383
I don't want this to hold up a new backport release, so parking this issue here.
Asked the pypy project for help here: https://bitbucket.org/pypy/pypy/issues/3010/help-with-failing-test-on-mock-backport
Sadly no response from the pypy guys, so closing this as wontfix :-(
This is a documented difference between CPython and PyPy, look for 'inspect' here: https://doc.pypy.org/en/latest/cpython_differences.html#miscellaneous
in PyPy, even builtin functions have signatures that the inspect module can find. Therefore create_autospec
actually does the right thing in this test, and refuses to let you call the mock with the wrong number of arguments.
I think it's the right approach to just skip the test on PyPy (or even in general on non-CPython implementations).
@cfbolz - thanks for the followup, I'm unsure whether it'd be best for you if I followed up here or on https://bitbucket.org/pypy/pypy/issues/3010/help-with-failing-test-on-mock-backport, please let me know if you have a preference.
Okay, so for SpecSignatureTest.test_autospec_on_bound_builtin_function
above, I see what you're saying and agree, so I can fix the test to show pypy gets it right, however, it doesn't appear to on this one:
def test_spec_has_descriptor_returning_function(self):
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:
MyClass.some_attr(1)
mock.some_attr(1)
<MagicMock name='mock.some_attr()' id='4566378704'>
On pypy:
(Pdb) MyClass.some_attr(1)
(Pdb) mock.some_attr(1)
*** TypeError: too many positional arguments
Why is pypy getting this one wrong?
Interesting, the other skip, 9aa24d5 appears to no longer be necessary, so I've removed it.
For this last one, see https://bugs.python.org/issue39485.