Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.4.x] Fix TerminalRepr instances to be hashable (#6988) #7006

Merged
merged 1 commit into from Apr 3, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6925.bugfix.rst
@@ -0,0 +1 @@
Fix TerminalRepr instances to be hashable again.
21 changes: 11 additions & 10 deletions src/_pytest/_code/code.py
Expand Up @@ -32,6 +32,7 @@
from _pytest._io import TerminalWriter
from _pytest._io.saferepr import safeformat
from _pytest._io.saferepr import saferepr
from _pytest.compat import ATTRS_EQ_FIELD
from _pytest.compat import overload
from _pytest.compat import TYPE_CHECKING

Expand Down Expand Up @@ -911,7 +912,7 @@ def repr_excinfo(self, excinfo: ExceptionInfo) -> "ExceptionChainRepr":
return ExceptionChainRepr(repr_chain)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class TerminalRepr:
def __str__(self) -> str:
# FYI this is called from pytest-xdist's serialization of exception
Expand All @@ -928,7 +929,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
raise NotImplementedError()


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionRepr(TerminalRepr):
def __attrs_post_init__(self):
self.sections = [] # type: List[Tuple[str, str, str]]
Expand All @@ -942,7 +943,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.line(content)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionChainRepr(ExceptionRepr):
chain = attr.ib(
type=Sequence[
Expand All @@ -966,7 +967,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
super().toterminal(tw)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprExceptionInfo(ExceptionRepr):
reprtraceback = attr.ib(type="ReprTraceback")
reprcrash = attr.ib(type="ReprFileLocation")
Expand All @@ -976,7 +977,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
super().toterminal(tw)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprTraceback(TerminalRepr):
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
extraline = attr.ib(type=Optional[str])
Expand Down Expand Up @@ -1010,7 +1011,7 @@ def __init__(self, tblines: Sequence[str]) -> None:
self.extraline = None


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntryNative(TerminalRepr):
lines = attr.ib(type=Sequence[str])
style = "native" # type: _TracebackStyle
Expand All @@ -1019,7 +1020,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.write("".join(self.lines))


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprEntry(TerminalRepr):
lines = attr.ib(type=Sequence[str])
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
Expand Down Expand Up @@ -1093,7 +1094,7 @@ def __str__(self) -> str:
)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprFileLocation(TerminalRepr):
path = attr.ib(type=str, converter=str)
lineno = attr.ib(type=int)
Expand All @@ -1110,7 +1111,7 @@ def toterminal(self, tw: TerminalWriter) -> None:
tw.line(":{}: {}".format(self.lineno, msg))


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprLocals(TerminalRepr):
lines = attr.ib(type=Sequence[str])

Expand All @@ -1119,7 +1120,7 @@ def toterminal(self, tw: TerminalWriter, indent="") -> None:
tw.line(indent + line)


@attr.s
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ReprFuncArgs(TerminalRepr):
args = attr.ib(type=Sequence[Tuple[str, object]])

Expand Down
18 changes: 18 additions & 0 deletions testing/code/test_code.py
Expand Up @@ -6,6 +6,7 @@
from _pytest._code import Code
from _pytest._code import ExceptionInfo
from _pytest._code import Frame
from _pytest._code.code import ExceptionChainRepr
from _pytest._code.code import ReprFuncArgs


Expand Down Expand Up @@ -180,3 +181,20 @@ def test_not_raise_exception_with_mixed_encoding(self, tw_mock) -> None:
tw_mock.lines[0]
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
)


def test_ExceptionChainRepr():
"""Test ExceptionChainRepr, especially with regard to being hashable."""
try:
raise ValueError()
except ValueError:
excinfo1 = ExceptionInfo.from_current()
excinfo2 = ExceptionInfo.from_current()

repr1 = excinfo1.getrepr()
repr2 = excinfo2.getrepr()
assert repr1 != repr2

assert isinstance(repr1, ExceptionChainRepr)
assert hash(repr1) != hash(repr2)
assert repr1 is not excinfo1.getrepr()