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 weakref import ReferenceType
|
||||
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native"]
|
||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value"]
|
||||
|
||||
|
||||
class Code:
|
||||
|
@ -583,7 +583,7 @@ class ExceptionInfo(Generic[_E]):
|
|||
Show locals per traceback entry.
|
||||
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:
|
||||
If paths should be changed to absolute or left unchanged.
|
||||
|
@ -758,16 +758,15 @@ class FormattedExcinfo:
|
|||
def repr_traceback_entry(
|
||||
self, entry: TracebackEntry, excinfo: Optional[ExceptionInfo] = None
|
||||
) -> "ReprEntry":
|
||||
source = self._getentrysource(entry)
|
||||
if source is None:
|
||||
source = Source("???")
|
||||
line_index = 0
|
||||
else:
|
||||
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"):
|
||||
source = self._getentrysource(entry)
|
||||
if source is None:
|
||||
source = Source("???")
|
||||
line_index = 0
|
||||
else:
|
||||
line_index = entry.lineno - entry.getfirstlinesource()
|
||||
short = style == "short"
|
||||
reprargs = self.repr_args(entry) if not short else None
|
||||
s = self.get_source(source, line_index, excinfo, short=short)
|
||||
|
@ -780,9 +779,14 @@ class FormattedExcinfo:
|
|||
reprfileloc = ReprFileLocation(path, entry.lineno + 1, message)
|
||||
localsrepr = self.repr_locals(entry.locals)
|
||||
return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style)
|
||||
if excinfo:
|
||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||
return ReprEntry(lines, None, None, None, style)
|
||||
elif style == "value":
|
||||
if excinfo:
|
||||
lines.extend(str(excinfo.value).split("\n"))
|
||||
return ReprEntry(lines, None, None, None, style)
|
||||
else:
|
||||
if excinfo:
|
||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
||||
return ReprEntry(lines, None, None, None, style)
|
||||
|
||||
def _makepath(self, path):
|
||||
if not self.abspath:
|
||||
|
@ -806,6 +810,11 @@ class FormattedExcinfo:
|
|||
|
||||
last = traceback[-1]
|
||||
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):
|
||||
einfo = (last == entry) and excinfo or None
|
||||
reprentry = self.repr_traceback_entry(entry, einfo)
|
||||
|
@ -865,7 +874,9 @@ class FormattedExcinfo:
|
|||
seen.add(id(e))
|
||||
if 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:
|
||||
# fallback to native repr if the exception doesn't have a traceback:
|
||||
# ExceptionInfo objects require a full traceback to work
|
||||
|
@ -1048,8 +1059,11 @@ class ReprEntry(TerminalRepr):
|
|||
"Unexpected failure lines between source lines:\n"
|
||||
+ "\n".join(self.lines)
|
||||
)
|
||||
indents.append(line[:indent_size])
|
||||
source_lines.append(line[indent_size:])
|
||||
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)
|
||||
|
|
|
@ -202,10 +202,8 @@ class _NodeReporter:
|
|||
if hasattr(report, "wasxfail"):
|
||||
self._add_simple(Junit.skipped, "xfail-marked test passes unexpectedly")
|
||||
else:
|
||||
if hasattr(report.longrepr, "reprcrash"):
|
||||
if getattr(report.longrepr, "reprcrash", None) is not None:
|
||||
message = report.longrepr.reprcrash.message
|
||||
elif isinstance(report.longrepr, str):
|
||||
message = report.longrepr
|
||||
else:
|
||||
message = str(report.longrepr)
|
||||
message = bin_xml_escape(message)
|
||||
|
|
|
@ -337,7 +337,7 @@ class Node(metaclass=NodeMeta):
|
|||
excinfo = ExceptionInfo(excinfo.value.excinfo)
|
||||
if isinstance(excinfo.value, fail.Exception):
|
||||
if not excinfo.value.pytrace:
|
||||
return str(excinfo.value)
|
||||
style = "value"
|
||||
if isinstance(excinfo.value, FixtureLookupError):
|
||||
return excinfo.value.formatrepr()
|
||||
if self.config.getoption("fulltrace", False):
|
||||
|
|
|
@ -1002,6 +1002,17 @@ class TestReportContents:
|
|||
assert rep.capstdout == ""
|
||||
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:
|
||||
"""Check that OutcomeExceptions validate their input to prevent confusing errors (#5578)"""
|
||||
|
|
|
@ -194,7 +194,7 @@ class TestXFail:
|
|||
assert len(reports) == 3
|
||||
callreport = reports[1]
|
||||
assert callreport.failed
|
||||
assert callreport.longrepr == "[XPASS(strict)] nope"
|
||||
assert str(callreport.longrepr) == "[XPASS(strict)] nope"
|
||||
assert not hasattr(callreport, "wasxfail")
|
||||
|
||||
def test_xfail_run_anyway(self, testdir):
|
||||
|
|
Loading…
Reference in New Issue