-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
|
for `twisted <http://twistedmatrix.com>`_ apps, starting a reactor and
|
||||||
processing deferreds from test functions.
|
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
|
coverage reporting, compatible with distributed testing
|
||||||
|
|
||||||
* `pytest-xdist <https://pypi.org/project/pytest-xdist/>`_:
|
* `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.
|
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
|
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;
|
# if _PYTEST_SETUP_SKIP_PLUGGY_DEP is set, skip installing pluggy;
|
||||||
# used by tox.ini to test with pluggy master
|
# used by tox.ini to test with pluggy master
|
||||||
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
if "_PYTEST_SETUP_SKIP_PLUGGY_DEP" not in os.environ:
|
||||||
INSTALL_REQUIRES.append("pluggy>=0.7")
|
INSTALL_REQUIRES.append("pluggy>=0.9")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
@ -497,7 +497,7 @@ class PytestPluginManager(PluginManager):
|
||||||
if not name.startswith("pytest_"):
|
if not name.startswith("pytest_"):
|
||||||
self.set_blocked("pytest_" + name)
|
self.set_blocked("pytest_" + name)
|
||||||
else:
|
else:
|
||||||
self.import_plugin(arg)
|
self.import_plugin(arg, consider_entry_points=True)
|
||||||
|
|
||||||
def consider_conftest(self, conftestmodule):
|
def consider_conftest(self, conftestmodule):
|
||||||
self.register(conftestmodule, name=conftestmodule.__file__)
|
self.register(conftestmodule, name=conftestmodule.__file__)
|
||||||
|
@ -513,7 +513,11 @@ class PytestPluginManager(PluginManager):
|
||||||
for import_spec in plugins:
|
for import_spec in plugins:
|
||||||
self.import_plugin(import_spec)
|
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",
|
# most often modname refers to builtin modules, e.g. "pytester",
|
||||||
# "terminal" or "capture". Those plugins are registered under their
|
# "terminal" or "capture". Those plugins are registered under their
|
||||||
# basename for historic purposes but must be imported with the
|
# basename for historic purposes but must be imported with the
|
||||||
|
@ -524,22 +528,26 @@ class PytestPluginManager(PluginManager):
|
||||||
modname = str(modname)
|
modname = str(modname)
|
||||||
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
|
if self.is_blocked(modname) or self.get_plugin(modname) is not None:
|
||||||
return
|
return
|
||||||
if modname in builtin_plugins:
|
|
||||||
importspec = "_pytest." + modname
|
importspec = "_pytest." + modname if modname in builtin_plugins else modname
|
||||||
else:
|
|
||||||
importspec = modname
|
|
||||||
self.rewrite_hook.mark_rewrite(importspec)
|
self.rewrite_hook.mark_rewrite(importspec)
|
||||||
|
|
||||||
|
if consider_entry_points:
|
||||||
|
loaded = self.load_setuptools_entrypoints("pytest11", name=modname)
|
||||||
|
if loaded:
|
||||||
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
__import__(importspec)
|
__import__(importspec)
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
new_exc_type = ImportError
|
|
||||||
new_exc_message = 'Error importing plugin "%s": %s' % (
|
new_exc_message = 'Error importing plugin "%s": %s' % (
|
||||||
modname,
|
modname,
|
||||||
safe_str(e.args[0]),
|
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:
|
except Skipped as e:
|
||||||
from _pytest.warnings import _issue_warning_captured
|
from _pytest.warnings import _issue_warning_captured
|
||||||
|
|
|
@ -60,7 +60,7 @@ def pytest_addoption(parser):
|
||||||
dest="plugins",
|
dest="plugins",
|
||||||
default=[],
|
default=[],
|
||||||
metavar="name",
|
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. "
|
"To avoid loading of plugins, use the `no:` prefix, e.g. "
|
||||||
"`no:doctest`.",
|
"`no:doctest`.",
|
||||||
)
|
)
|
||||||
|
|
|
@ -8,6 +8,7 @@ import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import types
|
import types
|
||||||
|
|
||||||
|
import attr
|
||||||
import py
|
import py
|
||||||
import six
|
import six
|
||||||
|
|
||||||
|
@ -108,6 +109,60 @@ class TestGeneralUsage(object):
|
||||||
assert result.ret == 0
|
assert result.ret == 0
|
||||||
result.stdout.fnmatch_lines(["*1 passed*"])
|
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):
|
def test_assertion_magic(self, testdir):
|
||||||
p = testdir.makepyfile(
|
p = testdir.makepyfile(
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -5,6 +5,8 @@ from __future__ import print_function
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
import attr
|
||||||
|
|
||||||
import _pytest._code
|
import _pytest._code
|
||||||
import pytest
|
import pytest
|
||||||
from _pytest.config import _iter_rewritable_modules
|
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")
|
pkg_resources = pytest.importorskip("pkg_resources")
|
||||||
|
|
||||||
def my_iter(group, name=None):
|
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):
|
class PseudoPlugin(object):
|
||||||
x = 42
|
x = 42
|
||||||
|
|
Loading…
Reference in New Issue