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":
|
||||||
source = self._getentrysource(entry)
|
|
||||||
if source is None:
|
|
||||||
source = Source("???")
|
|
||||||
line_index = 0
|
|
||||||
else:
|
|
||||||
line_index = entry.lineno - entry.getfirstlinesource()
|
|
||||||
|
|
||||||
lines = [] # type: List[str]
|
lines = [] # type: List[str]
|
||||||
style = entry._repr_style if entry._repr_style is not None else self.style
|
style = entry._repr_style if entry._repr_style is not None else self.style
|
||||||
if style in ("short", "long"):
|
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"
|
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,9 +779,14 @@ 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)
|
||||||
if excinfo:
|
elif style == "value":
|
||||||
lines.extend(self.get_exconly(excinfo, indent=4))
|
if excinfo:
|
||||||
return ReprEntry(lines, None, None, None, style)
|
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):
|
def _makepath(self, path):
|
||||||
if not self.abspath:
|
if not self.abspath:
|
||||||
|
@ -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,8 +1059,11 @@ 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)
|
||||||
)
|
)
|
||||||
indents.append(line[:indent_size])
|
if self.style == "value":
|
||||||
source_lines.append(line[indent_size:])
|
source_lines.append(line)
|
||||||
|
else:
|
||||||
|
indents.append(line[:indent_size])
|
||||||
|
source_lines.append(line[indent_size:])
|
||||||
else:
|
else:
|
||||||
seeing_failures = True
|
seeing_failures = True
|
||||||
failure_lines.append(line)
|
failure_lines.append(line)
|
||||||
|
|
|
@ -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