diff --git a/AUTHORS b/AUTHORS index 1b3cbaf57..0bf529f02 100644 --- a/AUTHORS +++ b/AUTHORS @@ -69,6 +69,7 @@ Ralf Schmitt Raphael Pierzina Ronny Pfannschmidt Ross Lawley +Ryan Wooden Samuele Pedroni Tom Viner Trevor Bekolay diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e89a40954..1d83e6b7b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,7 +30,9 @@ * -* +* catch IndexError exceptions when getting exception source location. This fixes + pytest internal error for dynamically generated code (fixtures and tests) + where source lines are fake by intention * diff --git a/_pytest/python.py b/_pytest/python.py index d5612a584..ec346f587 100644 --- a/_pytest/python.py +++ b/_pytest/python.py @@ -1774,7 +1774,7 @@ class FixtureLookupError(LookupError): fspath, lineno = getfslineno(function) try: lines, _ = inspect.getsourcelines(get_real_func(function)) - except IOError: + except (IOError, IndexError): error_msg = "file %s, line %s: source code not available" addline(error_msg % (fspath, lineno+1)) else: diff --git a/testing/test_runner.py b/testing/test_runner.py index 6f4a0cee3..c3c415e0f 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -568,6 +568,32 @@ def test_makereport_getsource(testdir): result.stdout.fnmatch_lines(['*else: assert False*']) +def test_makereport_getsource_dynamic_code(testdir, monkeypatch): + """Test that exception in dynamically generated code doesn't break getting the source line.""" + import inspect + original_findsource = inspect.findsource + def findsource(obj, *args, **kwargs): + # Can be triggered by dynamically created functions + if obj.__name__ == 'foo': + raise IndexError() + return original_findsource(obj, *args, **kwargs) + monkeypatch.setattr(inspect, 'findsource', findsource) + + testdir.makepyfile(""" + import pytest + + @pytest.fixture + def foo(missing): + pass + + def test_fix(foo): + assert False + """) + result = testdir.runpytest('-vv') + assert 'INTERNALERROR' not in result.stdout.str() + result.stdout.fnmatch_lines(["*test_fix*", "*fixture*'missing'*not found*"]) + + def test_store_except_info_on_eror(): """ Test that upon test failure, the exception info is stored on sys.last_traceback and friends.