Merge remote-tracking branch 'origin' into issue_7346

This commit is contained in:
Gleb Nikonorov
2020-06-13 09:54:23 -04:00
21 changed files with 216 additions and 126 deletions

View File

@@ -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]]

View File

@@ -23,6 +23,8 @@ from typing import Set
from typing import Tuple
from typing import Union
import py
from _pytest._io.saferepr import saferepr
from _pytest._version import version
from _pytest.assertion import util
@@ -177,10 +179,10 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
"""
if self.session is not None and not self._session_paths_checked:
self._session_paths_checked = True
for path in self.session._initialpaths:
for initial_path in self.session._initialpaths:
# Make something as c:/projects/my_project/path.py ->
# ['c:', 'projects', 'my_project', 'path.py']
parts = str(path).split(os.path.sep)
parts = str(initial_path).split(os.path.sep)
# add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0])
@@ -213,7 +215,7 @@ class AssertionRewritingHook(importlib.abc.MetaPathFinder, importlib.abc.Loader)
return True
if self.session is not None:
if self.session.isinitpath(fn):
if self.session.isinitpath(py.path.local(fn)):
state.trace(
"matched test file (was specified on cmdline): {!r}".format(fn)
)

View File

@@ -464,8 +464,7 @@ def pytest_cmdline_main(config: Config) -> Optional[Union[int, ExitCode]]:
@pytest.hookimpl(tryfirst=True)
def pytest_configure(config: Config) -> None:
# Type ignored: pending mechanism to store typed objects scoped to config.
config.cache = Cache.for_config(config) # type: ignore # noqa: F821
config.cache = Cache.for_config(config)
config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin")
@@ -496,7 +495,7 @@ def pytest_report_header(config: Config) -> Optional[str]:
# starting with .., ../.. if sensible
try:
displaypath = cachedir.relative_to(config.rootdir)
displaypath = cachedir.relative_to(str(config.rootdir))
except ValueError:
displaypath = cachedir
return "cachedir: {}".format(displaypath)

View File

@@ -519,11 +519,10 @@ class MultiCapture:
def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr()
# TODO: Fix type ignores.
if out:
self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821
self.out.writeorg(out)
if err:
self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821
self.err.writeorg(err)
return out, err
def suspend_capturing(self, in_: bool = False) -> None:
@@ -543,8 +542,7 @@ class MultiCapture:
if self.err:
self.err.resume()
if self._in_suspended:
# TODO: Fix type ignore.
self.in_.resume() # type: ignore[union-attr] # noqa: F821
self.in_.resume()
self._in_suspended = False
def stop_capturing(self) -> None:
@@ -751,11 +749,11 @@ class CaptureManager:
yield
@pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo):
def pytest_keyboard_interrupt(self) -> None:
self.stop_global_capturing()
@pytest.hookimpl(tryfirst=True)
def pytest_internalerror(self, excinfo):
def pytest_internalerror(self) -> None:
self.stop_global_capturing()

View File

@@ -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
@@ -307,10 +308,9 @@ class PytestPluginManager(PluginManager):
self._dirpath2confmods = {} # type: Dict[Any, List[object]]
# Maps a py.path.local to a module object.
self._conftestpath2mod = {} # type: Dict[Any, object]
self._confcutdir = None
self._confcutdir = None # type: Optional[py.path.local]
self._noconftest = False
# Set of py.path.local's.
self._duplicatepaths = set() # type: Set[Any]
self._duplicatepaths = set() # type: Set[py.path.local]
self.add_hookspecs(_pytest.hookspec)
self.register(self)
@@ -893,9 +893,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(
@@ -940,13 +944,12 @@ class Config:
ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option)
)
r = determine_setup(
self.rootdir, self.inifile, self.inicfg = determine_setup(
ns.inifilename,
ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None,
config=self,
)
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile
self._parser.addini("addopts", "extra command line options", "args")
@@ -994,9 +997,7 @@ class Config:
package_files = (
str(file)
for dist in importlib_metadata.distributions()
# Type ignored due to missing stub:
# https://github.com/python/typeshed/pull/3795
if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore
if any(ep.group == "pytest11" for ep in dist.entry_points)
for file in dist.files or []
)
@@ -1073,6 +1074,11 @@ class Config:
# Imported lazily to improve start-up time.
from packaging.version import Version
if not isinstance(minver, str):
raise pytest.UsageError(
"%s: 'minversion' must be a single value" % self.inifile
)
if Version(minver) > Version(pytest.__version__):
raise pytest.UsageError(
"%s: 'minversion' requires pytest-%s, actual pytest-%s'"
@@ -1200,6 +1206,8 @@ class Config:
# in this case, we already have a list ready to use
#
if type == "pathlist":
# TODO: This assert is probably not valid in all cases.
assert self.inifile is not None
dp = py.path.local(self.inifile).dirpath()
input_values = shlex.split(value) if isinstance(value, str) else value
return [dp.join(x, abs=True) for x in input_values]

View File

@@ -63,7 +63,7 @@ def load_config_dict_from_file(
elif filepath.ext == ".toml":
import toml
config = toml.load(filepath)
config = toml.load(str(filepath))
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None:
@@ -161,16 +161,18 @@ def determine_setup(
args: List[str],
rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = None,
) -> Tuple[py.path.local, Optional[str], Dict[str, Union[str, List[str]]]]:
) -> Tuple[py.path.local, Optional[py.path.local], Dict[str, Union[str, List[str]]]]:
rootdir = None
dirs = get_dirs_from_args(args)
if inifile:
inicfg = load_config_dict_from_file(py.path.local(inifile)) or {}
inipath_ = py.path.local(inifile)
inipath = inipath_ # type: Optional[py.path.local]
inicfg = load_config_dict_from_file(inipath_) or {}
if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs)
else:
ancestor = get_common_ancestor(dirs)
rootdir, inifile, inicfg = locate_config([ancestor])
rootdir, inipath, inicfg = locate_config([ancestor])
if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True):
if possible_rootdir.join("setup.py").exists():
@@ -178,7 +180,7 @@ def determine_setup(
break
else:
if dirs != [ancestor]:
rootdir, inifile, inicfg = locate_config(dirs)
rootdir, inipath, inicfg = locate_config(dirs)
if rootdir is None:
if config is not None:
cwd = config.invocation_dir
@@ -196,4 +198,5 @@ def determine_setup(
rootdir
)
)
return rootdir, inifile, inicfg or {}
assert rootdir is not None
return rootdir, inipath, inicfg or {}

View File

@@ -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)

View File

@@ -19,6 +19,8 @@ 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
from _pytest.config import PytestPluginManager
@@ -30,6 +32,7 @@ if TYPE_CHECKING:
from _pytest.nodes import Collector
from _pytest.nodes import Item
from _pytest.nodes import Node
from _pytest.outcomes import Exit
from _pytest.python import Function
from _pytest.python import Metafunc
from _pytest.python import Module
@@ -757,11 +760,19 @@ 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(excinfo):
def pytest_keyboard_interrupt(
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
) -> None:
""" called for keyboard interrupt. """

View File

@@ -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)

View File

@@ -586,7 +586,7 @@ class LoggingPlugin:
fpath = Path(fname)
if not fpath.is_absolute():
fpath = Path(self._config.rootdir, fpath)
fpath = Path(str(self._config.rootdir), fpath)
if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True)

View File

@@ -439,7 +439,7 @@ class Session(nodes.FSCollector):
) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]
# Dirnames of pkgs with dunder-init files.
self._collection_pkg_roots = {} # type: Dict[py.path.local, Package]
self._collection_pkg_roots = {} # type: Dict[str, Package]
self._bestrelpathcache = _bestrelpath_cache(
config.rootdir
@@ -601,7 +601,7 @@ class Session(nodes.FSCollector):
col = self._collectfile(pkginit, handle_dupes=False)
if col:
if isinstance(col[0], Package):
self._collection_pkg_roots[parent] = col[0]
self._collection_pkg_roots[str(parent)] = col[0]
# always store a list in the cache, matchnodes expects it
self._collection_node_cache1[col[0].fspath] = [col[0]]
@@ -623,8 +623,8 @@ class Session(nodes.FSCollector):
for x in self._collectfile(pkginit):
yield x
if isinstance(x, Package):
self._collection_pkg_roots[dirpath] = x
if dirpath in self._collection_pkg_roots:
self._collection_pkg_roots[str(dirpath)] = x
if str(dirpath) in self._collection_pkg_roots:
# Do not collect packages here.
continue

View File

@@ -66,9 +66,7 @@ def get_empty_parameterset_mark(
fs,
lineno,
)
# Type ignored because MarkDecorator.__call__() is a bit tough to
# annotate ATM.
return mark(reason=reason) # type: ignore[no-any-return] # noqa: F723
return mark(reason=reason)
class ParameterSet(

View File

@@ -4,17 +4,29 @@ import re
import sys
import warnings
from contextlib import contextmanager
from typing import Any
from typing import Generator
from typing import List
from typing import MutableMapping
from typing import Optional
from typing import Tuple
from typing import TypeVar
from typing import Union
import pytest
from _pytest.compat import overload
from _pytest.fixtures import fixture
from _pytest.pathlib import Path
RE_IMPORT_ERROR_NAME = re.compile(r"^No module named (.*)$")
K = TypeVar("K")
V = TypeVar("V")
@fixture
def monkeypatch():
def monkeypatch() -> Generator["MonkeyPatch", None, None]:
"""The returned ``monkeypatch`` fixture provides these
helper methods to modify objects, dictionaries or os.environ::
@@ -37,7 +49,7 @@ def monkeypatch():
mpatch.undo()
def resolve(name):
def resolve(name: str) -> object:
# simplified from zope.dottedname
parts = name.split(".")
@@ -66,7 +78,7 @@ def resolve(name):
return found
def annotated_getattr(obj, name, ann):
def annotated_getattr(obj: object, name: str, ann: str) -> object:
try:
obj = getattr(obj, name)
except AttributeError:
@@ -78,7 +90,7 @@ def annotated_getattr(obj, name, ann):
return obj
def derive_importpath(import_path, raising):
def derive_importpath(import_path: str, raising: bool) -> Tuple[str, object]:
if not isinstance(import_path, str) or "." not in import_path:
raise TypeError(
"must be absolute import path string, not {!r}".format(import_path)
@@ -91,7 +103,7 @@ def derive_importpath(import_path, raising):
class Notset:
def __repr__(self):
def __repr__(self) -> str:
return "<notset>"
@@ -102,11 +114,13 @@ class MonkeyPatch:
""" Object returned by the ``monkeypatch`` fixture keeping a record of setattr/item/env/syspath changes.
"""
def __init__(self):
self._setattr = []
self._setitem = []
self._cwd = None
self._savesyspath = None
def __init__(self) -> None:
self._setattr = [] # type: List[Tuple[object, str, object]]
self._setitem = (
[]
) # type: List[Tuple[MutableMapping[Any, Any], object, object]]
self._cwd = None # type: Optional[str]
self._savesyspath = None # type: Optional[List[str]]
@contextmanager
def context(self) -> Generator["MonkeyPatch", None, None]:
@@ -133,7 +147,25 @@ class MonkeyPatch:
finally:
m.undo()
def setattr(self, target, name, value=notset, raising=True):
@overload
def setattr(
self, target: str, name: object, value: Notset = ..., raising: bool = ...,
) -> None:
raise NotImplementedError()
@overload # noqa: F811
def setattr( # noqa: F811
self, target: object, name: str, value: object, raising: bool = ...,
) -> None:
raise NotImplementedError()
def setattr( # noqa: F811
self,
target: Union[str, object],
name: Union[object, str],
value: object = notset,
raising: bool = True,
) -> None:
""" Set attribute value on target, memorizing the old value.
By default raise AttributeError if the attribute did not exist.
@@ -150,7 +182,7 @@ class MonkeyPatch:
__tracebackhide__ = True
import inspect
if value is notset:
if isinstance(value, Notset):
if not isinstance(target, str):
raise TypeError(
"use setattr(target, name, value) or "
@@ -159,6 +191,13 @@ class MonkeyPatch:
)
value = name
name, target = derive_importpath(target, raising)
else:
if not isinstance(name, str):
raise TypeError(
"use setattr(target, name, value) with name being a string or "
"setattr(target, value) with target being a dotted "
"import string"
)
oldval = getattr(target, name, notset)
if raising and oldval is notset:
@@ -170,7 +209,12 @@ class MonkeyPatch:
self._setattr.append((target, name, oldval))
setattr(target, name, value)
def delattr(self, target, name=notset, raising=True):
def delattr(
self,
target: Union[object, str],
name: Union[str, Notset] = notset,
raising: bool = True,
) -> None:
""" Delete attribute ``name`` from ``target``, by default raise
AttributeError it the attribute did not previously exist.
@@ -184,7 +228,7 @@ class MonkeyPatch:
__tracebackhide__ = True
import inspect
if name is notset:
if isinstance(name, Notset):
if not isinstance(target, str):
raise TypeError(
"use delattr(target, name) or "
@@ -204,12 +248,12 @@ class MonkeyPatch:
self._setattr.append((target, name, oldval))
delattr(target, name)
def setitem(self, dic, name, value):
def setitem(self, dic: MutableMapping[K, V], name: K, value: V) -> None:
""" Set dictionary entry ``name`` to value. """
self._setitem.append((dic, name, dic.get(name, notset)))
dic[name] = value
def delitem(self, dic, name, raising=True):
def delitem(self, dic: MutableMapping[K, V], name: K, raising: bool = True) -> None:
""" Delete ``name`` from dict. Raise KeyError if it doesn't exist.
If ``raising`` is set to False, no exception will be raised if the
@@ -222,7 +266,7 @@ class MonkeyPatch:
self._setitem.append((dic, name, dic.get(name, notset)))
del dic[name]
def setenv(self, name, value, prepend=None):
def setenv(self, name: str, value: str, prepend: Optional[str] = None) -> None:
""" Set environment variable ``name`` to ``value``. If ``prepend``
is a character, read the current environment variable value
and prepend the ``value`` adjoined with the ``prepend`` character."""
@@ -241,16 +285,17 @@ class MonkeyPatch:
value = value + prepend + os.environ[name]
self.setitem(os.environ, name, value)
def delenv(self, name, raising=True):
def delenv(self, name: str, raising: bool = True) -> None:
""" Delete ``name`` from the environment. Raise KeyError if it does
not exist.
If ``raising`` is set to False, no exception will be raised if the
environment variable is missing.
"""
self.delitem(os.environ, name, raising=raising)
environ = os.environ # type: MutableMapping[str, str]
self.delitem(environ, name, raising=raising)
def syspath_prepend(self, path):
def syspath_prepend(self, path) -> None:
""" Prepend ``path`` to ``sys.path`` list of import locations. """
from pkg_resources import fixup_namespace_packages
@@ -272,7 +317,7 @@ class MonkeyPatch:
invalidate_caches()
def chdir(self, path):
def chdir(self, path) -> None:
""" Change the current working directory to the specified path.
Path can be a string or a py.path.local object.
"""
@@ -286,7 +331,7 @@ class MonkeyPatch:
else:
os.chdir(path)
def undo(self):
def undo(self) -> None:
""" Undo previous changes. This call consumes the
undo stack. Calling it a second time has no effect unless
you do more monkeypatching after the undo call.
@@ -306,14 +351,14 @@ class MonkeyPatch:
else:
delattr(obj, name)
self._setattr[:] = []
for dictionary, name, value in reversed(self._setitem):
for dictionary, key, value in reversed(self._setitem):
if value is notset:
try:
del dictionary[name]
del dictionary[key]
except KeyError:
pass # was already deleted, so we have the desired state
else:
dictionary[name] = value
dictionary[key] = value
self._setitem[:] = []
if self._savesyspath is not None:
sys.path[:] = self._savesyspath

View File

@@ -393,7 +393,7 @@ class Node(metaclass=NodeMeta):
# It will be better to just always display paths relative to invocation_dir, but
# this requires a lot of plumbing (#6428).
try:
abspath = Path(os.getcwd()) != Path(self.config.invocation_dir)
abspath = Path(os.getcwd()) != Path(str(self.config.invocation_dir))
except OSError:
abspath = True

View File

@@ -656,7 +656,7 @@ class Package(Module):
parts_ = parts(path.strpath)
if any(
pkg_prefix in parts_ and pkg_prefix.join("__init__.py") != path
str(pkg_prefix) in parts_ and pkg_prefix.join("__init__.py") != path
for pkg_prefix in pkg_prefixes
):
continue
@@ -1332,7 +1332,7 @@ def _show_fixtures_per_test(config, session):
def get_best_relpath(func):
loc = getlocation(func, curdir)
return curdir.bestrelpath(loc)
return curdir.bestrelpath(py.path.local(loc))
def write_fixture(fixture_def):
argname = fixture_def.argname
@@ -1406,7 +1406,7 @@ def _showfixtures_main(config: Config, session: Session) -> None:
(
len(fixturedef.baseid),
fixturedef.func.__module__,
curdir.bestrelpath(loc),
curdir.bestrelpath(py.path.local(loc)),
fixturedef.argname,
fixturedef,
)

View File

@@ -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))

View File

@@ -30,6 +30,8 @@ from more_itertools import collapse
import pytest
from _pytest import nodes
from _pytest import timing
from _pytest._code import ExceptionInfo
from _pytest._code.code import ExceptionRepr
from _pytest._io import TerminalWriter
from _pytest._io.wcwidth import wcswidth
from _pytest.compat import order_preserving_dict
@@ -315,6 +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[ExceptionRepr]
@property
def writer(self) -> TerminalWriter:
@@ -377,9 +380,9 @@ class TerminalReporter:
if self.currentfspath is not None and self._show_progress_info:
self._write_progress_information_filling_space()
self.currentfspath = fspath
fspath = self.startdir.bestrelpath(fspath)
relfspath = self.startdir.bestrelpath(fspath)
self._tw.line()
self._tw.write(fspath + " ")
self._tw.write(relfspath + " ")
self._tw.write(res, flush=True, **markup)
def write_ensure_prefix(self, prefix, extra: str = "", **kwargs) -> None:
@@ -448,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,
@@ -783,7 +786,7 @@ class TerminalReporter:
self.write_sep("!", str(session.shouldfail), red=True)
if exitstatus == ExitCode.INTERRUPTED:
self._report_keyboardinterrupt()
del self._keyboardinterrupt_memo
self._keyboardinterrupt_memo = None
elif session.shouldstop:
self.write_sep("!", str(session.shouldstop), red=True)
self.summary_stats()
@@ -799,15 +802,17 @@ class TerminalReporter:
# Display any extra warnings from teardown here (if any).
self.summary_warnings()
def pytest_keyboard_interrupt(self, excinfo) -> None:
def pytest_keyboard_interrupt(self, excinfo: ExceptionInfo[BaseException]) -> None:
self._keyboardinterrupt_memo = excinfo.getrepr(funcargs=True)
def pytest_unconfigure(self) -> None:
if hasattr(self, "_keyboardinterrupt_memo"):
if self._keyboardinterrupt_memo is not None:
self._report_keyboardinterrupt()
def _report_keyboardinterrupt(self) -> None:
excrepr = self._keyboardinterrupt_memo
assert excrepr is not None
assert excrepr.reprcrash is not None
msg = excrepr.reprcrash.message
self.write_sep("!", msg)
if "KeyboardInterrupt" in msg: