deprecated pytest_plugins in non-top-level conftest

This commit is contained in:
Brian Maissy 2018-02-21 01:27:24 +02:00
parent d6ddeb395b
commit 54b15f5826
6 changed files with 98 additions and 0 deletions

View File

@ -201,6 +201,8 @@ class PytestPluginManager(PluginManager):
# Config._consider_importhook will set a real object if required. # Config._consider_importhook will set a real object if required.
self.rewrite_hook = _pytest.assertion.DummyRewriteHook() self.rewrite_hook = _pytest.assertion.DummyRewriteHook()
# Used to know when we are importing conftests after the pytest_configure stage
self._configured = False
def addhooks(self, module_or_class): def addhooks(self, module_or_class):
""" """
@ -276,6 +278,7 @@ class PytestPluginManager(PluginManager):
config.addinivalue_line("markers", config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the " "trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.") "plugin machinery will try to call it last/as late as possible.")
self._configured = True
def _warn(self, message): def _warn(self, message):
kwargs = message if isinstance(message, dict) else { kwargs = message if isinstance(message, dict) else {
@ -366,6 +369,9 @@ class PytestPluginManager(PluginManager):
_ensure_removed_sysmodule(conftestpath.purebasename) _ensure_removed_sysmodule(conftestpath.purebasename)
try: try:
mod = conftestpath.pyimport() mod = conftestpath.pyimport()
if hasattr(mod, 'pytest_plugins') and self._configured:
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
warnings.warn(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST)
except Exception: except Exception:
raise ConftestImportFailure(conftestpath, sys.exc_info()) raise ConftestImportFailure(conftestpath, sys.exc_info())

View File

@ -56,3 +56,9 @@ METAFUNC_ADD_CALL = (
"Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n" "Metafunc.addcall is deprecated and scheduled to be removed in pytest 4.0.\n"
"Please use Metafunc.parametrize instead." "Please use Metafunc.parametrize instead."
) )
PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST = RemovedInPytest4Warning(
"Defining pytest_plugins in a non-top-level conftest is deprecated, "
"because it affects the entire directory tree in a non-explicit way.\n"
"Please move it to the top level conftest file instead."
)

1
changelog/3084.removal Normal file
View File

@ -0,0 +1 @@
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py files, because they "leak" to the entire directory tree.

View File

@ -79,6 +79,12 @@ will be loaded as well.
which will import the specified module as a ``pytest`` plugin. which will import the specified module as a ``pytest`` plugin.
.. note::
Requiring plugins using a ``pytest_plugins`` variable in non-root
``conftest.py`` files is deprecated. See
:ref:`full explanation <requiring plugins in non-noot conftests>`
in the Writing plugins section.
.. _`findpluginname`: .. _`findpluginname`:
Finding out which plugins are active Finding out which plugins are active

View File

@ -254,6 +254,18 @@ application modules:
if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
of the variable will also be loaded as plugins, and so on. of the variable will also be loaded as plugins, and so on.
.. _`requiring plugins in non-noot conftests`:
.. note::
Requiring plugins using a ``pytest_plugins`` variable in non-root
``conftest.py`` files is deprecated.
This is important because ``conftest.py`` files implement per-directory
hook implementations, but once a plugin is imported, it will affect the
entire directory tree. In order to avoid confusion, defining
``pytest_plugins`` in any ``conftest.py`` file which is not located in the
tests root directory is deprecated, and will raise a warning.
This mechanism makes it easy to share fixtures within applications or even This mechanism makes it easy to share fixtures within applications or even
external applications without the need to create external plugins using external applications without the need to create external plugins using
the ``setuptools``'s entry point technique. the ``setuptools``'s entry point technique.

View File

@ -134,3 +134,70 @@ def test_pytest_catchlog_deprecated(testdir, plugin):
"*pytest-*log plugin has been merged into the core*", "*pytest-*log plugin has been merged into the core*",
"*1 passed, 1 warnings*", "*1 passed, 1 warnings*",
]) ])
def test_pytest_plugins_in_non_top_level_conftest_deprecated(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
subdirectory = testdir.tmpdir.join("subdirectory")
subdirectory.mkdir()
# create the inner conftest with makeconftest and then move it to the subdirectory
testdir.makeconftest("""
pytest_plugins=['capture']
""")
testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py"))
# make the top level conftest
testdir.makeconftest("""
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
""")
testdir.makepyfile("""
def test_func():
pass
""")
res = testdir.runpytest_subprocess()
assert res.ret == 0
res.stderr.fnmatch_lines('*' + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0])
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_top_level_conftest(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
subdirectory = testdir.tmpdir.join('subdirectory')
subdirectory.mkdir()
testdir.makeconftest("""
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
pytest_plugins=['capture']
""")
testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py"))
testdir.makepyfile("""
def test_func():
pass
""")
res = testdir.runpytest_subprocess()
assert res.ret == 0
res.stderr.fnmatch_lines('*' + str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0])
def test_pytest_plugins_in_non_top_level_conftest_deprecated_no_false_positives(testdir):
from _pytest.deprecated import PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST
subdirectory = testdir.tmpdir.join('subdirectory')
subdirectory.mkdir()
testdir.makeconftest("""
pass
""")
testdir.tmpdir.join("conftest.py").move(subdirectory.join("conftest.py"))
testdir.makeconftest("""
import warnings
warnings.filterwarnings('always', category=DeprecationWarning)
pytest_plugins=['capture']
""")
testdir.makepyfile("""
def test_func():
pass
""")
res = testdir.runpytest_subprocess()
assert res.ret == 0
assert str(PYTEST_PLUGINS_FROM_NON_TOP_LEVEL_CONFTEST).splitlines()[0] not in res.stderr.str()