code: make TracebackEntry immutable

TracebackEntry being mutable caught me by surprise and makes reasoning
about the exception formatting code harder. Make it a proper value.
This commit is contained in:
Ran Benita 2023-04-12 22:58:57 +03:00
parent 0a20452f78
commit 6f7f89f3c4
3 changed files with 20 additions and 10 deletions

View File

@ -49,6 +49,7 @@ 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
@ -197,18 +198,20 @@ class TracebackEntry:
def __init__( def __init__(
self, self,
rawentry: TracebackType, rawentry: TracebackType,
repr_style: Optional['Literal["short", "long"]'] = None,
) -> None: ) -> None:
self._rawentry = rawentry self._rawentry: "Final" = rawentry
self._repr_style: Optional['Literal["short", "long"]'] = None self._repr_style: "Final" = repr_style
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)

View File

@ -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
@ -1819,8 +1820,12 @@ class Function(PyobjMixin, nodes.Item):
# 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(excinfo.traceback) > 2:
for entry in excinfo.traceback[1:-1]: excinfo.traceback = Traceback(
entry.set_repr_style("short") entry
if i == 0 or i == len(excinfo.traceback) - 1
else entry.with_repr_style("short")
for i, entry in enumerate(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]

View File

@ -1122,8 +1122,10 @@ raise ValueError()
) )
excinfo = pytest.raises(ValueError, mod.f) excinfo = pytest.raises(ValueError, mod.f)
excinfo.traceback = excinfo.traceback.filter(excinfo) 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: