-p option now can be used to early-load plugins by entry-point name
Fixes #4718
This commit is contained in:
parent
759d7fde5d
commit
a0207274f4
|
@ -0,0 +1,6 @@
|
|||
The ``-p`` option can now be used to early-load plugins also by entry-point name, instead of just
|
||||
by module name.
|
||||
|
||||
This makes it possible to early load external plugins like ``pytest-cov`` in the command-line::
|
||||
|
||||
pytest -p pytest_cov
|
|
@ -0,0 +1 @@
|
|||
``pluggy>=0.9`` is now required.
|
|
@ -27,7 +27,7 @@ Here is a little annotated list for some popular plugins:
|
|||
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||
processing deferreds from test functions.
|
||||
|
||||
* `pytest-cov <https://pypi.org/project/pytest-cov/>`_:
|
||||
* `pytest-cov <https://pypi.org/project/pytest-cov/>`__:
|
||||
coverage reporting, compatible with distributed testing
|
||||
|
||||
* `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_:
|
||||
|
|
|
@ -680,6 +680,22 @@ for example ``-x`` if you only want to send one particular failure.
|
|||
|
||||
Currently only pasting to the http://bpaste.net service is implemented.
|
||||
|
||||
Early loading plugins
|
||||
---------------------
|
||||
|
||||
You can early-load plugins (internal and external) explicitly in the command-line with the ``-p`` option::
|
||||
|
||||
pytest -p mypluginmodule
|
||||
|
||||
The option receives a ``name`` parameter, which can be:
|
||||
|
||||
* A full module dotted name, for example ``myproject.plugins``. This dotted name must be importable.
|
||||
* The entry-point name of a plugin. This is the name passed to ``setuptools`` when the plugin is
|
||||
registered. For example to early-load the `pytest-cov <https://pypi.org/project/pytest-cov/>`__ plugin you can use::
|
||||
|
||||
pytest -p pytest_cov
|
||||
|
||||
|
||||
Disabling plugins
|
||||
-----------------
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -22,7 +22,7 @@ INSTALL_REQUIRES = [
|
|||
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||
# used by tox.ini to test with pluggy master
|
||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||
INSTALL_REQUIRES.append("pluggy>=0.7")
|
||||
INSTALL_REQUIRES.append("pluggy>=0.9")
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -497,7 +497,7 @@ class PytestPluginManager(PluginManager):
|
|||
if not name.startswith("pytest_"):
|
||||
self.set_blocked("pytest_" + name)
|
||||
else:
|
||||
self.import_plugin(arg)
|
||||
self.import_plugin(arg, consider_entry_points=True)
|
||||
|
||||
def consider_conftest(self, conftestmodule):
|
||||
self.register(conftestmodule, name=conftestmodule.__file__)
|
||||
|
@ -513,7 +513,11 @@ class PytestPluginManager(PluginManager):
|
|||
for import_spec in plugins:
|
||||
self.import_plugin(import_spec)
|
||||
|
||||
def import_plugin(self, modname):
|
||||
def import_plugin(self, modname, consider_entry_points=False):
|
||||
"""
|
||||
Imports a plugin with ``modname``. If ``consider_entry_points`` is True, entry point
|
||||
names are also considered to find a plugin.
|
||||
"""
|
||||
# most often modname refers to builtin modules, e.g. "pytester",
|
||||
# "terminal" or "capture". Those plugins are registered under their
|
||||
# basename for historic purposes but must be imported with the
|
||||
|
@ -524,22 +528,26 @@ class PytestPluginManager(PluginManager):
|
|||
modname = str(modname)
|
||||
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
|
||||
return
|
||||
if modname in builtin_plugins:
|
||||
importspec = "_pytest." + modname
|
||||
else:
|
||||
importspec = modname
|
||||
|
||||
importspec = "_pytest." + modname if modname in builtin_plugins else modname
|
||||
self.rewrite_hook.mark_rewrite(importspec)
|
||||
|
||||
if consider_entry_points:
|
||||
loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
|
||||
if loaded:
|
||||
return
|
||||
|
||||
try:
|
||||
__import__(importspec)
|
||||
except ImportError as e:
|
||||
new_exc_type = ImportError
|
||||
new_exc_message = 'Error importing plugin "%s": %s' % (
|
||||
modname,
|
||||
safe_str(e.args[0]),
|
||||
)
|
||||
new_exc = new_exc_type(new_exc_message)
|
||||
new_exc = ImportError(new_exc_message)
|
||||
tb = sys.exc_info()[2]
|
||||
|
||||
six.reraise(new_exc_type, new_exc, sys.exc_info()[2])
|
||||
six.reraise(ImportError, new_exc, tb)
|
||||
|
||||
except Skipped as e:
|
||||
from _pytest.warnings import _issue_warning_captured
|
||||
|
|
|
@ -60,7 +60,7 @@ def pytest_addoption(parser):
|
|||
dest="plugins",
|
||||
default=[],
|
||||
metavar="name",
|
||||
help="early-load given plugin (multi-allowed). "
|
||||
help="early-load given plugin module name or entry point (multi-allowed). "
|
||||
"To avoid loading of plugins, use the `no:` prefix, e.g. "
|
||||
"`no:doctest`.",
|
||||
)
|
||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
|||
import textwrap
|
||||
import types
|
||||
|
||||
import attr
|
||||
import py
|
||||
import six
|
||||
|
||||
|
@ -108,6 +109,60 @@ class TestGeneralUsage(object):
|
|||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
||||
|
||||
@pytest.mark.parametrize("load_cov_early", [True, False])
|
||||
def test_early_load_setuptools_name(self, testdir, monkeypatch, load_cov_early):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
testdir.makepyfile(mytestplugin1_module="")
|
||||
testdir.makepyfile(mytestplugin2_module="")
|
||||
testdir.makepyfile(mycov_module="")
|
||||
testdir.syspathinsert()
|
||||
|
||||
loaded = []
|
||||
|
||||
@attr.s
|
||||
class DummyEntryPoint(object):
|
||||
name = attr.ib()
|
||||
module = attr.ib()
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
|
||||
def load(self):
|
||||
__import__(self.module)
|
||||
loaded.append(self.name)
|
||||
return sys.modules[self.module]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
|
||||
entry_points = [
|
||||
DummyEntryPoint("myplugin1", "mytestplugin1_module"),
|
||||
DummyEntryPoint("myplugin2", "mytestplugin2_module"),
|
||||
DummyEntryPoint("mycov", "mycov_module"),
|
||||
]
|
||||
|
||||
def my_iter(group, name=None):
|
||||
assert group == "pytest11"
|
||||
for ep in entry_points:
|
||||
if name is not None and ep.name != name:
|
||||
continue
|
||||
yield ep
|
||||
|
||||
monkeypatch.setattr(pkg_resources, "iter_entry_points", my_iter)
|
||||
params = ("-p", "mycov") if load_cov_early else ()
|
||||
testdir.runpytest_inprocess(*params)
|
||||
if load_cov_early:
|
||||
assert loaded == ["mycov", "myplugin1", "myplugin2"]
|
||||
else:
|
||||
assert loaded == ["myplugin1", "myplugin2", "mycov"]
|
||||
|
||||
def test_assertion_magic(self, testdir):
|
||||
p = testdir.makepyfile(
|
||||
"""
|
||||
|
|
|
@ -5,6 +5,8 @@ from __future__ import print_function
|
|||
import sys
|
||||
import textwrap
|
||||
|
||||
import attr
|
||||
|
||||
import _pytest._code
|
||||
import pytest
|
||||
from _pytest.config import _iter_rewritable_modules
|
||||
|
@ -622,7 +624,28 @@ def test_disable_plugin_autoload(testdir, monkeypatch, parse_args, should_load):
|
|||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
|
||||
def my_iter(group, name=None):
|
||||
raise AssertionError("Should not be called")
|
||||
assert group == "pytest11"
|
||||
assert name == "mytestplugin"
|
||||
return iter([DummyEntryPoint()])
|
||||
|
||||
@attr.s
|
||||
class DummyEntryPoint(object):
|
||||
name = "mytestplugin"
|
||||
version = "1.0"
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.name
|
||||
|
||||
def load(self):
|
||||
return sys.modules[self.name]
|
||||
|
||||
@property
|
||||
def dist(self):
|
||||
return self
|
||||
|
||||
def _get_metadata(self, *args):
|
||||
return []
|
||||
|
||||
class PseudoPlugin(object):
|
||||
x = 42
|
||||
|
|
Loading…
Reference in New Issue