From 75714ee70726e46220cf02b85b3edb2894b6eebd Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Mon, 3 Feb 2020 14:10:54 +0100 Subject: [PATCH] pluginmanager.consider_preparse: add exclude_only kwarg (#6443) Plugins specified with ``-p`` are now loaded after internal plugins, which results in their hooks being called *before* the internal ones. This makes the ``-p`` behavior consistent with ``PYTEST_PLUGINS``. * fix/adjust test_disable_plugin_autoload * adjust test_plugin_loading_order --- changelog/6443.breaking.rst | 3 +++ src/_pytest/config/__init__.py | 8 +++++--- testing/test_config.py | 13 ++++++++++++- testing/test_warnings.py | 5 +---- 4 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 changelog/6443.breaking.rst diff --git a/changelog/6443.breaking.rst b/changelog/6443.breaking.rst new file mode 100644 index 000000000..b39c53014 --- /dev/null +++ b/changelog/6443.breaking.rst @@ -0,0 +1,3 @@ +Plugins specified with ``-p`` are now loaded after internal plugins, which results in their hooks being called *before* the internal ones. + +This makes the ``-p`` behavior consistent with ``PYTEST_PLUGINS``. diff --git a/src/_pytest/config/__init__.py b/src/_pytest/config/__init__.py index 73c35ce63..6fbbf1959 100644 --- a/src/_pytest/config/__init__.py +++ b/src/_pytest/config/__init__.py @@ -193,7 +193,7 @@ def get_config(args=None, plugins=None): if args is not None: # Handle any "-p no:plugin" args. - pluginmanager.consider_preparse(args) + pluginmanager.consider_preparse(args, exclude_only=True) for spec in default_plugins: pluginmanager.import_plugin(spec) @@ -499,7 +499,7 @@ class PytestPluginManager(PluginManager): # # - def consider_preparse(self, args): + def consider_preparse(self, args, *, exclude_only=False): i = 0 n = len(args) while i < n: @@ -516,6 +516,8 @@ class PytestPluginManager(PluginManager): parg = opt[2:] else: continue + if exclude_only and not parg.startswith("no:"): + continue self.consider_pluginarg(parg) def consider_pluginarg(self, arg): @@ -951,7 +953,7 @@ class Config: self._checkversion() self._consider_importhook(args) - self.pluginmanager.consider_preparse(args) + self.pluginmanager.consider_preparse(args, exclude_only=False) if not os.environ.get("PYTEST_DISABLE_PLUGIN_AUTOLOAD"): # Don't autoload from setuptools entry point. Only explicitly specified # plugins are going to be loaded. diff --git a/testing/test_config.py b/testing/test_config.py index cc54e5b23..8a1be0b27 100644 --- a/testing/test_config.py +++ b/testing/test_config.py @@ -659,6 +659,13 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load): class PseudoPlugin: x = 42 + attrs_used = [] + + def __getattr__(self, name): + assert name == "__loader__" + self.attrs_used.append(name) + return object() + def distributions(): return (Distribution(),) @@ -668,6 +675,10 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load): config = testdir.parseconfig(*parse_args) has_loaded = config.pluginmanager.get_plugin("mytestplugin") is not None assert has_loaded == should_load + if should_load: + assert PseudoPlugin.attrs_used == ["__loader__"] + else: + assert PseudoPlugin.attrs_used == [] def test_plugin_loading_order(testdir): @@ -676,7 +687,7 @@ def test_plugin_loading_order(testdir): """ def test_terminal_plugin(request): import myplugin - assert myplugin.terminal_plugin == [True, True] + assert myplugin.terminal_plugin == [False, True] """, **{ "myplugin": """ diff --git a/testing/test_warnings.py b/testing/test_warnings.py index 99b24e332..e4d957385 100644 --- a/testing/test_warnings.py +++ b/testing/test_warnings.py @@ -679,10 +679,7 @@ class TestStackLevel: # with stacklevel=2 the warning should originate from # config.PytestPluginManager.import_plugin is thrown by a skipped plugin - # During config parsing the the pluginargs are checked in a while loop - # that as a result of the argument count runs import_plugin twice, hence - # two identical warnings are captured (is this intentional?). - assert len(capwarn.captured) == 2 + assert len(capwarn.captured) == 1 warning, location = capwarn.captured.pop() file, _, func = location