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

@ -23,6 +23,8 @@ from typing import Set
from typing import Tuple from typing import Tuple
from typing import Union from typing import Union
import py
from _pytest._io.saferepr import saferepr from _pytest._io.saferepr import saferepr
from _pytest._version import version from _pytest._version import version
from _pytest.assertion import util 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: if self.session is not None and not self._session_paths_checked:
self._session_paths_checked = True 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 -> # Make something as c:/projects/my_project/path.py ->
# ['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. # add 'path' to basenames to be checked.
self._basenames_to_check_rewrite.add(os.path.splitext(parts[-1])[0]) 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 return True
if self.session is not None: if self.session is not None:
if self.session.isinitpath(fn): if self.session.isinitpath(py.path.local(fn)):
state.trace( state.trace(
"matched test file (was specified on cmdline): {!r}".format(fn) "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) @pytest.hookimpl(tryfirst=True)
def pytest_configure(config: Config) -> None: def pytest_configure(config: Config) -> None:
# Type ignored: pending mechanism to store typed objects scoped to config. config.cache = Cache.for_config(config)
config.cache = Cache.for_config(config) # type: ignore # noqa: F821
config.pluginmanager.register(LFPlugin(config), "lfplugin") config.pluginmanager.register(LFPlugin(config), "lfplugin")
config.pluginmanager.register(NFPlugin(config), "nfplugin") config.pluginmanager.register(NFPlugin(config), "nfplugin")
@ -496,7 +495,7 @@ def pytest_report_header(config: Config) -> Optional[str]:
# starting with .., ../.. if sensible # starting with .., ../.. if sensible
try: try:
displaypath = cachedir.relative_to(config.rootdir) displaypath = cachedir.relative_to(str(config.rootdir))
except ValueError: except ValueError:
displaypath = cachedir displaypath = cachedir
return "cachedir: {}".format(displaypath) return "cachedir: {}".format(displaypath)

View File

@ -519,11 +519,10 @@ class MultiCapture:
def pop_outerr_to_orig(self): def pop_outerr_to_orig(self):
""" pop current snapshot out/err capture and flush to orig streams. """ """ pop current snapshot out/err capture and flush to orig streams. """
out, err = self.readouterr() out, err = self.readouterr()
# TODO: Fix type ignores.
if out: if out:
self.out.writeorg(out) # type: ignore[union-attr] # noqa: F821 self.out.writeorg(out)
if err: if err:
self.err.writeorg(err) # type: ignore[union-attr] # noqa: F821 self.err.writeorg(err)
return out, err return out, err
def suspend_capturing(self, in_: bool = False) -> None: def suspend_capturing(self, in_: bool = False) -> None:
@ -543,8 +542,7 @@ class MultiCapture:
if self.err: if self.err:
self.err.resume() self.err.resume()
if self._in_suspended: if self._in_suspended:
# TODO: Fix type ignore. self.in_.resume()
self.in_.resume() # type: ignore[union-attr] # noqa: F821
self._in_suspended = False self._in_suspended = False
def stop_capturing(self) -> None: def stop_capturing(self) -> None:
@ -751,11 +749,11 @@ class CaptureManager:
yield yield
@pytest.hookimpl(tryfirst=True) @pytest.hookimpl(tryfirst=True)
def pytest_keyboard_interrupt(self, excinfo): def pytest_keyboard_interrupt(self) -> None:
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
@ -307,10 +308,9 @@ class PytestPluginManager(PluginManager):
self._dirpath2confmods = {} # type: Dict[Any, List[object]] self._dirpath2confmods = {} # type: Dict[Any, List[object]]
# Maps a py.path.local to a module object. # Maps a py.path.local to a module object.
self._conftestpath2mod = {} # type: Dict[Any, object] self._conftestpath2mod = {} # type: Dict[Any, object]
self._confcutdir = None self._confcutdir = None # type: Optional[py.path.local]
self._noconftest = False self._noconftest = False
# Set of py.path.local's. self._duplicatepaths = set() # type: Set[py.path.local]
self._duplicatepaths = set() # type: Set[Any]
self.add_hookspecs(_pytest.hookspec) self.add_hookspecs(_pytest.hookspec)
self.register(self) self.register(self)
@ -893,9 +893,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(
@ -940,13 +944,12 @@ class Config:
ns, unknown_args = self._parser.parse_known_and_unknown_args( ns, unknown_args = self._parser.parse_known_and_unknown_args(
args, namespace=copy.copy(self.option) args, namespace=copy.copy(self.option)
) )
r = determine_setup( self.rootdir, self.inifile, self.inicfg = determine_setup(
ns.inifilename, ns.inifilename,
ns.file_or_dir + unknown_args, ns.file_or_dir + unknown_args,
rootdir_cmd_arg=ns.rootdir or None, rootdir_cmd_arg=ns.rootdir or None,
config=self, config=self,
) )
self.rootdir, self.inifile, self.inicfg = r
self._parser.extra_info["rootdir"] = self.rootdir self._parser.extra_info["rootdir"] = self.rootdir
self._parser.extra_info["inifile"] = self.inifile self._parser.extra_info["inifile"] = self.inifile
self._parser.addini("addopts", "extra command line options", "args") self._parser.addini("addopts", "extra command line options", "args")
@ -994,9 +997,7 @@ class Config:
package_files = ( package_files = (
str(file) str(file)
for dist in importlib_metadata.distributions() for dist in importlib_metadata.distributions()
# Type ignored due to missing stub: if any(ep.group == "pytest11" for ep in dist.entry_points)
# https://github.com/python/typeshed/pull/3795
if any(ep.group == "pytest11" for ep in dist.entry_points) # type: ignore
for file in dist.files or [] for file in dist.files or []
) )
@ -1073,6 +1074,11 @@ class Config:
# Imported lazily to improve start-up time. # Imported lazily to improve start-up time.
from packaging.version import Version 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__): if Version(minver) > Version(pytest.__version__):
raise pytest.UsageError( raise pytest.UsageError(
"%s: 'minversion' requires pytest-%s, actual pytest-%s'" "%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 # in this case, we already have a list ready to use
# #
if type == "pathlist": 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() dp = py.path.local(self.inifile).dirpath()
input_values = shlex.split(value) if isinstance(value, str) else value input_values = shlex.split(value) if isinstance(value, str) else value
return [dp.join(x, abs=True) for x in input_values] 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": elif filepath.ext == ".toml":
import toml import toml
config = toml.load(filepath) config = toml.load(str(filepath))
result = config.get("tool", {}).get("pytest", {}).get("ini_options", None) result = config.get("tool", {}).get("pytest", {}).get("ini_options", None)
if result is not None: if result is not None:
@ -161,16 +161,18 @@ def determine_setup(
args: List[str], args: List[str],
rootdir_cmd_arg: Optional[str] = None, rootdir_cmd_arg: Optional[str] = None,
config: Optional["Config"] = 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 rootdir = None
dirs = get_dirs_from_args(args) dirs = get_dirs_from_args(args)
if inifile: 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: if rootdir_cmd_arg is None:
rootdir = get_common_ancestor(dirs) rootdir = get_common_ancestor(dirs)
else: else:
ancestor = get_common_ancestor(dirs) 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: if rootdir is None and rootdir_cmd_arg is None:
for possible_rootdir in ancestor.parts(reverse=True): for possible_rootdir in ancestor.parts(reverse=True):
if possible_rootdir.join("setup.py").exists(): if possible_rootdir.join("setup.py").exists():
@ -178,7 +180,7 @@ def determine_setup(
break break
else: else:
if dirs != [ancestor]: if dirs != [ancestor]:
rootdir, inifile, inicfg = locate_config(dirs) rootdir, inipath, inicfg = locate_config(dirs)
if rootdir is None: if rootdir is None:
if config is not None: if config is not None:
cwd = config.invocation_dir cwd = config.invocation_dir
@ -196,4 +198,5 @@ def determine_setup(
rootdir 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 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,8 @@ 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.config import Config from _pytest.config import Config
from _pytest.config import ExitCode from _pytest.config import ExitCode
from _pytest.config import PytestPluginManager from _pytest.config import PytestPluginManager
@ -30,6 +32,7 @@ if TYPE_CHECKING:
from _pytest.nodes import Collector from _pytest.nodes import Collector
from _pytest.nodes import Item from _pytest.nodes import Item
from _pytest.nodes import Node from _pytest.nodes import Node
from _pytest.outcomes import Exit
from _pytest.python import Function from _pytest.python import Function
from _pytest.python import Metafunc from _pytest.python import Metafunc
from _pytest.python import Module from _pytest.python import Module
@ -757,11 +760,19 @@ 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(excinfo): def pytest_keyboard_interrupt(
excinfo: "ExceptionInfo[Union[KeyboardInterrupt, Exit]]",
) -> None:
""" called for keyboard interrupt. """ """ called for 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

@ -586,7 +586,7 @@ class LoggingPlugin:
fpath = Path(fname) fpath = Path(fname)
if not fpath.is_absolute(): if not fpath.is_absolute():
fpath = Path(self._config.rootdir, fpath) fpath = Path(str(self._config.rootdir), fpath)
if not fpath.parent.exists(): if not fpath.parent.exists():
fpath.parent.mkdir(exist_ok=True, parents=True) 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] ) # type: Dict[Tuple[Type[nodes.Collector], str], CollectReport]
# Dirnames of pkgs with dunder-init files. # 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( self._bestrelpathcache = _bestrelpath_cache(
config.rootdir config.rootdir
@ -601,7 +601,7 @@ class Session(nodes.FSCollector):
col = self._collectfile(pkginit, handle_dupes=False) col = self._collectfile(pkginit, handle_dupes=False)
if col: if col:
if isinstance(col[0], Package): 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 # always store a list in the cache, matchnodes expects it
self._collection_node_cache1[col[0].fspath] = [col[0]] self._collection_node_cache1[col[0].fspath] = [col[0]]
@ -623,8 +623,8 @@ class Session(nodes.FSCollector):
for x in self._collectfile(pkginit): for x in self._collectfile(pkginit):
yield x yield x
if isinstance(x, Package): if isinstance(x, Package):
self._collection_pkg_roots[dirpath] = x self._collection_pkg_roots[str(dirpath)] = x
if dirpath in self._collection_pkg_roots: if str(dirpath) in self._collection_pkg_roots:
# Do not collect packages here. # Do not collect packages here.
continue continue

View File

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

View File

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

View File

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

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

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

View File

@ -580,8 +580,9 @@ class TestInvocationVariants:
assert res.ret == 0 assert res.ret == 0
res.stdout.fnmatch_lines(["*1 passed*"]) res.stdout.fnmatch_lines(["*1 passed*"])
def test_equivalence_pytest_pytest(self): def test_equivalence_pytest_pydottest(self) -> None:
assert pytest.main == py.test.cmdline.main # 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): def test_invoke_with_invalid_type(self):
with pytest.raises( with pytest.raises(

View File

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