From 9f672c85c514e08949c36fd8292dc8e248b9461f Mon Sep 17 00:00:00 2001
From: Bruno Oliveira <nicoddemus@gmail.com>
Date: Thu, 3 Sep 2020 07:14:32 -0300
Subject: [PATCH] Fix handle of exceptions in ReprEntry with tb=line

Fix #7707
---
 changelog/7707.bugfix.rst    |  1 +
 src/_pytest/_code/code.py    | 24 +++++++++------------
 testing/code/test_excinfo.py | 42 +++++++++++++++++++++---------------
 3 files changed, 36 insertions(+), 31 deletions(-)
 create mode 100644 changelog/7707.bugfix.rst

diff --git a/changelog/7707.bugfix.rst b/changelog/7707.bugfix.rst
new file mode 100644
index 000000000..fbe979d9d
--- /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 96fa9199b..12d39306a 100644
--- a/src/_pytest/_code/code.py
+++ b/src/_pytest/_code/code.py
@@ -1049,25 +1049,21 @@ class ReprEntry(TerminalRepr):
         # 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 = []  # type: List[str]
+        source_lines = []  # type: List[str]
+        failure_lines = []  # type: 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 75446c570..4dfd6f5cc 100644
--- a/testing/code/test_excinfo.py
+++ b/testing/code/test_excinfo.py
@@ -4,6 +4,8 @@ import os
 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 @@ raise ValueError()
     @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)