Merge remote-tracking branch 'origin' into issue_7346
This commit is contained in:
commit
7ea116d74c
|
@ -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]]
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
)
|
)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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. """
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Reference in New Issue