Merge pull request #3230 from brianmaissy/features

deprecate pytest_plugins in non-top-level conftest
This commit is contained in:
Ronny Pfannschmidt 2018-03-21 07:44:34 +01:00 committed by GitHub
commit add5ce0fb8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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.
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):
"""
@ -276,6 +278,7 @@ class PytestPluginManager(PluginManager):
config.addinivalue_line("markers",
"trylast: mark a hook implementation function such that the "
"plugin machinery will try to call it last/as late as possible.")
self._configured = True
def _warn(self, message):
kwargs = message if isinstance(message, dict) else {
@ -366,6 +369,9 @@ class PytestPluginManager(PluginManager):
_ensure_removed_sysmodule(conftestpath.purebasename)
try:
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:
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"
"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

@ -80,6 +80,12 @@ will be loaded as well.
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-root conftests>`
in the Writing plugins section.
.. _`findpluginname`:
Finding out which plugins are active

View File

@ -257,6 +257,18 @@ application modules:
if ``myapp.testsupport.myplugin`` also declares ``pytest_plugins``, the contents
of the variable will also be loaded as plugins, and so on.
.. _`requiring plugins in non-root 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
external applications without the need to create external plugins using
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*",
"*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()