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 import TerminalWriter
|
||||||
from _pytest._io.saferepr import safeformat
|
from _pytest._io.saferepr import safeformat
|
||||||
from _pytest._io.saferepr import saferepr
|
from _pytest._io.saferepr import saferepr
|
||||||
|
from _pytest.compat import ATTRS_EQ_FIELD
|
||||||
from _pytest.compat import overload
|
from _pytest.compat import overload
|
||||||
from _pytest.compat import TYPE_CHECKING
|
from _pytest.compat import TYPE_CHECKING
|
||||||
|
|
||||||
|
@ -911,7 +912,7 @@ class FormattedExcinfo:
|
||||||
return ExceptionChainRepr(repr_chain)
|
return ExceptionChainRepr(repr_chain)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class TerminalRepr:
|
class TerminalRepr:
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
# FYI this is called from pytest-xdist's serialization of exception
|
# FYI this is called from pytest-xdist's serialization of exception
|
||||||
|
@ -928,7 +929,7 @@ class TerminalRepr:
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ExceptionRepr(TerminalRepr):
|
class ExceptionRepr(TerminalRepr):
|
||||||
def __attrs_post_init__(self):
|
def __attrs_post_init__(self):
|
||||||
self.sections = [] # type: List[Tuple[str, str, str]]
|
self.sections = [] # type: List[Tuple[str, str, str]]
|
||||||
|
@ -942,7 +943,7 @@ class ExceptionRepr(TerminalRepr):
|
||||||
tw.line(content)
|
tw.line(content)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ExceptionChainRepr(ExceptionRepr):
|
class ExceptionChainRepr(ExceptionRepr):
|
||||||
chain = attr.ib(
|
chain = attr.ib(
|
||||||
type=Sequence[
|
type=Sequence[
|
||||||
|
@ -966,7 +967,7 @@ class ExceptionChainRepr(ExceptionRepr):
|
||||||
super().toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprExceptionInfo(ExceptionRepr):
|
class ReprExceptionInfo(ExceptionRepr):
|
||||||
reprtraceback = attr.ib(type="ReprTraceback")
|
reprtraceback = attr.ib(type="ReprTraceback")
|
||||||
reprcrash = attr.ib(type="ReprFileLocation")
|
reprcrash = attr.ib(type="ReprFileLocation")
|
||||||
|
@ -976,7 +977,7 @@ class ReprExceptionInfo(ExceptionRepr):
|
||||||
super().toterminal(tw)
|
super().toterminal(tw)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprTraceback(TerminalRepr):
|
class ReprTraceback(TerminalRepr):
|
||||||
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
|
reprentries = attr.ib(type=Sequence[Union["ReprEntry", "ReprEntryNative"]])
|
||||||
extraline = attr.ib(type=Optional[str])
|
extraline = attr.ib(type=Optional[str])
|
||||||
|
@ -1010,7 +1011,7 @@ class ReprTracebackNative(ReprTraceback):
|
||||||
self.extraline = None
|
self.extraline = None
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprEntryNative(TerminalRepr):
|
class ReprEntryNative(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
style = "native" # type: _TracebackStyle
|
style = "native" # type: _TracebackStyle
|
||||||
|
@ -1019,7 +1020,7 @@ class ReprEntryNative(TerminalRepr):
|
||||||
tw.write("".join(self.lines))
|
tw.write("".join(self.lines))
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprEntry(TerminalRepr):
|
class ReprEntry(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"])
|
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):
|
class ReprFileLocation(TerminalRepr):
|
||||||
path = attr.ib(type=str, converter=str)
|
path = attr.ib(type=str, converter=str)
|
||||||
lineno = attr.ib(type=int)
|
lineno = attr.ib(type=int)
|
||||||
|
@ -1117,7 +1118,7 @@ class ReprFileLocation(TerminalRepr):
|
||||||
tw.line(":{}: {}".format(self.lineno, msg))
|
tw.line(":{}: {}".format(self.lineno, msg))
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprLocals(TerminalRepr):
|
class ReprLocals(TerminalRepr):
|
||||||
lines = attr.ib(type=Sequence[str])
|
lines = attr.ib(type=Sequence[str])
|
||||||
|
|
||||||
|
@ -1126,7 +1127,7 @@ class ReprLocals(TerminalRepr):
|
||||||
tw.line(indent + line)
|
tw.line(indent + line)
|
||||||
|
|
||||||
|
|
||||||
@attr.s
|
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
|
||||||
class ReprFuncArgs(TerminalRepr):
|
class ReprFuncArgs(TerminalRepr):
|
||||||
args = attr.ib(type=Sequence[Tuple[str, object]])
|
args = attr.ib(type=Sequence[Tuple[str, object]])
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import pytest
|
||||||
from _pytest._code import Code
|
from _pytest._code import Code
|
||||||
from _pytest._code import ExceptionInfo
|
from _pytest._code import ExceptionInfo
|
||||||
from _pytest._code import Frame
|
from _pytest._code import Frame
|
||||||
|
from _pytest._code.code import ExceptionChainRepr
|
||||||
from _pytest._code.code import ReprFuncArgs
|
from _pytest._code.code import ReprFuncArgs
|
||||||
|
|
||||||
|
|
||||||
|
@ -180,3 +181,20 @@ class TestReprFuncArgs:
|
||||||
tw_mock.lines[0]
|
tw_mock.lines[0]
|
||||||
== r"unicode_string = São Paulo, utf8_string = b'S\xc3\xa3o Paulo'"
|
== 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