From 10b998f52917ad59a5edef7bcd94d0d8eeefd227 Mon Sep 17 00:00:00 2001 From: Ceridwen Date: Thu, 19 Oct 2017 15:19:44 -0700 Subject: [PATCH] Clean up getfuncargnames() and fix tests to be compatible --- _pytest/compat.py | 90 +++++++++++---------------------------- _pytest/fixtures.py | 3 +- testing/python/fixture.py | 5 +-- 3 files changed, 26 insertions(+), 72 deletions(-) diff --git a/_pytest/compat.py b/_pytest/compat.py index 96896e8d4c6..8499e888205 100644 --- a/_pytest/compat.py +++ b/_pytest/compat.py @@ -87,9 +87,8 @@ def num_mock_patch_args(function): return len(patchings) -def getfuncargnames(function, startindex=None, cls=None): - """ - Returns the names of a function's unbound arguments. +def getfuncargnames(function, is_method=False, cls=None): + """Returns the names of a function's mandatory arguments. This should return the names of all function arguments that: * Aren't bound to an instance or type as in instance or class methods. @@ -97,77 +96,36 @@ def getfuncargnames(function, startindex=None, cls=None): * Aren't bound with functools.partial. * Aren't replaced with mocks. - @RonnyPfannschmidt: This function should be refactored when we revisit fixtures. The - fixture mechanism should ask the node for the fixture names, and not try to obtain - directly from the function object well after collection has occurred. - """ - # [p for p in inspect.signature(f).parameters.values() if (p.kind is Parameter.POSITIONAL_OR_KEYWORD or p.kind is Parameter.KEYWORD_ONLY) and p.default is Parameter.empty] + The is_method and cls arguments indicate that the function should + be treated as a bound method even though it's not unless, only in + the case of cls, the function is a static method. - new_args = tuple( + @RonnyPfannschmidt: This function should be refactored when we + revisit fixtures. The fixture mechanism should ask the node for + the fixture names, and not try to obtain directly from the + function object well after collection has occurred. + + """ + # The parameters attribute of a Signature object contains an + # ordered mapping of parameter names to Parameter instances. This + # creates a tuple of the names of the parameters that don't have + # defaults. + arg_names = tuple( p.name for p in signature(function).parameters.values() if (p.kind is Parameter.POSITIONAL_OR_KEYWORD or p.kind is Parameter.KEYWORD_ONLY) and p.default is Parameter.empty) - if (startindex or + # If this function should be treated as a bound method even though + # it's passed as an unbound method or function, remove the first + # parameter name. + if (is_method or (cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod))): - new_args = new_args[1:] + arg_names = arg_names[1:] + # Remove any names that will be replaced with mocks. if hasattr(function, "__wrapped__"): - new_args = new_args[num_mock_patch_args(function):] - - # # DIVIDER - # if startindex is None and cls is not None: - # is_staticmethod = isinstance(cls.__dict__.get(function.__name__, None), staticmethod) - # startindex = 0 if is_staticmethod else 1 - # # print('STARTINDEX STATICMETHOD', startindex) - # # XXX merge with main.py's varnames - # # assert not isclass(function) - # realfunction = function - # while hasattr(realfunction, "__wrapped__"): - # realfunction = realfunction.__wrapped__ - # if startindex is None: - # startindex = inspect.ismethod(function) and 1 or 0 - # # print('STARTINDEX METHOD', startindex) - # if realfunction != function: - # startindex += num_mock_patch_args(function) - # function = realfunction - # # print('STARTINDEX MOCK', startindex) - # if isinstance(function, functools.partial): - # argnames = inspect.getargs(_pytest._code.getrawcode(function.func))[0] - # partial = function - # argnames = argnames[len(partial.args):] - # if partial.keywords: - # for kw in partial.keywords: - # argnames.remove(kw) - # else: - # argnames = inspect.getargs(_pytest._code.getrawcode(function))[0] - - # # FINAL SECTION - # defaults = getattr(function, 'func_defaults', - # getattr(function, '__defaults__', None)) or () - # numdefaults = len(defaults) - # if numdefaults: - # # return tuple(argnames[startindex:-numdefaults]) - # args = tuple(argnames[startindex:-numdefaults]) - # # new_args = tuple(new_argnames)[new_startindex:-numdefaults] - # # return tuple(argnames[startindex:]) - # else: - # args = tuple(argnames[startindex:]) - # # new_args = tuple(new_argnames)[new_startindex:] - - # if args != new_args: - # print('FUNCTION', function) - # print('ISMETHOD', inspect.ismethod(function)) - # if cls is not None: - # print('IS_STATICMETHOD', isinstance(cls.__dict__.get(function.__name__, None), staticmethod)) - # print(repr(signature(function))) - # print(argnames) - # # print(new_argnames) - # print(args) - # print(cls) - # print(new_args) - # assert args == new_args - return new_args + arg_names = arg_names[num_mock_patch_args(function):] + return arg_names if _PY3: diff --git a/_pytest/fixtures.py b/_pytest/fixtures.py index af993f3f99e..5ac93b1a921 100644 --- a/_pytest/fixtures.py +++ b/_pytest/fixtures.py @@ -728,8 +728,7 @@ def __init__(self, fixturemanager, baseid, argname, func, scope, params, where=baseid ) self.params = params - startindex = unittest and 1 or None - self.argnames = getfuncargnames(func, startindex=startindex) + self.argnames = getfuncargnames(func, is_method=unittest) self.unittest = unittest self.ids = ids self._finalizer = [] diff --git a/testing/python/fixture.py b/testing/python/fixture.py index 06b08d68eea..fa5da328476 100644 --- a/testing/python/fixture.py +++ b/testing/python/fixture.py @@ -34,9 +34,6 @@ def static(arg1, arg2): pass assert fixtures.getfuncargnames(A().f) == ('arg1',) - if sys.version_info < (3, 0): - assert fixtures.getfuncargnames(A.f) == ('arg1',) - assert fixtures.getfuncargnames(A.static, cls=A) == ('arg1', 'arg2') @@ -2826,7 +2823,7 @@ def test_show_fixtures_indented_in_class(self, testdir): import pytest class TestClass: @pytest.fixture - def fixture1(): + def fixture1(self): """line1 line2 indented line