Merge pull request #7222 from nicoddemus/remove-key-error-conftest-exception
This commit is contained in:
commit
abbd97f917
|
@ -1,5 +1,6 @@
|
||||||
""" command line options, ini-file and conftest.py processing. """
|
""" command line options, ini-file and conftest.py processing. """
|
||||||
import argparse
|
import argparse
|
||||||
|
import contextlib
|
||||||
import copy
|
import copy
|
||||||
import enum
|
import enum
|
||||||
import inspect
|
import inspect
|
||||||
|
@ -87,10 +88,15 @@ class ExitCode(enum.IntEnum):
|
||||||
|
|
||||||
class ConftestImportFailure(Exception):
|
class ConftestImportFailure(Exception):
|
||||||
def __init__(self, path, excinfo):
|
def __init__(self, path, excinfo):
|
||||||
Exception.__init__(self, path, excinfo)
|
super().__init__(path, excinfo)
|
||||||
self.path = path
|
self.path = path
|
||||||
self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType]
|
self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return "{}: {} (from {})".format(
|
||||||
|
self.excinfo[0].__name__, self.excinfo[1], self.path
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
def main(args=None, plugins=None) -> Union[int, ExitCode]:
|
||||||
""" return exit code, after performing an in-process test run.
|
""" return exit code, after performing an in-process test run.
|
||||||
|
@ -280,19 +286,6 @@ def _prepareconfig(
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
def _fail_on_non_top_pytest_plugins(conftestpath, confcutdir):
|
|
||||||
msg = (
|
|
||||||
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
|
|
||||||
"It affects the entire test suite instead of just below the conftest as expected.\n"
|
|
||||||
" {}\n"
|
|
||||||
"Please move it to a top level conftest file at the rootdir:\n"
|
|
||||||
" {}\n"
|
|
||||||
"For more information, visit:\n"
|
|
||||||
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
|
|
||||||
)
|
|
||||||
fail(msg.format(conftestpath, confcutdir), pytrace=False)
|
|
||||||
|
|
||||||
|
|
||||||
class PytestPluginManager(PluginManager):
|
class PytestPluginManager(PluginManager):
|
||||||
"""
|
"""
|
||||||
Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
|
Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
|
||||||
|
@ -511,34 +504,49 @@ class PytestPluginManager(PluginManager):
|
||||||
# Using Path().resolve() is better than py.path.realpath because
|
# Using Path().resolve() is better than py.path.realpath because
|
||||||
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
|
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
|
||||||
key = Path(str(conftestpath)).resolve()
|
key = Path(str(conftestpath)).resolve()
|
||||||
try:
|
|
||||||
return self._conftestpath2mod[key]
|
|
||||||
except KeyError:
|
|
||||||
pkgpath = conftestpath.pypkgpath()
|
|
||||||
if pkgpath is None:
|
|
||||||
_ensure_removed_sysmodule(conftestpath.purebasename)
|
|
||||||
try:
|
|
||||||
mod = conftestpath.pyimport()
|
|
||||||
if (
|
|
||||||
hasattr(mod, "pytest_plugins")
|
|
||||||
and self._configured
|
|
||||||
and not self._using_pyargs
|
|
||||||
):
|
|
||||||
_fail_on_non_top_pytest_plugins(conftestpath, self._confcutdir)
|
|
||||||
except Exception:
|
|
||||||
raise ConftestImportFailure(conftestpath, sys.exc_info())
|
|
||||||
|
|
||||||
self._conftest_plugins.add(mod)
|
with contextlib.suppress(KeyError):
|
||||||
self._conftestpath2mod[key] = mod
|
return self._conftestpath2mod[key]
|
||||||
dirpath = conftestpath.dirpath()
|
|
||||||
if dirpath in self._dirpath2confmods:
|
pkgpath = conftestpath.pypkgpath()
|
||||||
for path, mods in self._dirpath2confmods.items():
|
if pkgpath is None:
|
||||||
if path and path.relto(dirpath) or path == dirpath:
|
_ensure_removed_sysmodule(conftestpath.purebasename)
|
||||||
assert mod not in mods
|
|
||||||
mods.append(mod)
|
try:
|
||||||
self.trace("loading conftestmodule {!r}".format(mod))
|
mod = conftestpath.pyimport()
|
||||||
self.consider_conftest(mod)
|
except Exception as e:
|
||||||
return mod
|
raise ConftestImportFailure(conftestpath, sys.exc_info()) from e
|
||||||
|
|
||||||
|
self._check_non_top_pytest_plugins(mod, conftestpath)
|
||||||
|
|
||||||
|
self._conftest_plugins.add(mod)
|
||||||
|
self._conftestpath2mod[key] = mod
|
||||||
|
dirpath = conftestpath.dirpath()
|
||||||
|
if dirpath in self._dirpath2confmods:
|
||||||
|
for path, mods in self._dirpath2confmods.items():
|
||||||
|
if path and path.relto(dirpath) or path == dirpath:
|
||||||
|
assert mod not in mods
|
||||||
|
mods.append(mod)
|
||||||
|
self.trace("loading conftestmodule {!r}".format(mod))
|
||||||
|
self.consider_conftest(mod)
|
||||||
|
return mod
|
||||||
|
|
||||||
|
def _check_non_top_pytest_plugins(self, mod, conftestpath):
|
||||||
|
if (
|
||||||
|
hasattr(mod, "pytest_plugins")
|
||||||
|
and self._configured
|
||||||
|
and not self._using_pyargs
|
||||||
|
):
|
||||||
|
msg = (
|
||||||
|
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
|
||||||
|
"It affects the entire test suite instead of just below the conftest as expected.\n"
|
||||||
|
" {}\n"
|
||||||
|
"Please move it to a top level conftest file at the rootdir:\n"
|
||||||
|
" {}\n"
|
||||||
|
"For more information, visit:\n"
|
||||||
|
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
|
||||||
|
)
|
||||||
|
fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
|
||||||
|
|
||||||
#
|
#
|
||||||
# API for bootstrapping plugin loading
|
# API for bootstrapping plugin loading
|
||||||
|
|
|
@ -10,6 +10,7 @@ import pytest
|
||||||
from _pytest.compat import importlib_metadata
|
from _pytest.compat import importlib_metadata
|
||||||
from _pytest.config import _iter_rewritable_modules
|
from _pytest.config import _iter_rewritable_modules
|
||||||
from _pytest.config import Config
|
from _pytest.config import Config
|
||||||
|
from _pytest.config import ConftestImportFailure
|
||||||
from _pytest.config import ExitCode
|
from _pytest.config import ExitCode
|
||||||
from _pytest.config.exceptions import UsageError
|
from _pytest.config.exceptions import UsageError
|
||||||
from _pytest.config.findpaths import determine_setup
|
from _pytest.config.findpaths import determine_setup
|
||||||
|
@ -1471,3 +1472,19 @@ class TestPytestPluginsVariable:
|
||||||
assert res.ret == 0
|
assert res.ret == 0
|
||||||
msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported"
|
msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported"
|
||||||
assert msg not in res.stdout.str()
|
assert msg not in res.stdout.str()
|
||||||
|
|
||||||
|
|
||||||
|
def test_conftest_import_error_repr(tmpdir):
|
||||||
|
"""
|
||||||
|
ConftestImportFailure should use a short error message and readable path to the failed
|
||||||
|
conftest.py file
|
||||||
|
"""
|
||||||
|
path = tmpdir.join("foo/conftest.py")
|
||||||
|
with pytest.raises(
|
||||||
|
ConftestImportFailure,
|
||||||
|
match=re.escape("RuntimeError: some error (from {})".format(path)),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
raise RuntimeError("some error")
|
||||||
|
except Exception:
|
||||||
|
raise ConftestImportFailure(path, sys.exc_info())
|
||||||
|
|
Loading…
Reference in New Issue