diff --git a/changelog/7707.bugfix.rst b/changelog/7707.bugfix.rst new file mode 100644 index 00000000000..fbe979d9d6a --- /dev/null +++ b/changelog/7707.bugfix.rst @@ -0,0 +1 @@ +Fix internal error when handling some exceptions that contain multiple lines or the style uses multiple lines (``--tb=line`` for example). diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 96fa9199b7e..20c515a9029 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -1049,25 +1049,21 @@ def _write_entry_lines(self, tw: TerminalWriter) -> None: # such as "> assert 0" fail_marker = "{} ".format(FormattedExcinfo.fail_marker) indent_size = len(fail_marker) - indents = [] - source_lines = [] - failure_lines = [] - seeing_failures = False - for line in self.lines: - is_source_line = not line.startswith(fail_marker) - if is_source_line: - assert not seeing_failures, ( - "Unexpected failure lines between source lines:\n" - + "\n".join(self.lines) - ) + indents: List[str] = [] + source_lines: List[str] = [] + failure_lines: List[str] = [] + for index, line in enumerate(self.lines): + is_failure_line = line.startswith(fail_marker) + if is_failure_line: + # from this point on all lines are considered part of the failure + failure_lines.extend(self.lines[index:]) + break + else: if self.style == "value": source_lines.append(line) else: indents.append(line[:indent_size]) source_lines.append(line[indent_size:]) - else: - seeing_failures = True - failure_lines.append(line) tw._write_source(source_lines, indents) diff --git a/testing/code/test_excinfo.py b/testing/code/test_excinfo.py index 75446c570b2..4dfd6f5cc95 100644 --- a/testing/code/test_excinfo.py +++ b/testing/code/test_excinfo.py @@ -4,6 +4,8 @@ import queue import sys import textwrap +from typing import Any +from typing import Dict from typing import Tuple from typing import Union @@ -1045,28 +1047,34 @@ def f(): @pytest.mark.parametrize( "reproptions", [ - { - "style": style, - "showlocals": showlocals, - "funcargs": funcargs, - "tbfilter": tbfilter, - } - for style in ("long", "short", "no") + pytest.param( + { + "style": style, + "showlocals": showlocals, + "funcargs": funcargs, + "tbfilter": tbfilter, + }, + id="style={},showlocals={},funcargs={},tbfilter={}".format( + style, showlocals, funcargs, tbfilter + ), + ) + for style in ["long", "short", "line", "no", "native", "value", "auto"] for showlocals in (True, False) for tbfilter in (True, False) for funcargs in (True, False) ], ) - def test_format_excinfo(self, importasmod, reproptions): - mod = importasmod( - """ - def g(x): - raise ValueError(x) - def f(): - g(3) - """ - ) - excinfo = pytest.raises(ValueError, mod.f) + def test_format_excinfo(self, reproptions: Dict[str, Any]) -> None: + def bar(): + assert False, "some error" + + def foo(): + bar() + + # using inline functions as opposed to importasmod so we get source code lines + # in the tracebacks (otherwise getinspect doesn't find the source code). + with pytest.raises(AssertionError) as excinfo: + foo() file = io.StringIO() tw = TerminalWriter(file=file) repr = excinfo.getrepr(**reproptions)