diff --git a/AUTHORS b/AUTHORS index 83ca41159..141a7423c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -51,6 +51,7 @@ Marc Schlaich Mark Abramowitz Markus Unterwaditzer Martijn Faassen +Michael Birtwell Michael Droettboom Nicolas Delaby Pieter Mulder diff --git a/CHANGELOG b/CHANGELOG index 49af784b0..d699bdd84 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,9 @@ - fix #1035: collecting tests if test module level obj has __getattr__(). Thanks Suor for the report and Bruno Oliveira / Tom Viner for the PR. +- fix #331: don't collect tests if their failure cannot be reported correctly + e.g. they are a callable instance of a class. + 2.8.2 ----- diff --git a/_pytest/python.py b/_pytest/python.py index b194031c7..b7a737a94 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -312,11 +312,14 @@ def pytest_pycollect_makeitem(collector, name, obj): elif collector.istestfunction(obj, name): # mock seems to store unbound methods (issue473), normalize it obj = getattr(obj, "__func__", obj) - if not isfunction(obj): + # We need to try and unwrap the function if it's a functools.partial + # or a funtools.wrapped. + # We musn't if it's been wrapped with mock.patch (python 2 only) + if not (isfunction(obj) or isfunction(get_real_func(obj))): collector.warn(code="C2", message= "cannot collect %r because it is not a function." % name, ) - if getattr(obj, "__test__", True): + elif getattr(obj, "__test__", True): if is_generator(obj): res = Generator(name, parent=collector) else: diff --git a/testing/python/collect.py b/testing/python/collect.py index 228d58364..b78374fd5 100644 --- a/testing/python/collect.py +++ b/testing/python/collect.py @@ -1013,3 +1013,27 @@ def test_collect_functools_partial(testdir): """) result = testdir.inline_run() result.assertoutcome(passed=6, failed=2) + + +def test_dont_collect_non_function_callable(testdir): + """Test for issue https://github.com/pytest-dev/pytest/issues/331 + + In this case an INTERNALERROR occurred trying to report the failure of + a test like this one because py test failed to get the source lines. + """ + testdir.makepyfile(""" + class Oh(object): + def __call__(self): + pass + + test_a = Oh() + + def test_real(): + pass + """) + result = testdir.runpytest('-rw') + result.stdout.fnmatch_lines([ + '*collected 1 item*', + 'WC2 *', + '*1 passed, 1 pytest-warnings in *', + ])