diff --git a/changelog/4984.bugfix.rst b/changelog/4984.bugfix.rst new file mode 100644 index 000000000..869a9f244 --- /dev/null +++ b/changelog/4984.bugfix.rst @@ -0,0 +1,3 @@ +Fixed an internal error crash with ``IndexError: list index out of range`` when +collecting a module which starts with a decorated function, the decorator +raises, and assertion rewriting is enabled. diff --git a/src/_pytest/assertion/rewrite.py b/src/_pytest/assertion/rewrite.py index 48587b17e..5ff578245 100644 --- a/src/_pytest/assertion/rewrite.py +++ b/src/_pytest/assertion/rewrite.py @@ -687,13 +687,18 @@ class AssertionRewriter(ast.NodeVisitor): return expect_docstring = False elif ( - not isinstance(item, ast.ImportFrom) - or item.level > 0 - or item.module != "__future__" + isinstance(item, ast.ImportFrom) + and item.level == 0 + and item.module == "__future__" ): - lineno = item.lineno + pass + else: break pos += 1 + # Special case: for a decorated function, set the lineno to that of the + # first decorator, not the `def`. Issue #4984. + if isinstance(item, ast.FunctionDef) and item.decorator_list: + lineno = item.decorator_list[0].lineno else: lineno = item.lineno imports = [ diff --git a/testing/test_collection.py b/testing/test_collection.py index 12030e56e..3e1b816b7 100644 --- a/testing/test_collection.py +++ b/testing/test_collection.py @@ -1393,3 +1393,17 @@ class TestImportModeImportlib: "* 1 failed in *", ] ) + + +def test_does_not_crash_on_error_from_decorated_function(testdir: Testdir) -> None: + """Regression test for an issue around bad exception formatting due to + assertion rewriting mangling lineno's (#4984).""" + testdir.makepyfile( + """ + @pytest.fixture + def a(): return 4 + """ + ) + result = testdir.runpytest() + # Not INTERNAL_ERROR + assert result.ret == ExitCode.INTERRUPTED