Merge pull request #10921 from bluetech/tb-simplify-2
Fix hidden traceback entries of chained exceptions getting shown
This commit is contained in:
commit
99c78aa93a
|
@ -0,0 +1 @@
|
||||||
|
Fixed traceback entries hidden with ``__tracebackhide__ = True`` still being shown for chained exceptions (parts after "... the above exception ..." message).
|
|
@ -31,7 +31,6 @@ from typing import Type
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import TypeVar
|
from typing import TypeVar
|
||||||
from typing import Union
|
from typing import Union
|
||||||
from weakref import ref
|
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
|
@ -50,9 +49,9 @@ from _pytest.pathlib import absolutepath
|
||||||
from _pytest.pathlib import bestrelpath
|
from _pytest.pathlib import bestrelpath
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
from typing_extensions import Final
|
||||||
from typing_extensions import Literal
|
from typing_extensions import Literal
|
||||||
from typing_extensions import SupportsIndex
|
from typing_extensions import SupportsIndex
|
||||||
from weakref import ReferenceType
|
|
||||||
|
|
||||||
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
_TracebackStyle = Literal["long", "short", "line", "no", "native", "value", "auto"]
|
||||||
|
|
||||||
|
@ -194,25 +193,25 @@ class Frame:
|
||||||
class TracebackEntry:
|
class TracebackEntry:
|
||||||
"""A single entry in a Traceback."""
|
"""A single entry in a Traceback."""
|
||||||
|
|
||||||
__slots__ = ("_rawentry", "_excinfo", "_repr_style")
|
__slots__ = ("_rawentry", "_repr_style")
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
rawentry: TracebackType,
|
rawentry: TracebackType,
|
||||||
excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
|
repr_style: Optional['Literal["short", "long"]'] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._rawentry = rawentry
|
self._rawentry: "Final" = rawentry
|
||||||
self._excinfo = excinfo
|
self._repr_style: "Final" = repr_style
|
||||||
self._repr_style: Optional['Literal["short", "long"]'] = None
|
|
||||||
|
def with_repr_style(
|
||||||
|
self, repr_style: Optional['Literal["short", "long"]']
|
||||||
|
) -> "TracebackEntry":
|
||||||
|
return TracebackEntry(self._rawentry, repr_style)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lineno(self) -> int:
|
def lineno(self) -> int:
|
||||||
return self._rawentry.tb_lineno - 1
|
return self._rawentry.tb_lineno - 1
|
||||||
|
|
||||||
def set_repr_style(self, mode: "Literal['short', 'long']") -> None:
|
|
||||||
assert mode in ("short", "long")
|
|
||||||
self._repr_style = mode
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def frame(self) -> Frame:
|
def frame(self) -> Frame:
|
||||||
return Frame(self._rawentry.tb_frame)
|
return Frame(self._rawentry.tb_frame)
|
||||||
|
@ -272,7 +271,7 @@ class TracebackEntry:
|
||||||
|
|
||||||
source = property(getsource)
|
source = property(getsource)
|
||||||
|
|
||||||
def ishidden(self) -> bool:
|
def ishidden(self, excinfo: Optional["ExceptionInfo[BaseException]"]) -> bool:
|
||||||
"""Return True if the current frame has a var __tracebackhide__
|
"""Return True if the current frame has a var __tracebackhide__
|
||||||
resolving to True.
|
resolving to True.
|
||||||
|
|
||||||
|
@ -296,7 +295,7 @@ class TracebackEntry:
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if tbh and callable(tbh):
|
if tbh and callable(tbh):
|
||||||
return tbh(None if self._excinfo is None else self._excinfo())
|
return tbh(excinfo)
|
||||||
return tbh
|
return tbh
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
@ -329,16 +328,14 @@ class Traceback(List[TracebackEntry]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
tb: Union[TracebackType, Iterable[TracebackEntry]],
|
||||||
excinfo: Optional["ReferenceType[ExceptionInfo[BaseException]]"] = None,
|
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize from given python traceback object and ExceptionInfo."""
|
"""Initialize from given python traceback object and ExceptionInfo."""
|
||||||
self._excinfo = excinfo
|
|
||||||
if isinstance(tb, TracebackType):
|
if isinstance(tb, TracebackType):
|
||||||
|
|
||||||
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
def f(cur: TracebackType) -> Iterable[TracebackEntry]:
|
||||||
cur_: Optional[TracebackType] = cur
|
cur_: Optional[TracebackType] = cur
|
||||||
while cur_ is not None:
|
while cur_ is not None:
|
||||||
yield TracebackEntry(cur_, excinfo=excinfo)
|
yield TracebackEntry(cur_)
|
||||||
cur_ = cur_.tb_next
|
cur_ = cur_.tb_next
|
||||||
|
|
||||||
super().__init__(f(tb))
|
super().__init__(f(tb))
|
||||||
|
@ -378,7 +375,7 @@ class Traceback(List[TracebackEntry]):
|
||||||
continue
|
continue
|
||||||
if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
|
if firstlineno is not None and x.frame.code.firstlineno != firstlineno:
|
||||||
continue
|
continue
|
||||||
return Traceback(x._rawentry, self._excinfo)
|
return Traceback(x._rawentry)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
|
@ -398,27 +395,27 @@ class Traceback(List[TracebackEntry]):
|
||||||
return super().__getitem__(key)
|
return super().__getitem__(key)
|
||||||
|
|
||||||
def filter(
|
def filter(
|
||||||
self, fn: Callable[[TracebackEntry], bool] = lambda x: not x.ishidden()
|
self,
|
||||||
|
# TODO(py38): change to positional only.
|
||||||
|
_excinfo_or_fn: Union[
|
||||||
|
"ExceptionInfo[BaseException]",
|
||||||
|
Callable[[TracebackEntry], bool],
|
||||||
|
],
|
||||||
) -> "Traceback":
|
) -> "Traceback":
|
||||||
"""Return a Traceback instance with certain items removed
|
"""Return a Traceback instance with certain items removed.
|
||||||
|
|
||||||
fn is a function that gets a single argument, a TracebackEntry
|
If the filter is an `ExceptionInfo`, removes all the ``TracebackEntry``s
|
||||||
instance, and should return True when the item should be added
|
which are hidden (see ishidden() above).
|
||||||
to the Traceback, False when not.
|
|
||||||
|
|
||||||
By default this removes all the TracebackEntries which are hidden
|
Otherwise, the filter is a function that gets a single argument, a
|
||||||
(see ishidden() above).
|
``TracebackEntry`` instance, and should return True when the item should
|
||||||
|
be added to the ``Traceback``, False when not.
|
||||||
"""
|
"""
|
||||||
return Traceback(filter(fn, self), self._excinfo)
|
if isinstance(_excinfo_or_fn, ExceptionInfo):
|
||||||
|
fn = lambda x: not x.ishidden(_excinfo_or_fn) # noqa: E731
|
||||||
def getcrashentry(self) -> Optional[TracebackEntry]:
|
else:
|
||||||
"""Return last non-hidden traceback entry that lead to the exception of
|
fn = _excinfo_or_fn
|
||||||
a traceback, or None if all hidden."""
|
return Traceback(filter(fn, self))
|
||||||
for i in range(-1, -len(self) - 1, -1):
|
|
||||||
entry = self[i]
|
|
||||||
if not entry.ishidden():
|
|
||||||
return entry
|
|
||||||
return None
|
|
||||||
|
|
||||||
def recursionindex(self) -> Optional[int]:
|
def recursionindex(self) -> Optional[int]:
|
||||||
"""Return the index of the frame/TracebackEntry where recursion originates if
|
"""Return the index of the frame/TracebackEntry where recursion originates if
|
||||||
|
@ -583,7 +580,7 @@ class ExceptionInfo(Generic[E]):
|
||||||
def traceback(self) -> Traceback:
|
def traceback(self) -> Traceback:
|
||||||
"""The traceback."""
|
"""The traceback."""
|
||||||
if self._traceback is None:
|
if self._traceback is None:
|
||||||
self._traceback = Traceback(self.tb, excinfo=ref(self))
|
self._traceback = Traceback(self.tb)
|
||||||
return self._traceback
|
return self._traceback
|
||||||
|
|
||||||
@traceback.setter
|
@traceback.setter
|
||||||
|
@ -623,19 +620,24 @@ class ExceptionInfo(Generic[E]):
|
||||||
return isinstance(self.value, exc)
|
return isinstance(self.value, exc)
|
||||||
|
|
||||||
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
def _getreprcrash(self) -> Optional["ReprFileLocation"]:
|
||||||
exconly = self.exconly(tryshort=True)
|
# Find last non-hidden traceback entry that led to the exception of the
|
||||||
entry = self.traceback.getcrashentry()
|
# traceback, or None if all hidden.
|
||||||
if entry is None:
|
for i in range(-1, -len(self.traceback) - 1, -1):
|
||||||
return None
|
entry = self.traceback[i]
|
||||||
|
if not entry.ishidden(self):
|
||||||
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
path, lineno = entry.frame.code.raw.co_filename, entry.lineno
|
||||||
|
exconly = self.exconly(tryshort=True)
|
||||||
return ReprFileLocation(path, lineno + 1, exconly)
|
return ReprFileLocation(path, lineno + 1, exconly)
|
||||||
|
return None
|
||||||
|
|
||||||
def getrepr(
|
def getrepr(
|
||||||
self,
|
self,
|
||||||
showlocals: bool = False,
|
showlocals: bool = False,
|
||||||
style: "_TracebackStyle" = "long",
|
style: "_TracebackStyle" = "long",
|
||||||
abspath: bool = False,
|
abspath: bool = False,
|
||||||
tbfilter: bool = True,
|
tbfilter: Union[
|
||||||
|
bool, Callable[["ExceptionInfo[BaseException]"], Traceback]
|
||||||
|
] = True,
|
||||||
funcargs: bool = False,
|
funcargs: bool = False,
|
||||||
truncate_locals: bool = True,
|
truncate_locals: bool = True,
|
||||||
chain: bool = True,
|
chain: bool = True,
|
||||||
|
@ -652,9 +654,15 @@ class ExceptionInfo(Generic[E]):
|
||||||
: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.
|
||||||
|
|
||||||
:param bool tbfilter:
|
:param tbfilter:
|
||||||
Hide entries that contain a local variable ``__tracebackhide__==True``.
|
A filter for traceback entries.
|
||||||
Ignored if ``style=="native"``.
|
|
||||||
|
* If false, don't hide any entries.
|
||||||
|
* If true, hide internal entries and entries that contain a local
|
||||||
|
variable ``__tracebackhide__ = True``.
|
||||||
|
* If a callable, delegates the filtering to the callable.
|
||||||
|
|
||||||
|
Ignored if ``style`` is ``"native"``.
|
||||||
|
|
||||||
:param bool funcargs:
|
:param bool funcargs:
|
||||||
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
|
Show fixtures ("funcargs" for legacy purposes) per traceback entry.
|
||||||
|
@ -719,7 +727,7 @@ class FormattedExcinfo:
|
||||||
showlocals: bool = False
|
showlocals: bool = False
|
||||||
style: "_TracebackStyle" = "long"
|
style: "_TracebackStyle" = "long"
|
||||||
abspath: bool = True
|
abspath: bool = True
|
||||||
tbfilter: bool = True
|
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]] = True
|
||||||
funcargs: bool = False
|
funcargs: bool = False
|
||||||
truncate_locals: bool = True
|
truncate_locals: bool = True
|
||||||
chain: bool = True
|
chain: bool = True
|
||||||
|
@ -881,8 +889,10 @@ class FormattedExcinfo:
|
||||||
|
|
||||||
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
|
def repr_traceback(self, excinfo: ExceptionInfo[BaseException]) -> "ReprTraceback":
|
||||||
traceback = excinfo.traceback
|
traceback = excinfo.traceback
|
||||||
if self.tbfilter:
|
if callable(self.tbfilter):
|
||||||
traceback = traceback.filter()
|
traceback = self.tbfilter(excinfo)
|
||||||
|
elif self.tbfilter:
|
||||||
|
traceback = traceback.filter(excinfo)
|
||||||
|
|
||||||
if isinstance(excinfo.value, RecursionError):
|
if isinstance(excinfo.value, RecursionError):
|
||||||
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
traceback, extraline = self._truncate_recursive_traceback(traceback)
|
||||||
|
|
|
@ -22,6 +22,7 @@ import _pytest._code
|
||||||
from _pytest._code import getfslineno
|
from _pytest._code import getfslineno
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
|
from _pytest._code.code import Traceback
|
||||||
from _pytest.compat import cached_property
|
from _pytest.compat import cached_property
|
||||||
from _pytest.compat import LEGACY_PATH
|
from _pytest.compat import LEGACY_PATH
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
@ -432,8 +433,8 @@ class Node(metaclass=NodeMeta):
|
||||||
assert current is None or isinstance(current, cls)
|
assert current is None or isinstance(current, cls)
|
||||||
return current
|
return current
|
||||||
|
|
||||||
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
|
def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
|
||||||
pass
|
return excinfo.traceback
|
||||||
|
|
||||||
def _repr_failure_py(
|
def _repr_failure_py(
|
||||||
self,
|
self,
|
||||||
|
@ -449,10 +450,13 @@ class Node(metaclass=NodeMeta):
|
||||||
style = "value"
|
style = "value"
|
||||||
if isinstance(excinfo.value, FixtureLookupError):
|
if isinstance(excinfo.value, FixtureLookupError):
|
||||||
return excinfo.value.formatrepr()
|
return excinfo.value.formatrepr()
|
||||||
|
|
||||||
|
tbfilter: Union[bool, Callable[[ExceptionInfo[BaseException]], Traceback]]
|
||||||
if self.config.getoption("fulltrace", False):
|
if self.config.getoption("fulltrace", False):
|
||||||
style = "long"
|
style = "long"
|
||||||
|
tbfilter = False
|
||||||
else:
|
else:
|
||||||
self._prunetraceback(excinfo)
|
tbfilter = self._traceback_filter
|
||||||
if style == "auto":
|
if style == "auto":
|
||||||
style = "long"
|
style = "long"
|
||||||
# XXX should excinfo.getrepr record all data and toterminal() process it?
|
# XXX should excinfo.getrepr record all data and toterminal() process it?
|
||||||
|
@ -483,7 +487,7 @@ class Node(metaclass=NodeMeta):
|
||||||
abspath=abspath,
|
abspath=abspath,
|
||||||
showlocals=self.config.getoption("showlocals", False),
|
showlocals=self.config.getoption("showlocals", False),
|
||||||
style=style,
|
style=style,
|
||||||
tbfilter=False, # pruned already, or in --fulltrace mode.
|
tbfilter=tbfilter,
|
||||||
truncate_locals=truncate_locals,
|
truncate_locals=truncate_locals,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -554,13 +558,14 @@ class Collector(Node):
|
||||||
|
|
||||||
return self._repr_failure_py(excinfo, style=tbstyle)
|
return self._repr_failure_py(excinfo, style=tbstyle)
|
||||||
|
|
||||||
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
|
def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
|
||||||
if hasattr(self, "path"):
|
if hasattr(self, "path"):
|
||||||
traceback = excinfo.traceback
|
traceback = excinfo.traceback
|
||||||
ntraceback = traceback.cut(path=self.path)
|
ntraceback = traceback.cut(path=self.path)
|
||||||
if ntraceback == traceback:
|
if ntraceback == traceback:
|
||||||
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
|
ntraceback = ntraceback.cut(excludepath=tracebackcutdir)
|
||||||
excinfo.traceback = ntraceback.filter()
|
return excinfo.traceback.filter(excinfo)
|
||||||
|
return excinfo.traceback
|
||||||
|
|
||||||
|
|
||||||
def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
|
def _check_initialpaths_for_relpath(session: "Session", path: Path) -> Optional[str]:
|
||||||
|
|
|
@ -35,6 +35,7 @@ from _pytest._code import filter_traceback
|
||||||
from _pytest._code import getfslineno
|
from _pytest._code import getfslineno
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
from _pytest._code.code import TerminalRepr
|
from _pytest._code.code import TerminalRepr
|
||||||
|
from _pytest._code.code import Traceback
|
||||||
from _pytest._io import TerminalWriter
|
from _pytest._io import TerminalWriter
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
from _pytest.compat import ascii_escaped
|
from _pytest.compat import ascii_escaped
|
||||||
|
@ -1801,7 +1802,7 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
def setup(self) -> None:
|
def setup(self) -> None:
|
||||||
self._request._fillfixtures()
|
self._request._fillfixtures()
|
||||||
|
|
||||||
def _prunetraceback(self, excinfo: ExceptionInfo[BaseException]) -> None:
|
def _traceback_filter(self, excinfo: ExceptionInfo[BaseException]) -> Traceback:
|
||||||
if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
|
if hasattr(self, "_obj") and not self.config.getoption("fulltrace", False):
|
||||||
code = _pytest._code.Code.from_function(get_real_func(self.obj))
|
code = _pytest._code.Code.from_function(get_real_func(self.obj))
|
||||||
path, firstlineno = code.path, code.firstlineno
|
path, firstlineno = code.path, code.firstlineno
|
||||||
|
@ -1813,14 +1814,21 @@ class Function(PyobjMixin, nodes.Item):
|
||||||
ntraceback = ntraceback.filter(filter_traceback)
|
ntraceback = ntraceback.filter(filter_traceback)
|
||||||
if not ntraceback:
|
if not ntraceback:
|
||||||
ntraceback = traceback
|
ntraceback = traceback
|
||||||
|
ntraceback = ntraceback.filter(excinfo)
|
||||||
|
|
||||||
excinfo.traceback = ntraceback.filter()
|
|
||||||
# issue364: mark all but first and last frames to
|
# issue364: mark all but first and last frames to
|
||||||
# only show a single-line message for each frame.
|
# only show a single-line message for each frame.
|
||||||
if self.config.getoption("tbstyle", "auto") == "auto":
|
if self.config.getoption("tbstyle", "auto") == "auto":
|
||||||
if len(excinfo.traceback) > 2:
|
if len(ntraceback) > 2:
|
||||||
for entry in excinfo.traceback[1:-1]:
|
ntraceback = Traceback(
|
||||||
entry.set_repr_style("short")
|
entry
|
||||||
|
if i == 0 or i == len(ntraceback) - 1
|
||||||
|
else entry.with_repr_style("short")
|
||||||
|
for i, entry in enumerate(ntraceback)
|
||||||
|
)
|
||||||
|
|
||||||
|
return ntraceback
|
||||||
|
return excinfo.traceback
|
||||||
|
|
||||||
# TODO: Type ignored -- breaks Liskov Substitution.
|
# TODO: Type ignored -- breaks Liskov Substitution.
|
||||||
def repr_failure( # type: ignore[override]
|
def repr_failure( # type: ignore[override]
|
||||||
|
|
|
@ -334,15 +334,16 @@ class TestCaseFunction(Function):
|
||||||
finally:
|
finally:
|
||||||
delattr(self._testcase, self.name)
|
delattr(self._testcase, self.name)
|
||||||
|
|
||||||
def _prunetraceback(
|
def _traceback_filter(
|
||||||
self, excinfo: _pytest._code.ExceptionInfo[BaseException]
|
self, excinfo: _pytest._code.ExceptionInfo[BaseException]
|
||||||
) -> None:
|
) -> _pytest._code.Traceback:
|
||||||
super()._prunetraceback(excinfo)
|
traceback = super()._traceback_filter(excinfo)
|
||||||
traceback = excinfo.traceback.filter(
|
ntraceback = traceback.filter(
|
||||||
lambda x: not x.frame.f_globals.get("__unittest")
|
lambda x: not x.frame.f_globals.get("__unittest"),
|
||||||
)
|
)
|
||||||
if traceback:
|
if not ntraceback:
|
||||||
excinfo.traceback = traceback
|
ntraceback = traceback
|
||||||
|
return ntraceback
|
||||||
|
|
||||||
|
|
||||||
@hookimpl(tryfirst=True)
|
@hookimpl(tryfirst=True)
|
||||||
|
|
|
@ -11,7 +11,7 @@ from typing import Tuple
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from typing import Union
|
from typing import Union
|
||||||
|
|
||||||
import _pytest
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest._code.code import ExceptionChainRepr
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ExceptionInfo
|
from _pytest._code.code import ExceptionInfo
|
||||||
|
@ -186,7 +186,7 @@ class TestTraceback_f_g_h:
|
||||||
|
|
||||||
def test_traceback_filter(self):
|
def test_traceback_filter(self):
|
||||||
traceback = self.excinfo.traceback
|
traceback = self.excinfo.traceback
|
||||||
ntraceback = traceback.filter()
|
ntraceback = traceback.filter(self.excinfo)
|
||||||
assert len(ntraceback) == len(traceback) - 1
|
assert len(ntraceback) == len(traceback) - 1
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
|
@ -217,7 +217,7 @@ class TestTraceback_f_g_h:
|
||||||
|
|
||||||
excinfo = pytest.raises(ValueError, h)
|
excinfo = pytest.raises(ValueError, h)
|
||||||
traceback = excinfo.traceback
|
traceback = excinfo.traceback
|
||||||
ntraceback = traceback.filter()
|
ntraceback = traceback.filter(excinfo)
|
||||||
print(f"old: {traceback!r}")
|
print(f"old: {traceback!r}")
|
||||||
print(f"new: {ntraceback!r}")
|
print(f"new: {ntraceback!r}")
|
||||||
|
|
||||||
|
@ -290,7 +290,7 @@ class TestTraceback_f_g_h:
|
||||||
excinfo = pytest.raises(ValueError, fail)
|
excinfo = pytest.raises(ValueError, fail)
|
||||||
assert excinfo.traceback.recursionindex() is None
|
assert excinfo.traceback.recursionindex() is None
|
||||||
|
|
||||||
def test_traceback_getcrashentry(self):
|
def test_getreprcrash(self):
|
||||||
def i():
|
def i():
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
@ -306,15 +306,13 @@ class TestTraceback_f_g_h:
|
||||||
g()
|
g()
|
||||||
|
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
tb = excinfo.traceback
|
reprcrash = excinfo._getreprcrash()
|
||||||
entry = tb.getcrashentry()
|
assert reprcrash is not None
|
||||||
assert entry is not None
|
|
||||||
co = _pytest._code.Code.from_function(h)
|
co = _pytest._code.Code.from_function(h)
|
||||||
assert entry.frame.code.path == co.path
|
assert reprcrash.path == str(co.path)
|
||||||
assert entry.lineno == co.firstlineno + 1
|
assert reprcrash.lineno == co.firstlineno + 1 + 1
|
||||||
assert entry.frame.code.name == "h"
|
|
||||||
|
|
||||||
def test_traceback_getcrashentry_empty(self):
|
def test_getreprcrash_empty(self):
|
||||||
def g():
|
def g():
|
||||||
__tracebackhide__ = True
|
__tracebackhide__ = True
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
@ -324,7 +322,7 @@ class TestTraceback_f_g_h:
|
||||||
g()
|
g()
|
||||||
|
|
||||||
excinfo = pytest.raises(ValueError, f)
|
excinfo = pytest.raises(ValueError, f)
|
||||||
assert excinfo.traceback.getcrashentry() is None
|
assert excinfo._getreprcrash() is None
|
||||||
|
|
||||||
|
|
||||||
def test_excinfo_exconly():
|
def test_excinfo_exconly():
|
||||||
|
@ -626,7 +624,7 @@ raise ValueError()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.func1)
|
excinfo = pytest.raises(ValueError, mod.func1)
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
p = FormattedExcinfo()
|
p = FormattedExcinfo()
|
||||||
reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
|
reprtb = p.repr_traceback_entry(excinfo.traceback[-1])
|
||||||
|
|
||||||
|
@ -659,7 +657,7 @@ raise ValueError()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
|
excinfo = pytest.raises(ValueError, mod.func1, "m" * 90, 5, 13, "z" * 120)
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
entry = excinfo.traceback[-1]
|
entry = excinfo.traceback[-1]
|
||||||
p = FormattedExcinfo(funcargs=True)
|
p = FormattedExcinfo(funcargs=True)
|
||||||
reprfuncargs = p.repr_args(entry)
|
reprfuncargs = p.repr_args(entry)
|
||||||
|
@ -686,7 +684,7 @@ raise ValueError()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
|
excinfo = pytest.raises(ValueError, mod.func1, "a", "b", c="d")
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
entry = excinfo.traceback[-1]
|
entry = excinfo.traceback[-1]
|
||||||
p = FormattedExcinfo(funcargs=True)
|
p = FormattedExcinfo(funcargs=True)
|
||||||
reprfuncargs = p.repr_args(entry)
|
reprfuncargs = p.repr_args(entry)
|
||||||
|
@ -960,7 +958,7 @@ raise ValueError()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.f)
|
excinfo = pytest.raises(ValueError, mod.f)
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
repr = excinfo.getrepr()
|
repr = excinfo.getrepr()
|
||||||
repr.toterminal(tw_mock)
|
repr.toterminal(tw_mock)
|
||||||
assert tw_mock.lines[0] == ""
|
assert tw_mock.lines[0] == ""
|
||||||
|
@ -994,7 +992,7 @@ raise ValueError()
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.f)
|
excinfo = pytest.raises(ValueError, mod.f)
|
||||||
tmp_path.joinpath("mod.py").unlink()
|
tmp_path.joinpath("mod.py").unlink()
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
repr = excinfo.getrepr()
|
repr = excinfo.getrepr()
|
||||||
repr.toterminal(tw_mock)
|
repr.toterminal(tw_mock)
|
||||||
assert tw_mock.lines[0] == ""
|
assert tw_mock.lines[0] == ""
|
||||||
|
@ -1026,7 +1024,7 @@ raise ValueError()
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.f)
|
excinfo = pytest.raises(ValueError, mod.f)
|
||||||
tmp_path.joinpath("mod.py").write_text("asdf")
|
tmp_path.joinpath("mod.py").write_text("asdf")
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
repr = excinfo.getrepr()
|
repr = excinfo.getrepr()
|
||||||
repr.toterminal(tw_mock)
|
repr.toterminal(tw_mock)
|
||||||
assert tw_mock.lines[0] == ""
|
assert tw_mock.lines[0] == ""
|
||||||
|
@ -1123,9 +1121,11 @@ raise ValueError()
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
excinfo = pytest.raises(ValueError, mod.f)
|
excinfo = pytest.raises(ValueError, mod.f)
|
||||||
excinfo.traceback = excinfo.traceback.filter()
|
excinfo.traceback = excinfo.traceback.filter(excinfo)
|
||||||
excinfo.traceback[1].set_repr_style("short")
|
excinfo.traceback = _pytest._code.Traceback(
|
||||||
excinfo.traceback[2].set_repr_style("short")
|
entry if i not in (1, 2) else entry.with_repr_style("short")
|
||||||
|
for i, entry in enumerate(excinfo.traceback)
|
||||||
|
)
|
||||||
r = excinfo.getrepr(style="long")
|
r = excinfo.getrepr(style="long")
|
||||||
r.toterminal(tw_mock)
|
r.toterminal(tw_mock)
|
||||||
for line in tw_mock.lines:
|
for line in tw_mock.lines:
|
||||||
|
@ -1391,7 +1391,7 @@ raise ValueError()
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
mod.f()
|
mod.f()
|
||||||
# previously crashed with `AttributeError: list has no attribute get`
|
# previously crashed with `AttributeError: list has no attribute get`
|
||||||
excinfo.traceback.filter()
|
excinfo.traceback.filter(excinfo)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("style", ["short", "long"])
|
@pytest.mark.parametrize("style", ["short", "long"])
|
||||||
|
@ -1603,3 +1603,48 @@ def test_all_entries_hidden(pytester: Pytester, tbstyle: str) -> None:
|
||||||
result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"])
|
result.stdout.fnmatch_lines(["*ZeroDivisionError: division by zero"])
|
||||||
if tbstyle not in ("line", "native"):
|
if tbstyle not in ("line", "native"):
|
||||||
result.stdout.fnmatch_lines(["All traceback entries are hidden.*"])
|
result.stdout.fnmatch_lines(["All traceback entries are hidden.*"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_hidden_entries_of_chained_exceptions_are_not_shown(pytester: Pytester) -> None:
|
||||||
|
"""Hidden entries of chained exceptions are not shown (#1904)."""
|
||||||
|
p = pytester.makepyfile(
|
||||||
|
"""
|
||||||
|
def g1():
|
||||||
|
__tracebackhide__ = True
|
||||||
|
str.does_not_exist
|
||||||
|
|
||||||
|
def f3():
|
||||||
|
__tracebackhide__ = True
|
||||||
|
1 / 0
|
||||||
|
|
||||||
|
def f2():
|
||||||
|
try:
|
||||||
|
f3()
|
||||||
|
except Exception:
|
||||||
|
g1()
|
||||||
|
|
||||||
|
def f1():
|
||||||
|
__tracebackhide__ = True
|
||||||
|
f2()
|
||||||
|
|
||||||
|
def test():
|
||||||
|
f1()
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
result = pytester.runpytest(str(p), "--tb=short")
|
||||||
|
assert result.ret == 1
|
||||||
|
result.stdout.fnmatch_lines(
|
||||||
|
[
|
||||||
|
"*.py:11: in f2",
|
||||||
|
" f3()",
|
||||||
|
"E ZeroDivisionError: division by zero",
|
||||||
|
"",
|
||||||
|
"During handling of the above exception, another exception occurred:",
|
||||||
|
"*.py:20: in test",
|
||||||
|
" f1()",
|
||||||
|
"*.py:13: in f2",
|
||||||
|
" g1()",
|
||||||
|
"E AttributeError:*'does_not_exist'",
|
||||||
|
],
|
||||||
|
consecutive=True,
|
||||||
|
)
|
||||||
|
|
|
@ -1003,9 +1003,9 @@ class TestTracebackCutting:
|
||||||
with pytest.raises(pytest.skip.Exception) as excinfo:
|
with pytest.raises(pytest.skip.Exception) as excinfo:
|
||||||
pytest.skip("xxx")
|
pytest.skip("xxx")
|
||||||
assert excinfo.traceback[-1].frame.code.name == "skip"
|
assert excinfo.traceback[-1].frame.code.name == "skip"
|
||||||
assert excinfo.traceback[-1].ishidden()
|
assert excinfo.traceback[-1].ishidden(excinfo)
|
||||||
assert excinfo.traceback[-2].frame.code.name == "test_skip_simple"
|
assert excinfo.traceback[-2].frame.code.name == "test_skip_simple"
|
||||||
assert not excinfo.traceback[-2].ishidden()
|
assert not excinfo.traceback[-2].ishidden(excinfo)
|
||||||
|
|
||||||
def test_traceback_argsetup(self, pytester: Pytester) -> None:
|
def test_traceback_argsetup(self, pytester: Pytester) -> None:
|
||||||
pytester.makeconftest(
|
pytester.makeconftest(
|
||||||
|
|
Loading…
Reference in New Issue