Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

deprecate pytest_plugins in non-top-level conftest #3230

Merged
merged 2 commits into from
Mar 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-root 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-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.
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()