From cf9a09e988fdfb4478e653072e0584fb8a8fa248 Mon Sep 17 00:00:00 2001 From: Anatoly Bubenkov Date: Tue, 15 Sep 2015 01:14:58 +0200 Subject: [PATCH] catch IndexError exceptions when getting exception source location --- CHANGELOG.rst | 4 ++++ _pytest/python.py | 2 +- testing/test_runner.py | 43 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e89a40954..13c9774ac 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -526,6 +526,10 @@ directories created by this fixture (defaults to $TEMP/pytest-$USER). Thanks Bruno Oliveira for the PR. +- 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 + 2.7.2 (compared to 2.7.1) ============================= 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..7edffee4e 100644 --- a/testing/test_runner.py +++ b/testing/test_runner.py @@ -568,6 +568,49 @@ def test_makereport_getsource(testdir): result.stdout.fnmatch_lines(['*else: assert False*']) +def test_makereport_getsource_dynamic_code(testdir): + """Test that exception in dynamically generated code doesn't break getting the source line.""" + sub = testdir.mkdir("sub") + sub.join("__init__.py").write("") + sub.join("fixtures.py").write(py.std.textwrap.dedent( + """ +import sys +import pytest +import inspect + +def get_caller_module(depth=2): + frame = sys._getframe(depth) + module = inspect.getmodule(frame) + if module is None: + return get_caller_module(depth=depth) + return module + +def inject(): + context = {} + exec(''' +import pytest + +@pytest.fixture +def foo(request): + request.getfuncargvalue('sone') +''', context) + module = get_caller_module() + foo = context['foo'] + module.foo = foo + +inject() + """)) + sub.join("conftest.py").write("""from fixtures import foo""") + sub.join("test_dynamic.py").write(py.std.textwrap.dedent( + """ +def test_foo(foo): + pass + """)) + result = testdir.runpytest('-vv') + assert 'INTERNALERROR' not in result.stdout.str() + result.stdout.fnmatch_lines(['*else: assert False*']) + + def test_store_except_info_on_eror(): """ Test that upon test failure, the exception info is stored on sys.last_traceback and friends.