hookspec: type annotate pytest_internalerror

Also switch to using ExceptionRepr instead of
`Union[ReprExceptionInfo, ExceptionChainRepr]`
which is somewhat annoying and less future proof.
This commit is contained in:
Ran Benita 2020-06-08 16:08:46 +03:00
parent 7081ed19b8
commit 0256cb3aae
8 changed files with 43 additions and 21 deletions

View File

@ -928,8 +928,13 @@ class TerminalRepr:
raise NotImplementedError() raise NotImplementedError()
# This class is abstract -- only subclasses are instantiated.
@attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore @attr.s(**{ATTRS_EQ_FIELD: False}) # type: ignore
class ExceptionRepr(TerminalRepr): class ExceptionRepr(TerminalRepr):
# Provided by in subclasses.
reprcrash = None # type: Optional[ReprFileLocation]
reprtraceback = None # type: ReprTraceback
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]]

View File

@ -753,7 +753,7 @@ class CaptureManager:
self.stop_global_capturing() self.stop_global_capturing()
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo): def pytest_internalerror(self) -> None:
self.stop_global_capturing() self.stop_global_capturing()

View File

@ -48,6 +48,7 @@ from _pytest.warning_types import PytestConfigWarning
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Type from typing import Type
from _pytest._code.code import _TracebackStyle
from .argparsing import Argument from .argparsing import Argument
@ -893,9 +894,13 @@ class Config:
return self 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): if option and getattr(option, "fulltrace", False):
style = "long" style = "long" # type: _TracebackStyle
else: else:
style = "native" style = "native"
excrepr = excinfo.getrepr( excrepr = excinfo.getrepr(

View File

@ -2,11 +2,13 @@
import argparse import argparse
import functools import functools
import sys import sys
import types
from typing import Generator from typing import Generator
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
from _pytest import outcomes from _pytest import outcomes
from _pytest._code import ExceptionInfo
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import ConftestImportFailure from _pytest.config import ConftestImportFailure
@ -280,9 +282,10 @@ class PdbInvoke:
out, err = capman.read_global_capture() out, err = capman.read_global_capture()
sys.stdout.write(out) sys.stdout.write(out)
sys.stdout.write(err) sys.stdout.write(err)
assert call.excinfo is not None
_enter_pdb(node, call.excinfo, report) _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) tb = _postmortem_traceback(excinfo)
post_mortem(tb) post_mortem(tb)
@ -320,7 +323,9 @@ def maybe_wrap_pytest_function_for_tracing(pyfuncitem):
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 # XXX we re-use the TerminalReporter's terminalwriter
# because this seems to avoid some encoding related troubles # because this seems to avoid some encoding related troubles
# for not completely clear reasons. # for not completely clear reasons.
@ -349,7 +354,7 @@ def _enter_pdb(node: Node, excinfo, rep: BaseReport) -> BaseReport:
return rep return rep
def _postmortem_traceback(excinfo): def _postmortem_traceback(excinfo: ExceptionInfo[BaseException]) -> types.TracebackType:
from doctest import UnexpectedException from doctest import UnexpectedException
if isinstance(excinfo.value, UnexpectedException): if isinstance(excinfo.value, UnexpectedException):
@ -361,10 +366,11 @@ def _postmortem_traceback(excinfo):
# Use the underlying exception instead: # Use the underlying exception instead:
return excinfo.value.excinfo[2] return excinfo.value.excinfo[2]
else: else:
assert excinfo._excinfo is not None
return excinfo._excinfo[2] return excinfo._excinfo[2]
def post_mortem(t) -> None: def post_mortem(t: types.TracebackType) -> None:
p = pytestPDB._init_pdb("post_mortem") p = pytestPDB._init_pdb("post_mortem")
p.reset() p.reset()
p.interaction(None, t) p.interaction(None, t)

View File

@ -19,6 +19,7 @@ if TYPE_CHECKING:
import warnings import warnings
from typing_extensions import Literal from typing_extensions import Literal
from _pytest._code.code import ExceptionRepr
from _pytest.code import ExceptionInfo from _pytest.code import ExceptionInfo
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
@ -759,8 +760,14 @@ def pytest_doctest_prepare_content(content):
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
def pytest_internalerror(excrepr, excinfo): def pytest_internalerror(
""" called for internal errors. """ 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( def pytest_keyboard_interrupt(

View File

@ -26,6 +26,7 @@ import pytest
from _pytest import deprecated from _pytest import deprecated
from _pytest import nodes from _pytest import nodes
from _pytest import timing from _pytest import timing
from _pytest._code.code import ExceptionRepr
from _pytest.compat import TYPE_CHECKING from _pytest.compat import TYPE_CHECKING
from _pytest.config import Config from _pytest.config import Config
from _pytest.config import filename_arg from _pytest.config import filename_arg
@ -642,7 +643,7 @@ class LogXML:
else: else:
reporter.append_collect_skipped(report) reporter.append_collect_skipped(report)
def pytest_internalerror(self, excrepr) -> None: def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
reporter = self.node_reporter("internal") reporter = self.node_reporter("internal")
reporter.attrs.update(classname="pytest", name="internal") reporter.attrs.update(classname="pytest", name="internal")
reporter._add_simple(Junit.error, "internal error", excrepr) reporter._add_simple(Junit.error, "internal error", excrepr)

View File

@ -5,6 +5,7 @@ import os
import py import py
from _pytest._code.code import ExceptionRepr
from _pytest.config import Config from _pytest.config import Config
from _pytest.config.argparsing import Parser from _pytest.config.argparsing import Parser
from _pytest.reports import CollectReport from _pytest.reports import CollectReport
@ -99,9 +100,9 @@ class ResultLog:
longrepr = "%s:%d: %s" % report.longrepr # type: ignore longrepr = "%s:%d: %s" % report.longrepr # type: ignore
self.log_outcome(report, code, longrepr) self.log_outcome(report, code, longrepr)
def pytest_internalerror(self, excrepr): def pytest_internalerror(self, excrepr: ExceptionRepr) -> None:
reprcrash = getattr(excrepr, "reprcrash", None) if excrepr.reprcrash is not None:
path = getattr(reprcrash, "path", None) path = excrepr.reprcrash.path
if path is None: else:
path = "cwd:%s" % py.path.local() path = "cwd:%s" % py.path.local()
self.write_log_entry(path, "!", str(excrepr)) self.write_log_entry(path, "!", str(excrepr))

View File

@ -31,8 +31,7 @@ import pytest
from _pytest import nodes from _pytest import nodes
from _pytest import timing from _pytest import timing
from _pytest._code import ExceptionInfo from _pytest._code import ExceptionInfo
from _pytest._code.code import ExceptionChainRepr from _pytest._code.code import ExceptionRepr
from _pytest._code.code import ReprExceptionInfo
from _pytest._io import TerminalWriter from _pytest._io import TerminalWriter
from _pytest._io.wcwidth import wcswidth from _pytest._io.wcwidth import wcswidth
from _pytest.compat import order_preserving_dict from _pytest.compat import order_preserving_dict
@ -318,9 +317,7 @@ class TerminalReporter:
self._show_progress_info = self._determine_show_progress_info() self._show_progress_info = self._determine_show_progress_info()
self._collect_report_last_write = None # type: Optional[float] self._collect_report_last_write = None # type: Optional[float]
self._already_displayed_warnings = None # type: Optional[int] self._already_displayed_warnings = None # type: Optional[int]
self._keyboardinterrupt_memo = ( self._keyboardinterrupt_memo = None # type: Optional[ExceptionRepr]
None
) # type: Optional[Union[ReprExceptionInfo, ExceptionChainRepr]]
@property @property
def writer(self) -> TerminalWriter: def writer(self) -> TerminalWriter:
@ -454,10 +451,10 @@ class TerminalReporter:
if set_main_color: if set_main_color:
self._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"): for line in str(excrepr).split("\n"):
self.write_line("INTERNALERROR> " + line) self.write_line("INTERNALERROR> " + line)
return 1 return True
def pytest_warning_recorded( def pytest_warning_recorded(
self, warning_message: warnings.WarningMessage, nodeid: str, self, warning_message: warnings.WarningMessage, nodeid: str,