Fix TerminalRepr instances to be hashable (#6988)
pytest-xdist assumes `ExceptionChainRepr` is hashable. Fixes https://github.com/pytest-dev/pytest/issues/6925. Fixes https://github.com/pytest-dev/pytest-xdist/issues/515.
This commit is contained in:
parent
2d9dac95ec
commit
20f6331afd
|
@ -0,0 +1 @@
|
|||
Fix TerminalRepr instances to be hashable again.
|
|
@ -32,6 +32,7 @@ import _pytest
|
|||
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
|
||||
|
||||
|
@ -911,7 +912,7 @@ class FormattedExcinfo:
|
|||
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
|
||||
|
@ -928,7 +929,7 @@ class TerminalRepr:
|
|||
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]]
|
||||
|
@ -942,7 +943,7 @@ class ExceptionRepr(TerminalRepr):
|
|||
tw.line(content)
|
||||
|
||||
|
||||
@attr.s
|
||||
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||
class ExceptionChainRepr(ExceptionRepr):
|
||||
chain = attr.ib(
|
||||
type=Sequence[
|
||||
|
@ -966,7 +967,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
|||
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")
|
||||
|
@ -976,7 +977,7 @@ class ReprExceptionInfo(ExceptionRepr):
|
|||
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])
|
||||
|
@ -1010,7 +1011,7 @@ class ReprTracebackNative(ReprTraceback):
|
|||
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
|
||||
|
@ -1019,7 +1020,7 @@ class ReprEntryNative(TerminalRepr):
|
|||
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"])
|
||||
|
@ -1100,7 +1101,7 @@ class ReprEntry(TerminalRepr):
|
|||
)
|
||||
|
||||
|
||||
@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)
|
||||
|
@ -1117,7 +1118,7 @@ class ReprFileLocation(TerminalRepr):
|
|||
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])
|
||||
|
||||
|
@ -1126,7 +1127,7 @@ class ReprLocals(TerminalRepr):
|
|||
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]])
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
|||
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
|
||||
|
||||
|
||||
|
@ -180,3 +181,20 @@ class TestReprFuncArgs:
|
|||
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()
|
||||
|
|
Loading…
Reference in New Issue