Skip to content

Commit abbd97f

Browse files
authored
Merge pull request #7222 from nicoddemus/remove-key-error-conftest-exception
2 parents d4dfe86 + c26f389 commit abbd97f

File tree

2 files changed

+66
-41
lines changed

2 files changed

+66
-41
lines changed

src/_pytest/config/__init__.py

Lines changed: 49 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
""" command line options, ini-file and conftest.py processing. """
22
import argparse
3+
import contextlib
34
import copy
45
import enum
56
import inspect
@@ -87,10 +88,15 @@ class ExitCode(enum.IntEnum):
8788

8889
class ConftestImportFailure(Exception):
8990
def __init__(self, path, excinfo):
90-
Exception.__init__(self, path, excinfo)
91+
super().__init__(path, excinfo)
9192
self.path = path
9293
self.excinfo = excinfo # type: Tuple[Type[Exception], Exception, TracebackType]
9394

95+
def __str__(self):
96+
return "{}: {} (from {})".format(
97+
self.excinfo[0].__name__, self.excinfo[1], self.path
98+
)
99+
94100

95101
def main(args=None, plugins=None) -> Union[int, ExitCode]:
96102
""" return exit code, after performing an in-process test run.
@@ -280,19 +286,6 @@ def _prepareconfig(
280286
raise
281287

282288

283-
def _fail_on_non_top_pytest_plugins(conftestpath, confcutdir):
284-
msg = (
285-
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
286-
"It affects the entire test suite instead of just below the conftest as expected.\n"
287-
" {}\n"
288-
"Please move it to a top level conftest file at the rootdir:\n"
289-
" {}\n"
290-
"For more information, visit:\n"
291-
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
292-
)
293-
fail(msg.format(conftestpath, confcutdir), pytrace=False)
294-
295-
296289
class PytestPluginManager(PluginManager):
297290
"""
298291
Overwrites :py:class:`pluggy.PluginManager <pluggy.PluginManager>` to add pytest-specific
@@ -511,34 +504,49 @@ def _importconftest(self, conftestpath):
511504
# Using Path().resolve() is better than py.path.realpath because
512505
# it resolves to the correct path/drive in case-insensitive file systems (#5792)
513506
key = Path(str(conftestpath)).resolve()
514-
try:
507+
508+
with contextlib.suppress(KeyError):
515509
return self._conftestpath2mod[key]
516-
except KeyError:
517-
pkgpath = conftestpath.pypkgpath()
518-
if pkgpath is None:
519-
_ensure_removed_sysmodule(conftestpath.purebasename)
520-
try:
521-
mod = conftestpath.pyimport()
522-
if (
523-
hasattr(mod, "pytest_plugins")
524-
and self._configured
525-
and not self._using_pyargs
526-
):
527-
_fail_on_non_top_pytest_plugins(conftestpath, self._confcutdir)
528-
except Exception:
529-
raise ConftestImportFailure(conftestpath, sys.exc_info())
530-
531-
self._conftest_plugins.add(mod)
532-
self._conftestpath2mod[key] = mod
533-
dirpath = conftestpath.dirpath()
534-
if dirpath in self._dirpath2confmods:
535-
for path, mods in self._dirpath2confmods.items():
536-
if path and path.relto(dirpath) or path == dirpath:
537-
assert mod not in mods
538-
mods.append(mod)
539-
self.trace("loading conftestmodule {!r}".format(mod))
540-
self.consider_conftest(mod)
541-
return mod
510+
511+
pkgpath = conftestpath.pypkgpath()
512+
if pkgpath is None:
513+
_ensure_removed_sysmodule(conftestpath.purebasename)
514+
515+
try:
516+
mod = conftestpath.pyimport()
517+
except Exception as e:
518+
raise ConftestImportFailure(conftestpath, sys.exc_info()) from e
519+
520+
self._check_non_top_pytest_plugins(mod, conftestpath)
521+
522+
self._conftest_plugins.add(mod)
523+
self._conftestpath2mod[key] = mod
524+
dirpath = conftestpath.dirpath()
525+
if dirpath in self._dirpath2confmods:
526+
for path, mods in self._dirpath2confmods.items():
527+
if path and path.relto(dirpath) or path == dirpath:
528+
assert mod not in mods
529+
mods.append(mod)
530+
self.trace("loading conftestmodule {!r}".format(mod))
531+
self.consider_conftest(mod)
532+
return mod
533+
534+
def _check_non_top_pytest_plugins(self, mod, conftestpath):
535+
if (
536+
hasattr(mod, "pytest_plugins")
537+
and self._configured
538+
and not self._using_pyargs
539+
):
540+
msg = (
541+
"Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:\n"
542+
"It affects the entire test suite instead of just below the conftest as expected.\n"
543+
" {}\n"
544+
"Please move it to a top level conftest file at the rootdir:\n"
545+
" {}\n"
546+
"For more information, visit:\n"
547+
" https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files"
548+
)
549+
fail(msg.format(conftestpath, self._confcutdir), pytrace=False)
542550

543551
#
544552
# API for bootstrapping plugin loading

testing/test_config.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from _pytest.compat import importlib_metadata
1111
from _pytest.config import _iter_rewritable_modules
1212
from _pytest.config import Config
13+
from _pytest.config import ConftestImportFailure
1314
from _pytest.config import ExitCode
1415
from _pytest.config.exceptions import UsageError
1516
from _pytest.config.findpaths import determine_setup
@@ -1471,3 +1472,19 @@ def test_pytest_plugins_in_non_top_level_conftest_unsupported_no_false_positives
14711472
assert res.ret == 0
14721473
msg = "Defining 'pytest_plugins' in a non-top-level conftest is no longer supported"
14731474
assert msg not in res.stdout.str()
1475+
1476+
1477+
def test_conftest_import_error_repr(tmpdir):
1478+
"""
1479+
ConftestImportFailure should use a short error message and readable path to the failed
1480+
conftest.py file
1481+
"""
1482+
path = tmpdir.join("foo/conftest.py")
1483+
with pytest.raises(
1484+
ConftestImportFailure,
1485+
match=re.escape("RuntimeError: some error (from {})".format(path)),
1486+
):
1487+
try:
1488+
raise RuntimeError("some error")
1489+
except Exception:
1490+
raise ConftestImportFailure(path, sys.exc_info())

0 commit comments

Comments
 (0)