From 78229ed62070154d0a72cecbb61079066daa4876 Mon Sep 17 00:00:00 2001 From: solaluset <60041069+solaluset@users.noreply.github.com> Date: Thu, 6 Nov 2025 16:54:08 +0200 Subject: [PATCH 1/4] Fix internal error when source is modified --- src/_pytest/_code/source.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index 99c242dd98e..d0a5df4457f 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -216,6 +216,7 @@ def getstatementrange_ast( pass # The end might still point to a comment or empty line, correct it. + end = min(end, len(source.lines)) while end: line = source.lines[end - 1].lstrip() if line.startswith("#") or not line: From 6ac721c70ddbc30f6dc39f0182aa6cdca935f48a Mon Sep 17 00:00:00 2001 From: solaluset <60041069+solaluset@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:45:44 +0200 Subject: [PATCH 2/4] Fix IndexError when retrieving start lineno --- src/_pytest/_code/source.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/_pytest/_code/source.py b/src/_pytest/_code/source.py index d0a5df4457f..cbadf667907 100644 --- a/src/_pytest/_code/source.py +++ b/src/_pytest/_code/source.py @@ -168,6 +168,8 @@ def get_statement_startend2(lineno: int, node: ast.AST) -> tuple[int, int | None values.append(val[0].lineno - 1 - 1) values.sort() insert_index = bisect_right(values, lineno) + if insert_index == 0: + return 0, None start = values[insert_index - 1] if insert_index >= len(values): end = None From c4848a445eb075ebee95f7dd8071fe65f41714bd Mon Sep 17 00:00:00 2001 From: solaluset <60041069+solaluset@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:47:31 +0200 Subject: [PATCH 3/4] Add test_patched_compile pytest should not crash when source is dynamically modified --- testing/code/test_source.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index e413af3766e..3704687ad75 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -7,6 +7,7 @@ import sys import textwrap from typing import Any +from unittest.mock import patch from _pytest._code import Code from _pytest._code import Frame @@ -647,3 +648,26 @@ def __init__(self, *args): # fmt: on values = [i for i in x.source.lines if i.strip()] assert len(values) == 4 + + +def test_patched_compile() -> None: + # ensure Source doesn't break + # when compile() modifies code dynamically + from builtins import compile + + def patched_compile1(_, *args, **kwargs): + return compile("", *args, **kwargs) + + with patch("builtins.compile", new=patched_compile1): + Source(patched_compile1).getstatement(1) + + # fmt: off + def patched_compile2(_, *args, **kwargs): +# first line of this function must not start with spaces +# LINES must be equal to number of lines of this function + LINES = 4 + return compile("\ndef a():\n" + "\n" * LINES + " pass", *args, **kwargs) + # fmt: on + + with patch("builtins.compile", new=patched_compile2): + Source(patched_compile2).getstatement(1) From 60d4d82dd29062dd0a72814bb468774d9187fb43 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sat, 8 Nov 2025 19:03:12 +0000 Subject: [PATCH 4/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- testing/code/test_source.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testing/code/test_source.py b/testing/code/test_source.py index 3704687ad75..1afcd906b61 100644 --- a/testing/code/test_source.py +++ b/testing/code/test_source.py @@ -663,8 +663,8 @@ def patched_compile1(_, *args, **kwargs): # fmt: off def patched_compile2(_, *args, **kwargs): -# first line of this function must not start with spaces -# LINES must be equal to number of lines of this function + # first line of this function must not start with spaces + # LINES must be equal to number of lines of this function LINES = 4 return compile("\ndef a():\n" + "\n" * LINES + " pass", *args, **kwargs) # fmt: on