From 0256cb3aae7221909244d187ed912716fcc5aa5e Mon Sep 17 00:00:00 2001 From: Ran Benita Date: Mon, 8 Jun 2020 16:08:46 +0300 Subject: [PATCH] hookspec: type annotate pytest_internalerror Also switch to using ExceptionRepr instead of `Union[ReprExceptionInfo, ExceptionChainRepr]` which is somewhat annoying and less future proof. --- src/_pytest/_code/code.py | 5 +++++ src/_pytest/capture.py | 2 +- src/_pytest/config/__init__.py | 9 +++++++-- src/_pytest/debugging.py | 14 ++++++++++---- src/_pytest/hookspec.py | 11 +++++++++-- src/_pytest/junitxml.py | 3 ++- src/_pytest/resultlog.py | 9 +++++---- src/_pytest/terminal.py | 11 ++++------- 8 files changed, 43 insertions(+), 21 deletions(-) diff --git a/src/_pytest/_code/code.py b/src/_pytest/_code/code.py index a40b23470..121ef3a9b 100644 --- a/src/_pytest/_code/code.py +++ b/src/_pytest/_code/code.py @@ -928,8 +928,13 @@ class TerminalRepr: raise NotImplementedError() +# This class is abstract -- only subclasses are instantiated. @attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore class ExceptionRepr(TerminalRepr): + # Provided by in subclasses. + reprcrash = None # type: Optional[ReprFileLocation] + reprtraceback = None # type: ReprTraceback + def __attrs_post_init__(self): self.sections = [] # type: List[Tuple[str, str, str]] diff --git a/src/_pytest/capture.py b/src/_pytest/capture.py index f2beb8d8d..daded6395 100644 --- a/src/_pytest/capture.py +++ b/src/_pytest/capture.py @@ -753,7 +753,7 @@ class CaptureManager: self.stop_global_capturing() @pytest.hookimpl(tryfirst=True) - def pytest_internalerror(self, excinfo): + def pytest_internalerror(self) -> None: self.stop_global_capturing() diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index daccdc6a1..2ae9ac849 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -48,6 +48,7 @@ from _pytest.warning_types import PytestConfigWarning if TYPE_CHECKING: from typing import Type + from _pytest._code.code import _TracebackStyle from .argparsing import Argument @@ -893,9 +894,13 @@ class Config: return self - def notify_exception(self, excinfo, option=None): + def notify_exception( + self, + excinfo: ExceptionInfo[BaseException], + option: Optional[argparse.Namespace] = None, + ) -> None: if option and getattr(option, "fulltrace", False): - style = "long" + style = "long" # type: _TracebackStyle else: style = "native" excrepr = excinfo.getrepr( diff --git a/src/_pytest/debugging.py b/src/_pytest/debugging.py index 3001db4ec..0567927c0 100644 --- a/src/_pytest/debugging.py +++ b/src/_pytest/debugging.py @@ -2,11 +2,13 @@ import argparse import functools import sys +import types from typing import Generator from typing import Tuple from typing import Union from _pytest import outcomes +from _pytest._code import ExceptionInfo from _pytest.compat import TYPE_CHECKING from _pytest.config import Config from _pytest.config import ConftestImportFailure @@ -280,9 +282,10 @@ class PdbInvoke: out, err = capman.read_global_capture() sys.stdout.write(out) sys.stdout.write(err) + assert call.excinfo is not None _enter_pdb(node, call.excinfo, report) - def pytest_internalerror(self, excrepr, excinfo) -> None: + def pytest_internalerror(self, excinfo: ExceptionInfo[BaseException]) -> None: tb = _postmortem_traceback(excinfo) post_mortem(tb) @@ -320,7 +323,9 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem): wrap_pytest_function_for_tracing(pyfuncitem) -def _enter_pdb(node: Node, excinfo, rep: BaseReport) -> BaseReport: +def _enter_pdb( + node: Node, excinfo: ExceptionInfo[BaseException], rep: BaseReport +) -> BaseReport: # XXX we re-use the TerminalReporter's terminalwriter # because this seems to avoid some encoding related troubles # for not completely clear reasons. @@ -349,7 +354,7 @@ def _enter_pdb(node: Node, excinfo, rep: BaseReport) -> BaseReport: return rep -def _postmortem_traceback(excinfo): +def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType: from doctest import UnexpectedException if isinstance(excinfo.value, UnexpectedException): @@ -361,10 +366,11 @@ def _postmortem_traceback(excinfo): # Use the underlying exception instead: return excinfo.value.excinfo[2] else: + assert excinfo._excinfo is not None return excinfo._excinfo[2] -def post_mortem(t) -> None: +def post_mortem(t: types.TracebackType) -> None: p = pytestPDB._init_pdb("post_mortem") p.reset() p.interaction(None, t) diff --git a/src/_pytest/hookspec.py b/src/_pytest/hookspec.py index a6decb03a..1c1726d53 100644 --- a/src/_pytest/hookspec.py +++ b/src/_pytest/hookspec.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: import warnings from typing_extensions import Literal + from _pytest._code.code import ExceptionRepr from _pytest.code import ExceptionInfo from _pytest.config import Config from _pytest.config import ExitCode @@ -759,8 +760,14 @@ def pytest_doctest_prepare_content(content): # ------------------------------------------------------------------------- -def pytest_internalerror(excrepr, excinfo): - """ called for internal errors. """ +def pytest_internalerror( + excrepr: "ExceptionRepr", excinfo: "ExceptionInfo[BaseException]", +) -> Optional[bool]: + """Called for internal errors. + + Return True to suppress the fallback handling of printing an + INTERNALERROR message directly to sys.stderr. + """ def pytest_keyboard_interrupt( diff --git a/src/_pytest/junitxml.py b/src/_pytest/junitxml.py index e62bc5235..86e8fcf38 100644 --- a/src/_pytest/junitxml.py +++ b/src/_pytest/junitxml.py @@ -26,6 +26,7 @@ import pytest from _pytest import deprecated from _pytest import nodes from _pytest import timing +from _pytest._code.code import ExceptionRepr from _pytest.compat import TYPE_CHECKING from _pytest.config import Config from _pytest.config import filename_arg @@ -642,7 +643,7 @@ class LogXML: else: reporter.append_collect_skipped(report) - def pytest_internalerror(self, excrepr) -> None: + def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: reporter = self.node_reporter("internal") reporter.attrs.update(classname="pytest", name="internal") reporter._add_simple(Junit.error, "internal error", excrepr) diff --git a/src/_pytest/resultlog.py b/src/_pytest/resultlog.py index c870ef08e..cd6824abf 100644 --- a/src/_pytest/resultlog.py +++ b/src/_pytest/resultlog.py @@ -5,6 +5,7 @@ import os import py +from _pytest._code.code import ExceptionRepr from _pytest.config import Config from _pytest.config.argparsing import Parser from _pytest.reports import CollectReport @@ -99,9 +100,9 @@ class ResultLog: longrepr = "%s:%d: %s" % report.longrepr # type: ignore self.log_outcome(report, code, longrepr) - def pytest_internalerror(self, excrepr): - reprcrash = getattr(excrepr, "reprcrash", None) - path = getattr(reprcrash, "path", None) - if path is None: + def pytest_internalerror(self, excrepr: ExceptionRepr) -> None: + if excrepr.reprcrash is not None: + path = excrepr.reprcrash.path + else: path = "cwd:%s" % py.path.local() self.write_log_entry(path, "!", str(excrepr)) diff --git a/src/_pytest/terminal.py b/src/_pytest/terminal.py index da8c5fc9e..e89776109 100644 --- a/src/_pytest/terminal.py +++ b/src/_pytest/terminal.py @@ -31,8 +31,7 @@ import pytest from _pytest import nodes from _pytest import timing from _pytest._code import ExceptionInfo -from _pytest._code.code import ExceptionChainRepr -from _pytest._code.code import ReprExceptionInfo +from _pytest._code.code import ExceptionRepr from _pytest._io import TerminalWriter from _pytest._io.wcwidth import wcswidth from _pytest.compat import order_preserving_dict @@ -318,9 +317,7 @@ class TerminalReporter: self._show_progress_info = self._determine_show_progress_info() self._collect_report_last_write = None # type: Optional[float] self._already_displayed_warnings = None # type: Optional[int] - self._keyboardinterrupt_memo = ( - None - ) # type: Optional[Union[ReprExceptionInfo, ExceptionChainRepr]] + self._keyboardinterrupt_memo = None # type: Optional[ExceptionRepr] @property def writer(self) -> TerminalWriter: @@ -454,10 +451,10 @@ class TerminalReporter: if set_main_color: self._set_main_color() - def pytest_internalerror(self, excrepr): + def pytest_internalerror(self, excrepr: ExceptionRepr) -> bool: for line in str(excrepr).split("\n"): self.write_line("INTERNALERROR> " + line) - return 1 + return True def pytest_warning_recorded( self, warning_message: warnings.WarningMessage, nodeid: str,