Issue 1316 - longrepr is a string when pytrace=False (#7100)
This commit is contained in:
		
							parent
							
								
									e3190604ef
								
							
						
					
					
						commit
						94c7b8b47c
					
				| 
						 | 
					@ -0,0 +1 @@
 | 
				
			||||||
 | 
					``TestReport.longrepr`` is now always an instance of ``ReprExceptionInfo``. Previously it was a ``str`` when a test failed with ``pytest.fail(..., pytrace=False)``.
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ if TYPE_CHECKING:
 | 
				
			||||||
    from typing_extensions import Literal
 | 
					    from typing_extensions import Literal
 | 
				
			||||||
    from weakref import ReferenceType
 | 
					    from weakref import ReferenceType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    _TracebackStyle = Literal["long", "short", "line", "no", "native"]
 | 
					    _TracebackStyle = Literal["long", "short", "line", "no", "native", "value"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Code:
 | 
					class Code:
 | 
				
			||||||
| 
						 | 
					@ -583,7 +583,7 @@ class ExceptionInfo(Generic[_E]):
 | 
				
			||||||
            Show locals per traceback entry.
 | 
					            Show locals per traceback entry.
 | 
				
			||||||
            Ignored if ``style=="native"``.
 | 
					            Ignored if ``style=="native"``.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param str style: long|short|no|native traceback style
 | 
					        :param str style: long|short|no|native|value traceback style
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        :param bool abspath:
 | 
					        :param bool abspath:
 | 
				
			||||||
            If paths should be changed to absolute or left unchanged.
 | 
					            If paths should be changed to absolute or left unchanged.
 | 
				
			||||||
| 
						 | 
					@ -758,16 +758,15 @@ class FormattedExcinfo:
 | 
				
			||||||
    def repr_traceback_entry(
 | 
					    def repr_traceback_entry(
 | 
				
			||||||
        self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
 | 
					        self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
 | 
				
			||||||
    ) -> "ReprEntry":
 | 
					    ) -> "ReprEntry":
 | 
				
			||||||
 | 
					        lines = []  # type: List[str]
 | 
				
			||||||
 | 
					        style = entry._repr_style if entry._repr_style is not None else self.style
 | 
				
			||||||
 | 
					        if style in ("short", "long"):
 | 
				
			||||||
            source = self._getentrysource(entry)
 | 
					            source = self._getentrysource(entry)
 | 
				
			||||||
            if source is None:
 | 
					            if source is None:
 | 
				
			||||||
                source = Source("???")
 | 
					                source = Source("???")
 | 
				
			||||||
                line_index = 0
 | 
					                line_index = 0
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                line_index = entry.lineno - entry.getfirstlinesource()
 | 
					                line_index = entry.lineno - entry.getfirstlinesource()
 | 
				
			||||||
 | 
					 | 
				
			||||||
        lines = []  # type: List[str]
 | 
					 | 
				
			||||||
        style = entry._repr_style if entry._repr_style is not None else self.style
 | 
					 | 
				
			||||||
        if style in ("short", "long"):
 | 
					 | 
				
			||||||
            short = style == "short"
 | 
					            short = style == "short"
 | 
				
			||||||
            reprargs = self.repr_args(entry) if not short else None
 | 
					            reprargs = self.repr_args(entry) if not short else None
 | 
				
			||||||
            s = self.get_source(source, line_index, excinfo, short=short)
 | 
					            s = self.get_source(source, line_index, excinfo, short=short)
 | 
				
			||||||
| 
						 | 
					@ -780,6 +779,11 @@ class FormattedExcinfo:
 | 
				
			||||||
            reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
 | 
					            reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
 | 
				
			||||||
            localsrepr = self.repr_locals(entry.locals)
 | 
					            localsrepr = self.repr_locals(entry.locals)
 | 
				
			||||||
            return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
 | 
					            return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
 | 
				
			||||||
 | 
					        elif style == "value":
 | 
				
			||||||
 | 
					            if excinfo:
 | 
				
			||||||
 | 
					                lines.extend(str(excinfo.value).split("\n"))
 | 
				
			||||||
 | 
					            return ReprEntry(lines, None, None, None, style)
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
            if excinfo:
 | 
					            if excinfo:
 | 
				
			||||||
                lines.extend(self.get_exconly(excinfo, indent=4))
 | 
					                lines.extend(self.get_exconly(excinfo, indent=4))
 | 
				
			||||||
            return ReprEntry(lines, None, None, None, style)
 | 
					            return ReprEntry(lines, None, None, None, style)
 | 
				
			||||||
| 
						 | 
					@ -806,6 +810,11 @@ class FormattedExcinfo:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        last = traceback[-1]
 | 
					        last = traceback[-1]
 | 
				
			||||||
        entries = []
 | 
					        entries = []
 | 
				
			||||||
 | 
					        if self.style == "value":
 | 
				
			||||||
 | 
					            reprentry = self.repr_traceback_entry(last, excinfo)
 | 
				
			||||||
 | 
					            entries.append(reprentry)
 | 
				
			||||||
 | 
					            return ReprTraceback(entries, None, style=self.style)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for index, entry in enumerate(traceback):
 | 
					        for index, entry in enumerate(traceback):
 | 
				
			||||||
            einfo = (last == entry) and excinfo or None
 | 
					            einfo = (last == entry) and excinfo or None
 | 
				
			||||||
            reprentry = self.repr_traceback_entry(entry, einfo)
 | 
					            reprentry = self.repr_traceback_entry(entry, einfo)
 | 
				
			||||||
| 
						 | 
					@ -865,7 +874,9 @@ class FormattedExcinfo:
 | 
				
			||||||
            seen.add(id(e))
 | 
					            seen.add(id(e))
 | 
				
			||||||
            if excinfo_:
 | 
					            if excinfo_:
 | 
				
			||||||
                reprtraceback = self.repr_traceback(excinfo_)
 | 
					                reprtraceback = self.repr_traceback(excinfo_)
 | 
				
			||||||
                reprcrash = excinfo_._getreprcrash()  # type: Optional[ReprFileLocation]
 | 
					                reprcrash = (
 | 
				
			||||||
 | 
					                    excinfo_._getreprcrash() if self.style != "value" else None
 | 
				
			||||||
 | 
					                )  # type: Optional[ReprFileLocation]
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                # fallback to native repr if the exception doesn't have a traceback:
 | 
					                # fallback to native repr if the exception doesn't have a traceback:
 | 
				
			||||||
                # ExceptionInfo objects require a full traceback to work
 | 
					                # ExceptionInfo objects require a full traceback to work
 | 
				
			||||||
| 
						 | 
					@ -1048,6 +1059,9 @@ class ReprEntry(TerminalRepr):
 | 
				
			||||||
                    "Unexpected failure lines between source lines:\n"
 | 
					                    "Unexpected failure lines between source lines:\n"
 | 
				
			||||||
                    + "\n".join(self.lines)
 | 
					                    + "\n".join(self.lines)
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
 | 
					                if self.style == "value":
 | 
				
			||||||
 | 
					                    source_lines.append(line)
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
                    indents.append(line[:indent_size])
 | 
					                    indents.append(line[:indent_size])
 | 
				
			||||||
                    source_lines.append(line[indent_size:])
 | 
					                    source_lines.append(line[indent_size:])
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -202,10 +202,8 @@ class _NodeReporter:
 | 
				
			||||||
        if hasattr(report, "wasxfail"):
 | 
					        if hasattr(report, "wasxfail"):
 | 
				
			||||||
            self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly")
 | 
					            self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly")
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            if hasattr(report.longrepr, "reprcrash"):
 | 
					            if getattr(report.longrepr, "reprcrash", None) is not None:
 | 
				
			||||||
                message = report.longrepr.reprcrash.message
 | 
					                message = report.longrepr.reprcrash.message
 | 
				
			||||||
            elif isinstance(report.longrepr, str):
 | 
					 | 
				
			||||||
                message = report.longrepr
 | 
					 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                message = str(report.longrepr)
 | 
					                message = str(report.longrepr)
 | 
				
			||||||
            message = bin_xml_escape(message)
 | 
					            message = bin_xml_escape(message)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -337,7 +337,7 @@ class Node(metaclass=NodeMeta):
 | 
				
			||||||
            excinfo = ExceptionInfo(excinfo.value.excinfo)
 | 
					            excinfo = ExceptionInfo(excinfo.value.excinfo)
 | 
				
			||||||
        if isinstance(excinfo.value, fail.Exception):
 | 
					        if isinstance(excinfo.value, fail.Exception):
 | 
				
			||||||
            if not excinfo.value.pytrace:
 | 
					            if not excinfo.value.pytrace:
 | 
				
			||||||
                return str(excinfo.value)
 | 
					                style = "value"
 | 
				
			||||||
        if isinstance(excinfo.value, FixtureLookupError):
 | 
					        if isinstance(excinfo.value, FixtureLookupError):
 | 
				
			||||||
            return excinfo.value.formatrepr()
 | 
					            return excinfo.value.formatrepr()
 | 
				
			||||||
        if self.config.getoption("fulltrace", False):
 | 
					        if self.config.getoption("fulltrace", False):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1002,6 +1002,17 @@ class TestReportContents:
 | 
				
			||||||
        assert rep.capstdout == ""
 | 
					        assert rep.capstdout == ""
 | 
				
			||||||
        assert rep.capstderr == ""
 | 
					        assert rep.capstderr == ""
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def test_longrepr_type(self, testdir) -> None:
 | 
				
			||||||
 | 
					        reports = testdir.runitem(
 | 
				
			||||||
 | 
					            """
 | 
				
			||||||
 | 
					            import pytest
 | 
				
			||||||
 | 
					            def test_func():
 | 
				
			||||||
 | 
					                pytest.fail(pytrace=False)
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        rep = reports[1]
 | 
				
			||||||
 | 
					        assert isinstance(rep.longrepr, _pytest._code.code.ExceptionRepr)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_outcome_exception_bad_msg() -> None:
 | 
					def test_outcome_exception_bad_msg() -> None:
 | 
				
			||||||
    """Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)"""
 | 
					    """Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)"""
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -194,7 +194,7 @@ class TestXFail:
 | 
				
			||||||
        assert len(reports) == 3
 | 
					        assert len(reports) == 3
 | 
				
			||||||
        callreport = reports[1]
 | 
					        callreport = reports[1]
 | 
				
			||||||
        assert callreport.failed
 | 
					        assert callreport.failed
 | 
				
			||||||
        assert callreport.longrepr == "[XPASS(strict)] nope"
 | 
					        assert str(callreport.longrepr) == "[XPASS(strict)] nope"
 | 
				
			||||||
        assert not hasattr(callreport, "wasxfail")
 | 
					        assert not hasattr(callreport, "wasxfail")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_xfail_run_anyway(self, testdir):
 | 
					    def test_xfail_run_anyway(self, testdir):
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue