diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index 3095dd37e..e7a01530a 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -789,9 +789,9 @@ class FormattedExcinfo: else: message = excinfo and excinfo.typename or "" path = self._makepath(entry.path) - filelocrepr = ReprFileLocation(path, entry.lineno + 1, message) + reprfileloc = ReprFileLocation(path, entry.lineno + 1, message) localsrepr = self.repr_locals(entry.locals) - return ReprEntry(lines, reprargs, localsrepr, filelocrepr, style) + return ReprEntry(lines, reprargs, localsrepr, reprfileloc, style) if excinfo: lines.extend(self.get_exconly(excinfo, indent=4)) return ReprEntry(lines, None, None, None, style) @@ -911,6 +911,7 @@ class FormattedExcinfo: return ExceptionChainRepr(repr_chain) +@attr.s class TerminalRepr: def __str__(self) -> str: # FYI this is called from pytest-xdist's serialization of exception @@ -927,8 +928,9 @@ class TerminalRepr: raise NotImplementedError() +@attr.s class ExceptionRepr(TerminalRepr): - def __init__(self) -> None: + def __attrs_post_init__(self): self.sections = [] # type: List[Tuple[str, str, str]] def addsection(self, name: str, content: str, sep: str = "-") -> None: @@ -940,19 +942,20 @@ class ExceptionRepr(TerminalRepr): tw.line(content) +@attr.s class ExceptionChainRepr(ExceptionRepr): - def __init__( - self, - chain: Sequence[ + chain = attr.ib( + type=Sequence[ Tuple["ReprTraceback", Optional["ReprFileLocation"], Optional[str]] - ], - ) -> None: - super().__init__() - self.chain = chain + ] + ) + + def __attrs_post_init__(self): + super().__attrs_post_init__() # reprcrash and reprtraceback of the outermost (the newest) exception # in the chain - self.reprtraceback = chain[-1][0] - self.reprcrash = chain[-1][1] + self.reprtraceback = self.chain[-1][0] + self.reprcrash = self.chain[-1][1] def toterminal(self, tw: TerminalWriter) -> None: for element in self.chain: @@ -963,13 +966,10 @@ class ExceptionChainRepr(ExceptionRepr): super().toterminal(tw) +@attr.s class ReprExceptionInfo(ExceptionRepr): - def __init__( - self, reprtraceback: "ReprTraceback", reprcrash: "ReprFileLocation" - ) -> None: - super().__init__() - self.reprtraceback = reprtraceback - self.reprcrash = reprcrash + reprtraceback = attr.ib(type="ReprTraceback") + reprcrash = attr.ib(type="ReprFileLocation") def toterminal(self, tw: TerminalWriter) -> None: self.reprtraceback.toterminal(tw) @@ -1010,30 +1010,22 @@ class ReprTracebackNative(ReprTraceback): self.extraline = None +@attr.s class ReprEntryNative(TerminalRepr): + lines = attr.ib(type=Sequence[str]) style = "native" # type: _TracebackStyle - def __init__(self, tblines: Sequence[str]) -> None: - self.lines = tblines - def toterminal(self, tw: TerminalWriter) -> None: tw.write("".join(self.lines)) +@attr.s class ReprEntry(TerminalRepr): - def __init__( - self, - lines: Sequence[str], - reprfuncargs: Optional["ReprFuncArgs"], - reprlocals: Optional["ReprLocals"], - filelocrepr: Optional["ReprFileLocation"], - style: "_TracebackStyle", - ) -> None: - self.lines = lines - self.reprfuncargs = reprfuncargs - self.reprlocals = reprlocals - self.reprfileloc = filelocrepr - self.style = style + lines = attr.ib(type=Sequence[str]) + reprfuncargs = attr.ib(type=Optional["ReprFuncArgs"]) + reprlocals = attr.ib(type=Optional["ReprLocals"]) + reprfileloc = attr.ib(type=Optional["ReprFileLocation"]) + style = attr.ib(type="_TracebackStyle") def _write_entry_lines(self, tw: TerminalWriter) -> None: """Writes the source code portions of a list of traceback entries with syntax highlighting. @@ -1118,18 +1110,18 @@ class ReprFileLocation(TerminalRepr): tw.line(":{}: {}".format(self.lineno, msg)) +@attr.s class ReprLocals(TerminalRepr): - def __init__(self, lines: Sequence[str]) -> None: - self.lines = lines + lines = attr.ib(type=Sequence[str]) def toterminal(self, tw: TerminalWriter, indent="") -> None: for line in self.lines: tw.line(indent + line) +@attr.s class ReprFuncArgs(TerminalRepr): - def __init__(self, args: Sequence[Tuple[str, object]]) -> None: - self.args = args + args = attr.ib(type=Sequence[Tuple[str, object]]) def toterminal(self, tw: TerminalWriter) -> None: if self.args: diff --git a/src/_pytest/reports.py b/src/_pytest/reports.py index 36cb51593..4fa465ea7 100644 --- a/src/_pytest/reports.py +++ b/src/_pytest/reports.py @@ -369,10 +369,10 @@ def _report_to_json(report): """ def serialize_repr_entry(entry): - entry_data = {"type": type(entry).__name__, "data": entry.__dict__.copy()} + entry_data = {"type": type(entry).__name__, "data": attr.asdict(entry)} for key, value in entry_data["data"].items(): if hasattr(value, "__dict__"): - entry_data["data"][key] = value.__dict__.copy() + entry_data["data"][key] = attr.asdict(value) return entry_data def serialize_repr_traceback(reprtraceback: ReprTraceback): @@ -451,7 +451,7 @@ def _report_kwargs_from_json(reportdict): lines=data["lines"], reprfuncargs=reprfuncargs, reprlocals=reprlocals, - filelocrepr=reprfileloc, + reprfileloc=reprfileloc, style=data["style"], ) # type: Union[ReprEntry, ReprEntryNative] elif entry_type == "ReprEntryNative": diff --git a/src/_pytest/runner.py b/src/_pytest/runner.py index e53c72e2c..8455b2aee 100644 --- a/src/_pytest/runner.py +++ b/src/_pytest/runner.py @@ -14,8 +14,8 @@ import attr from .reports import CollectErrorRepr from .reports import CollectReport from .reports import TestReport +from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionInfo -from _pytest._code.code import ExceptionRepr from _pytest.compat import TYPE_CHECKING from _pytest.nodes import Collector from _pytest.nodes import Node @@ -276,7 +276,7 @@ def pytest_make_collect_report(collector: Collector) -> CollectReport: if call.excinfo.errisinstance(tuple(skip_exceptions)): outcome = "skipped" r_ = collector._repr_failure_py(call.excinfo, "line") - assert isinstance(r_, ExceptionRepr), r_ + assert isinstance(r_, ExceptionChainRepr), repr(r_) r = r_.reprcrash assert r longrepr = (str(r.path), r.lineno, r.message)