check that tests that are partial staticmethods are supported (#5701)

check that tests that are partial staticmethods are supported
This commit is contained in:
Bruno Oliveira 2019-08-15 09:12:01 -03:00 committed by GitHub
commit 28c6c5bb71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 65 additions and 55 deletions

View File

@ -0,0 +1 @@
Fix collection of ``staticmethod`` objects defined with ``functools.partial``.

View File

@ -80,7 +80,7 @@ def num_mock_patch_args(function):
) )
def getfuncargnames(function, is_method=False, cls=None): def getfuncargnames(function, *, name: str = "", is_method=False, cls=None):
"""Returns the names of a function's mandatory arguments. """Returns the names of a function's mandatory arguments.
This should return the names of all function arguments that: This should return the names of all function arguments that:
@ -93,11 +93,12 @@ def getfuncargnames(function, is_method=False, cls=None):
be treated as a bound method even though it's not unless, only in be treated as a bound method even though it's not unless, only in
the case of cls, the function is a static method. the case of cls, the function is a static method.
The name parameter should be the original name in which the function was collected.
@RonnyPfannschmidt: This function should be refactored when we @RonnyPfannschmidt: This function should be refactored when we
revisit fixtures. The fixture mechanism should ask the node for revisit fixtures. The fixture mechanism should ask the node for
the fixture names, and not try to obtain directly from the the fixture names, and not try to obtain directly from the
function object well after collection has occurred. function object well after collection has occurred.
""" """
# The parameters attribute of a Signature object contains an # The parameters attribute of a Signature object contains an
# ordered mapping of parameter names to Parameter instances. This # ordered mapping of parameter names to Parameter instances. This
@ -120,11 +121,14 @@ def getfuncargnames(function, is_method=False, cls=None):
) )
and p.default is Parameter.empty and p.default is Parameter.empty
) )
if not name:
name = function.__name__
# If this function should be treated as a bound method even though # If this function should be treated as a bound method even though
# it's passed as an unbound method or function, remove the first # it's passed as an unbound method or function, remove the first
# parameter name. # parameter name.
if is_method or ( if is_method or (
cls and not isinstance(cls.__dict__.get(function.__name__, None), staticmethod) cls and not isinstance(cls.__dict__.get(name, None), staticmethod)
): ):
arg_names = arg_names[1:] arg_names = arg_names[1:]
# Remove any names that will be replaced with mocks. # Remove any names that will be replaced with mocks.
@ -247,7 +251,7 @@ def get_real_method(obj, holder):
try: try:
is_method = hasattr(obj, "__func__") is_method = hasattr(obj, "__func__")
obj = get_real_func(obj) obj = get_real_func(obj)
except Exception: except Exception: # pragma: no cover
return obj return obj
if is_method and hasattr(obj, "__get__") and callable(obj.__get__): if is_method and hasattr(obj, "__get__") and callable(obj.__get__):
obj = obj.__get__(holder) obj = obj.__get__(holder)

View File

@ -828,7 +828,7 @@ class FixtureDef:
where=baseid, where=baseid,
) )
self.params = params self.params = params
self.argnames = getfuncargnames(func, is_method=unittest) self.argnames = getfuncargnames(func, name=argname, is_method=unittest)
self.unittest = unittest self.unittest = unittest
self.ids = ids self.ids = ids
self._finalizers = [] self._finalizers = []
@ -1143,7 +1143,7 @@ class FixtureManager:
def getfixtureinfo(self, node, func, cls, funcargs=True): def getfixtureinfo(self, node, func, cls, funcargs=True):
if funcargs and not getattr(node, "nofuncargs", False): if funcargs and not getattr(node, "nofuncargs", False):
argnames = getfuncargnames(func, cls=cls) argnames = getfuncargnames(func, name=node.name, cls=cls)
else: else:
argnames = () argnames = ()

View File

@ -1143,52 +1143,6 @@ def test_unorderable_types(testdir):
assert result.ret == ExitCode.NO_TESTS_COLLECTED assert result.ret == ExitCode.NO_TESTS_COLLECTED
def test_collect_functools_partial(testdir):
"""
Test that collection of functools.partial object works, and arguments
to the wrapped functions are dealt with correctly (see #811).
"""
testdir.makepyfile(
"""
import functools
import pytest
@pytest.fixture
def fix1():
return 'fix1'
@pytest.fixture
def fix2():
return 'fix2'
def check1(i, fix1):
assert i == 2
assert fix1 == 'fix1'
def check2(fix1, i):
assert i == 2
assert fix1 == 'fix1'
def check3(fix1, i, fix2):
assert i == 2
assert fix1 == 'fix1'
assert fix2 == 'fix2'
test_ok_1 = functools.partial(check1, i=2)
test_ok_2 = functools.partial(check1, i=2, fix1='fix1')
test_ok_3 = functools.partial(check1, 2)
test_ok_4 = functools.partial(check2, i=2)
test_ok_5 = functools.partial(check3, i=2)
test_ok_6 = functools.partial(check3, i=2, fix1='fix1')
test_fail_1 = functools.partial(check2, 2)
test_fail_2 = functools.partial(check3, 2)
"""
)
result = testdir.inline_run()
result.assertoutcome(passed=6, failed=2)
@pytest.mark.filterwarnings("default") @pytest.mark.filterwarnings("default")
def test_dont_collect_non_function_callable(testdir): def test_dont_collect_non_function_callable(testdir):
"""Test for issue https://github.com/pytest-dev/pytest/issues/331 """Test for issue https://github.com/pytest-dev/pytest/issues/331

View File

@ -10,7 +10,9 @@ from _pytest.pytester import get_public_names
from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG from _pytest.warnings import SHOW_PYTEST_WARNINGS_ARG
def test_getfuncargnames(): def test_getfuncargnames_functions():
"""Test getfuncargnames for normal functions"""
def f(): def f():
pass pass
@ -31,18 +33,56 @@ def test_getfuncargnames():
assert fixtures.getfuncargnames(j) == ("arg1", "arg2") assert fixtures.getfuncargnames(j) == ("arg1", "arg2")
def test_getfuncargnames_methods():
"""Test getfuncargnames for normal methods"""
class A: class A:
def f(self, arg1, arg2="hello"): def f(self, arg1, arg2="hello"):
pass pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
def test_getfuncargnames_staticmethod():
"""Test getfuncargnames for staticmethods"""
class A:
@staticmethod @staticmethod
def static(arg1, arg2): def static(arg1, arg2, x=1):
pass pass
assert fixtures.getfuncargnames(A().f) == ("arg1",)
assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2") assert fixtures.getfuncargnames(A.static, cls=A) == ("arg1", "arg2")
def test_getfuncargnames_partial():
"""Check getfuncargnames for methods defined with functools.partial (#5701)"""
import functools
def check(arg1, arg2, i):
pass
class T:
test_ok = functools.partial(check, i=2)
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")
def test_getfuncargnames_staticmethod_partial():
"""Check getfuncargnames for staticmethods defined with functools.partial (#5701)"""
import functools
def check(arg1, arg2, i):
pass
class T:
test_ok = staticmethod(functools.partial(check, i=2))
values = fixtures.getfuncargnames(T().test_ok, name="test_ok")
assert values == ("arg1", "arg2")
@pytest.mark.pytester_example_path("fixtures/fill_fixtures") @pytest.mark.pytester_example_path("fixtures/fill_fixtures")
class TestFillFixtures: class TestFillFixtures:
def test_fillfuncargs_exposed(self): def test_fillfuncargs_exposed(self):

View File

@ -1,4 +1,5 @@
import sys import sys
from functools import partial
from functools import wraps from functools import wraps
import pytest import pytest
@ -72,6 +73,16 @@ def test_get_real_func():
assert get_real_func(wrapped_func2) is wrapped_func assert get_real_func(wrapped_func2) is wrapped_func
def test_get_real_func_partial():
"""Test get_real_func handles partial instances correctly"""
def foo(x):
return x
assert get_real_func(foo) is foo
assert get_real_func(partial(foo)) is foo
def test_is_generator_asyncio(testdir): def test_is_generator_asyncio(testdir):
testdir.makepyfile( testdir.makepyfile(
""" """