Skip to content

Commit

Permalink
deprecated pytest_plugins in non-top-level conftest
Browse files Browse the repository at this point in the history
  • Loading branch information
brianmaissy committed Mar 10, 2018
1 parent d6ddeb3 commit 54b15f5
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 0 deletions.
6 changes: 6 additions & 0 deletions _pytest/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ def __init__(self):

# 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):
"""
Expand Down Expand Up @@ -276,6 +278,7 @@ def pytest_configure(self, config):
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 {
Expand Down Expand Up @@ -366,6 +369,9 @@ def _importconftest(self, conftestpath):
_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())

Expand Down
6 changes: 6 additions & 0 deletions _pytest/deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,9 @@ class RemovedInPytest4Warning(DeprecationWarning):
"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 change: 1 addition & 0 deletions changelog/3084.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py files, because they "leak" to the entire directory tree.
6 changes: 6 additions & 0 deletions doc/en/plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,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-noot conftests>`
in the Writing plugins section.

.. _`findpluginname`:

Finding out which plugins are active
Expand Down
12 changes: 12 additions & 0 deletions doc/en/writing_plugins.rst
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,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-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
external applications without the need to create external plugins using
the ``setuptools``'s entry point technique.
Expand Down
67 changes: 67 additions & 0 deletions testing/deprecated_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,70 @@ def test_func(pytestconfig):
"*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()

0 comments on commit 54b15f5

Please sign in to comment.