Merge pull request #3230 from brianmaissy/features
deprecate pytest_plugins in non-top-level conftest
This commit is contained in:
		
						commit
						add5ce0fb8
					
				|  | @ -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()) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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." | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | Defining ``pytest_plugins`` is now deprecated in non-top-level conftest.py files, because they "leak" to the entire directory tree. | ||||||
|  | @ -80,6 +80,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-root conftests>` | ||||||
|  |     in the Writing plugins section. | ||||||
|  | 
 | ||||||
| .. _`findpluginname`: | .. _`findpluginname`: | ||||||
| 
 | 
 | ||||||
| Finding out which plugins are active | Finding out which plugins are active | ||||||
|  |  | ||||||
|  | @ -257,6 +257,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-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 | 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. | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue