Merge remote-tracking branch 'origin' into issue_7346

This commit is contained in:
Gleb Nikonorov 2020-06-13 09:54:23 -04:00
commit 7ea116d74c
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:

View File

@ -580,8 +580,9 @@ class TestInvocationVariants:
assert res.ret == 0
res.stdout.fnmatch_lines(["*1 passed*"])
def test_equivalence_pytest_pytest(self):
assert pytest.main == py.test.cmdline.main
def test_equivalence_pytest_pydottest(self) -> None:
# Type ignored because `py.test` is not and will not be typed.
assert pytest.main == py.test.cmdline.main # type: ignore[attr-defined]
def test_invoke_with_invalid_type(self):
with pytest.raises(

View File

@ -1258,7 +1258,7 @@ class TestEarlyRewriteBailout:
def spy_find_spec(name, path):
self.find_spec_calls.append(name)
return importlib.machinery.PathFinder.find_spec(name, path) # type: ignore
return importlib.machinery.PathFinder.find_spec(name, path)
hook = AssertionRewritingHook(pytestconfig)
# use default patterns, otherwise we inherit pytest's testing config

View File

@ -740,13 +740,14 @@ class TestConfigFromdictargs:
)
with cwd.ensure(dir=True).as_cwd():
config = Config.fromdictargs(option_dict, ())
inipath = py.path.local(inifile)
assert config.args == [str(cwd)]
assert config.option.inifilename == inifile
assert config.option.capture == "no"
# this indicates this is the file used for getting configuration values
assert config.inifile == inifile
assert config.inifile == inipath
assert config.inicfg.get("name") == "value"
assert config.inicfg.get("should_not_be_set") is None

View File

@ -5,9 +5,12 @@ import textwrap
from typing import Dict
from typing import Generator
import py
import pytest
from _pytest.compat import TYPE_CHECKING
from _pytest.monkeypatch import MonkeyPatch
from _pytest.pytester import Testdir
if TYPE_CHECKING:
from typing import Type
@ -45,9 +48,12 @@ def test_setattr() -> None:
monkeypatch.undo() # double-undo makes no modification
assert A.x == 5
with pytest.raises(TypeError):
monkeypatch.setattr(A, "y") # type: ignore[call-overload]
class TestSetattrWithImportPath:
def test_string_expression(self, monkeypatch):
def test_string_expression(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.setattr("os.path.abspath", lambda x: "hello2")
assert os.path.abspath("123") == "hello2"
@ -64,30 +70,31 @@ class TestSetattrWithImportPath:
assert _pytest.config.Config == 42 # type: ignore
monkeypatch.delattr("_pytest.config.Config")
def test_wrong_target(self, monkeypatch):
pytest.raises(TypeError, lambda: monkeypatch.setattr(None, None))
def test_wrong_target(self, monkeypatch: MonkeyPatch) -> None:
with pytest.raises(TypeError):
monkeypatch.setattr(None, None) # type: ignore[call-overload]
def test_unknown_import(self, monkeypatch):
pytest.raises(ImportError, lambda: monkeypatch.setattr("unkn123.classx", None))
def test_unknown_import(self, monkeypatch: MonkeyPatch) -> None:
with pytest.raises(ImportError):
monkeypatch.setattr("unkn123.classx", None)
def test_unknown_attr(self, monkeypatch):
pytest.raises(
AttributeError, lambda: monkeypatch.setattr("os.path.qweqwe", None)
)
def test_unknown_attr(self, monkeypatch: MonkeyPatch) -> None:
with pytest.raises(AttributeError):
monkeypatch.setattr("os.path.qweqwe", None)
def test_unknown_attr_non_raising(self, monkeypatch: MonkeyPatch) -> None:
# https://github.com/pytest-dev/pytest/issues/746
monkeypatch.setattr("os.path.qweqwe", 42, raising=False)
assert os.path.qweqwe == 42 # type: ignore
def test_delattr(self, monkeypatch):
def test_delattr(self, monkeypatch: MonkeyPatch) -> None:
monkeypatch.delattr("os.path.abspath")
assert not hasattr(os.path, "abspath")
monkeypatch.undo()
assert os.path.abspath
def test_delattr():
def test_delattr() -> None:
class A:
x = 1
@ -107,7 +114,7 @@ def test_delattr():
assert A.x == 1
def test_setitem():
def test_setitem() -> None:
d = {"x": 1}
monkeypatch = MonkeyPatch()
monkeypatch.setitem(d, "x", 2)
@ -135,7 +142,7 @@ def test_setitem_deleted_meanwhile() -> None:
@pytest.mark.parametrize("before", [True, False])
def test_setenv_deleted_meanwhile(before):
def test_setenv_deleted_meanwhile(before: bool) -> None:
key = "qwpeoip123"
if before:
os.environ[key] = "world"
@ -167,10 +174,10 @@ def test_delitem() -> None:
assert d == {"hello": "world", "x": 1}
def test_setenv():
def test_setenv() -> None:
monkeypatch = MonkeyPatch()
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 2)
monkeypatch.setenv("XYZ123", 2) # type: ignore[arg-type]
import os
assert os.environ["XYZ123"] == "2"
@ -178,7 +185,7 @@ def test_setenv():
assert "XYZ123" not in os.environ
def test_delenv():
def test_delenv() -> None:
name = "xyz1234"
assert name not in os.environ
monkeypatch = MonkeyPatch()
@ -208,31 +215,28 @@ class TestEnvironWarnings:
VAR_NAME = "PYTEST_INTERNAL_MY_VAR"
def test_setenv_non_str_warning(self, monkeypatch):
def test_setenv_non_str_warning(self, monkeypatch: MonkeyPatch) -> None:
value = 2
msg = (
"Value of environment variable PYTEST_INTERNAL_MY_VAR type should be str, "
"but got 2 (type: int); converted to str implicitly"
)
with pytest.warns(pytest.PytestWarning, match=re.escape(msg)):
monkeypatch.setenv(str(self.VAR_NAME), value)
monkeypatch.setenv(str(self.VAR_NAME), value) # type: ignore[arg-type]
def test_setenv_prepend():
def test_setenv_prepend() -> None:
import os
monkeypatch = MonkeyPatch()
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 2, prepend="-")
assert os.environ["XYZ123"] == "2"
with pytest.warns(pytest.PytestWarning):
monkeypatch.setenv("XYZ123", 3, prepend="-")
monkeypatch.setenv("XYZ123", "2", prepend="-")
monkeypatch.setenv("XYZ123", "3", prepend="-")
assert os.environ["XYZ123"] == "3-2"
monkeypatch.undo()
assert "XYZ123" not in os.environ
def test_monkeypatch_plugin(testdir):
def test_monkeypatch_plugin(testdir: Testdir) -> None:
reprec = testdir.inline_runsource(
"""
def test_method(monkeypatch):
@ -243,7 +247,7 @@ def test_monkeypatch_plugin(testdir):
assert tuple(res) == (1, 0, 0), res
def test_syspath_prepend(mp: MonkeyPatch):
def test_syspath_prepend(mp: MonkeyPatch) -> None:
old = list(sys.path)
mp.syspath_prepend("world")
mp.syspath_prepend("hello")
@ -255,7 +259,7 @@ def test_syspath_prepend(mp: MonkeyPatch):
assert sys.path == old
def test_syspath_prepend_double_undo(mp: MonkeyPatch):
def test_syspath_prepend_double_undo(mp: MonkeyPatch) -> None:
old_syspath = sys.path[:]
try:
mp.syspath_prepend("hello world")
@ -267,24 +271,24 @@ def test_syspath_prepend_double_undo(mp: MonkeyPatch):
sys.path[:] = old_syspath
def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir):
def test_chdir_with_path_local(mp: MonkeyPatch, tmpdir: py.path.local) -> None:
mp.chdir(tmpdir)
assert os.getcwd() == tmpdir.strpath
def test_chdir_with_str(mp: MonkeyPatch, tmpdir):
def test_chdir_with_str(mp: MonkeyPatch, tmpdir: py.path.local) -> None:
mp.chdir(tmpdir.strpath)
assert os.getcwd() == tmpdir.strpath
def test_chdir_undo(mp: MonkeyPatch, tmpdir):
def test_chdir_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None:
cwd = os.getcwd()
mp.chdir(tmpdir)
mp.undo()
assert os.getcwd() == cwd
def test_chdir_double_undo(mp: MonkeyPatch, tmpdir):
def test_chdir_double_undo(mp: MonkeyPatch, tmpdir: py.path.local) -> None:
mp.chdir(tmpdir.strpath)
mp.undo()
tmpdir.chdir()
@ -292,7 +296,7 @@ def test_chdir_double_undo(mp: MonkeyPatch, tmpdir):
assert os.getcwd() == tmpdir.strpath
def test_issue185_time_breaks(testdir):
def test_issue185_time_breaks(testdir: Testdir) -> None:
testdir.makepyfile(
"""
import time
@ -310,7 +314,7 @@ def test_issue185_time_breaks(testdir):
)
def test_importerror(testdir):
def test_importerror(testdir: Testdir) -> None:
p = testdir.mkpydir("package")
p.join("a.py").write(
textwrap.dedent(
@ -360,7 +364,7 @@ def test_issue156_undo_staticmethod(Sample: "Type[Sample]") -> None:
assert Sample.hello()
def test_undo_class_descriptors_delattr():
def test_undo_class_descriptors_delattr() -> None:
class SampleParent:
@classmethod
def hello(_cls):
@ -387,7 +391,7 @@ def test_undo_class_descriptors_delattr():
assert original_world == SampleChild.world
def test_issue1338_name_resolving():
def test_issue1338_name_resolving() -> None:
pytest.importorskip("requests")
monkeypatch = MonkeyPatch()
try:
@ -396,7 +400,7 @@ def test_issue1338_name_resolving():
monkeypatch.undo()
def test_context():
def test_context() -> None:
monkeypatch = MonkeyPatch()
import functools
@ -408,7 +412,9 @@ def test_context():
assert inspect.isclass(functools.partial)
def test_syspath_prepend_with_namespace_packages(testdir, monkeypatch):
def test_syspath_prepend_with_namespace_packages(
testdir: Testdir, monkeypatch: MonkeyPatch
) -> None:
for dirname in "hello", "world":
d = testdir.mkdir(dirname)
ns = d.mkdir("ns_pkg")