minimize HookRelay to become a pure container, refactor initialization and
tests of plugin management to be a bit better split between pytest and pytest-independent bits --HG-- branch : plugin_no_pytest
This commit is contained in:
parent
db650de372
commit
1ef49ac5ab
|
@ -87,9 +87,17 @@ def _prepareconfig(args=None, plugins=None):
|
|||
pluginmanager.ensure_shutdown()
|
||||
raise
|
||||
|
||||
def exclude_pytest_names(name):
|
||||
return not name.startswith(name) or name == "pytest_plugins" or \
|
||||
name.startswith("pytest_funcarg__")
|
||||
|
||||
class PytestPluginManager(PluginManager):
|
||||
def __init__(self, hookspecs=[hookspec]):
|
||||
super(PytestPluginManager, self).__init__(hookspecs=hookspecs)
|
||||
def __init__(self):
|
||||
super(PytestPluginManager, self).__init__(prefix="pytest_",
|
||||
excludefunc=exclude_pytest_names)
|
||||
self._warnings = []
|
||||
self._plugin_distinfo = []
|
||||
self.addhooks(hookspec)
|
||||
self.register(self)
|
||||
if os.environ.get('PYTEST_DEBUG'):
|
||||
err = sys.stderr
|
||||
|
@ -100,6 +108,14 @@ class PytestPluginManager(PluginManager):
|
|||
pass
|
||||
self.set_tracing(err.write)
|
||||
|
||||
def getplugin(self, name):
|
||||
if name is None:
|
||||
return name
|
||||
plugin = super(PytestPluginManager, self).getplugin(name)
|
||||
if plugin is None:
|
||||
plugin = super(PytestPluginManager, self).getplugin("_pytest." + name)
|
||||
return plugin
|
||||
|
||||
def pytest_configure(self, config):
|
||||
config.addinivalue_line("markers",
|
||||
"tryfirst: mark a hook implementation function such that the "
|
||||
|
@ -110,6 +126,89 @@ class PytestPluginManager(PluginManager):
|
|||
for warning in self._warnings:
|
||||
config.warn(code="I1", message=warning)
|
||||
|
||||
#
|
||||
# API for bootstrapping plugin loading
|
||||
#
|
||||
#
|
||||
def _envlist(self, varname):
|
||||
val = os.environ.get(varname, None)
|
||||
if val is not None:
|
||||
return val.split(',')
|
||||
return ()
|
||||
|
||||
def consider_env(self):
|
||||
for spec in self._envlist("PYTEST_PLUGINS"):
|
||||
self.import_plugin(spec)
|
||||
|
||||
def consider_setuptools_entrypoints(self):
|
||||
try:
|
||||
from pkg_resources import iter_entry_points, DistributionNotFound
|
||||
except ImportError:
|
||||
return # XXX issue a warning
|
||||
for ep in iter_entry_points('pytest11'):
|
||||
name = ep.name
|
||||
if name.startswith("pytest_"):
|
||||
name = name[7:]
|
||||
if ep.name in self._name2plugin or name in self._name2plugin:
|
||||
continue
|
||||
try:
|
||||
plugin = ep.load()
|
||||
except DistributionNotFound:
|
||||
continue
|
||||
self._plugin_distinfo.append((ep.dist, plugin))
|
||||
self.register(plugin, name=name)
|
||||
|
||||
def consider_preparse(self, args):
|
||||
for opt1,opt2 in zip(args, args[1:]):
|
||||
if opt1 == "-p":
|
||||
self.consider_pluginarg(opt2)
|
||||
|
||||
def consider_pluginarg(self, arg):
|
||||
if arg.startswith("no:"):
|
||||
name = arg[3:]
|
||||
plugin = self.getplugin(name)
|
||||
if plugin is not None:
|
||||
self.unregister(plugin)
|
||||
self._name2plugin[name] = -1
|
||||
else:
|
||||
if self.getplugin(arg) is None:
|
||||
self.import_plugin(arg)
|
||||
|
||||
def consider_conftest(self, conftestmodule):
|
||||
if self.register(conftestmodule, name=conftestmodule.__file__,
|
||||
conftest=True):
|
||||
self.consider_module(conftestmodule)
|
||||
|
||||
def consider_module(self, mod):
|
||||
attr = getattr(mod, "pytest_plugins", ())
|
||||
if attr:
|
||||
if not isinstance(attr, (list, tuple)):
|
||||
attr = (attr,)
|
||||
for spec in attr:
|
||||
self.import_plugin(spec)
|
||||
|
||||
def import_plugin(self, modname):
|
||||
assert isinstance(modname, str)
|
||||
if self.getplugin(modname) is not None:
|
||||
return
|
||||
try:
|
||||
mod = importplugin(modname)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except ImportError:
|
||||
if modname.startswith("pytest_"):
|
||||
return self.import_plugin(modname[7:])
|
||||
raise
|
||||
except:
|
||||
e = sys.exc_info()[1]
|
||||
import pytest
|
||||
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
|
||||
raise
|
||||
self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
|
||||
else:
|
||||
self.register(mod, modname)
|
||||
self.consider_module(mod)
|
||||
|
||||
|
||||
class Parser:
|
||||
""" Parser for command line arguments and ini-file values. """
|
||||
|
@ -933,3 +1032,15 @@ def setns(obj, dic):
|
|||
#if obj != pytest:
|
||||
# pytest.__all__.append(name)
|
||||
setattr(pytest, name, value)
|
||||
|
||||
|
||||
def importplugin(importspec):
|
||||
name = importspec
|
||||
try:
|
||||
mod = "_pytest." + name
|
||||
__import__(mod)
|
||||
return sys.modules[mod]
|
||||
except ImportError:
|
||||
__import__(importspec)
|
||||
return sys.modules[importspec]
|
||||
|
||||
|
|
227
_pytest/core.py
227
_pytest/core.py
|
@ -1,12 +1,10 @@
|
|||
"""
|
||||
pytest PluginManager, basic initialization and tracing.
|
||||
PluginManager, basic initialization and tracing.
|
||||
"""
|
||||
import os
|
||||
import sys
|
||||
import inspect
|
||||
import py
|
||||
# don't import pytest to avoid circular imports
|
||||
|
||||
|
||||
py3 = sys.version_info > (3,0)
|
||||
|
||||
|
@ -137,16 +135,16 @@ class CallOutcome:
|
|||
|
||||
|
||||
class PluginManager(object):
|
||||
def __init__(self, hookspecs=None, prefix="pytest_"):
|
||||
def __init__(self, prefix, excludefunc=None):
|
||||
self._prefix = prefix
|
||||
self._excludefunc = excludefunc
|
||||
self._name2plugin = {}
|
||||
self._plugins = []
|
||||
self._conftestplugins = []
|
||||
self._plugin2hookcallers = {}
|
||||
self._warnings = []
|
||||
self.trace = TagTracer().get("pluginmanage")
|
||||
self._plugin_distinfo = []
|
||||
self._shutdown = []
|
||||
self.hook = HookRelay(hookspecs or [], pm=self, prefix=prefix)
|
||||
self.hook = HookRelay(pm=self)
|
||||
|
||||
def set_tracing(self, writer):
|
||||
self.trace.root.setwriter(writer)
|
||||
|
@ -174,6 +172,39 @@ class PluginManager(object):
|
|||
assert not hasattr(self, "_registercallback")
|
||||
self._registercallback = callback
|
||||
|
||||
def make_hook_caller(self, name, plugins):
|
||||
caller = getattr(self.hook, name)
|
||||
methods = self.listattr(name, plugins=plugins)
|
||||
if methods:
|
||||
return HookCaller(self.hook, caller.name, caller.firstresult,
|
||||
argnames=caller.argnames, methods=methods)
|
||||
return caller
|
||||
|
||||
def _scan_plugin(self, plugin):
|
||||
def fail(msg, *args):
|
||||
name = getattr(plugin, '__name__', plugin)
|
||||
raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
|
||||
|
||||
for name in dir(plugin):
|
||||
if name[0] == "_" or not name.startswith(self._prefix):
|
||||
continue
|
||||
hook = getattr(self.hook, name, None)
|
||||
method = getattr(plugin, name)
|
||||
if hook is None:
|
||||
if self._excludefunc is not None and self._excludefunc(name):
|
||||
continue
|
||||
if getattr(method, 'optionalhook', False):
|
||||
continue
|
||||
fail("found unknown hook: %r", name)
|
||||
for arg in varnames(method):
|
||||
if arg not in hook.argnames:
|
||||
fail("argument %r not available\n"
|
||||
"actual definition: %s\n"
|
||||
"available hookargs: %s",
|
||||
arg, formatdef(method),
|
||||
", ".join(hook.argnames))
|
||||
yield hook
|
||||
|
||||
def register(self, plugin, name=None, prepend=False, conftest=False):
|
||||
if self._name2plugin.get(name, None) == -1:
|
||||
return
|
||||
|
@ -185,7 +216,7 @@ class PluginManager(object):
|
|||
reg = getattr(self, "_registercallback", None)
|
||||
if reg is not None:
|
||||
reg(plugin, name) # may call addhooks
|
||||
hookcallers = list(self.hook._scan_plugin(plugin))
|
||||
hookcallers = list(self._scan_plugin(plugin))
|
||||
self._plugin2hookcallers[plugin] = hookcallers
|
||||
self._name2plugin[name] = plugin
|
||||
if conftest:
|
||||
|
@ -227,108 +258,29 @@ class PluginManager(object):
|
|||
return True
|
||||
return plugin in self._plugins or plugin in self._conftestplugins
|
||||
|
||||
def addhooks(self, spec, prefix="pytest_"):
|
||||
self.hook._addhooks(spec, prefix=prefix)
|
||||
def addhooks(self, module_or_class):
|
||||
isclass = int(inspect.isclass(module_or_class))
|
||||
names = []
|
||||
for name in dir(module_or_class):
|
||||
if name.startswith(self._prefix):
|
||||
method = module_or_class.__dict__[name]
|
||||
firstresult = getattr(method, 'firstresult', False)
|
||||
hc = HookCaller(self.hook, name, firstresult=firstresult,
|
||||
argnames=varnames(method, startindex=isclass))
|
||||
setattr(self.hook, name, hc)
|
||||
names.append(name)
|
||||
if not names:
|
||||
raise ValueError("did not find new %r hooks in %r"
|
||||
%(self._prefix, module_or_class))
|
||||
|
||||
def getplugins(self):
|
||||
return self._plugins + self._conftestplugins
|
||||
|
||||
def skipifmissing(self, name):
|
||||
if not self.hasplugin(name):
|
||||
import pytest
|
||||
pytest.skip("plugin %r is missing" % name)
|
||||
|
||||
def hasplugin(self, name):
|
||||
return bool(self.getplugin(name))
|
||||
|
||||
def getplugin(self, name):
|
||||
if name is None:
|
||||
return None
|
||||
try:
|
||||
return self._name2plugin[name]
|
||||
except KeyError:
|
||||
return self._name2plugin.get("_pytest." + name, None)
|
||||
|
||||
# API for bootstrapping
|
||||
#
|
||||
def _envlist(self, varname):
|
||||
val = os.environ.get(varname, None)
|
||||
if val is not None:
|
||||
return val.split(',')
|
||||
return ()
|
||||
|
||||
def consider_env(self):
|
||||
for spec in self._envlist("PYTEST_PLUGINS"):
|
||||
self.import_plugin(spec)
|
||||
|
||||
def consider_setuptools_entrypoints(self):
|
||||
try:
|
||||
from pkg_resources import iter_entry_points, DistributionNotFound
|
||||
except ImportError:
|
||||
return # XXX issue a warning
|
||||
for ep in iter_entry_points('pytest11'):
|
||||
name = ep.name
|
||||
if name.startswith("pytest_"):
|
||||
name = name[7:]
|
||||
if ep.name in self._name2plugin or name in self._name2plugin:
|
||||
continue
|
||||
try:
|
||||
plugin = ep.load()
|
||||
except DistributionNotFound:
|
||||
continue
|
||||
self._plugin_distinfo.append((ep.dist, plugin))
|
||||
self.register(plugin, name=name)
|
||||
|
||||
def consider_preparse(self, args):
|
||||
for opt1,opt2 in zip(args, args[1:]):
|
||||
if opt1 == "-p":
|
||||
self.consider_pluginarg(opt2)
|
||||
|
||||
def consider_pluginarg(self, arg):
|
||||
if arg.startswith("no:"):
|
||||
name = arg[3:]
|
||||
plugin = self.getplugin(name)
|
||||
if plugin is not None:
|
||||
self.unregister(plugin)
|
||||
self._name2plugin[name] = -1
|
||||
else:
|
||||
if self.getplugin(arg) is None:
|
||||
self.import_plugin(arg)
|
||||
|
||||
def consider_conftest(self, conftestmodule):
|
||||
if self.register(conftestmodule, name=conftestmodule.__file__,
|
||||
conftest=True):
|
||||
self.consider_module(conftestmodule)
|
||||
|
||||
def consider_module(self, mod):
|
||||
attr = getattr(mod, "pytest_plugins", ())
|
||||
if attr:
|
||||
if not isinstance(attr, (list, tuple)):
|
||||
attr = (attr,)
|
||||
for spec in attr:
|
||||
self.import_plugin(spec)
|
||||
|
||||
def import_plugin(self, modname):
|
||||
assert isinstance(modname, str)
|
||||
if self.getplugin(modname) is not None:
|
||||
return
|
||||
try:
|
||||
mod = importplugin(modname)
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except ImportError:
|
||||
if modname.startswith("pytest_"):
|
||||
return self.import_plugin(modname[7:])
|
||||
raise
|
||||
except:
|
||||
e = sys.exc_info()[1]
|
||||
import pytest
|
||||
if not hasattr(pytest, 'skip') or not isinstance(e, pytest.skip.Exception):
|
||||
raise
|
||||
self._warnings.append("skipped plugin %r: %s" %((modname, e.msg)))
|
||||
else:
|
||||
self.register(mod, modname)
|
||||
self.consider_module(mod)
|
||||
return self._name2plugin.get(name)
|
||||
|
||||
def listattr(self, attrname, plugins=None):
|
||||
if plugins is None:
|
||||
|
@ -358,16 +310,6 @@ class PluginManager(object):
|
|||
kwargs=kwargs, firstresult=True).execute()
|
||||
|
||||
|
||||
def importplugin(importspec):
|
||||
name = importspec
|
||||
try:
|
||||
mod = "_pytest." + name
|
||||
__import__(mod)
|
||||
return sys.modules[mod]
|
||||
except ImportError:
|
||||
__import__(importspec)
|
||||
return sys.modules[importspec]
|
||||
|
||||
class MultiCall:
|
||||
""" execute a call into multiple python functions/methods. """
|
||||
|
||||
|
@ -439,60 +381,9 @@ def varnames(func, startindex=None):
|
|||
|
||||
|
||||
class HookRelay:
|
||||
def __init__(self, hookspecs, pm, prefix="pytest_"):
|
||||
if not isinstance(hookspecs, list):
|
||||
hookspecs = [hookspecs]
|
||||
def __init__(self, pm):
|
||||
self._pm = pm
|
||||
self.trace = pm.trace.root.get("hook")
|
||||
self.prefix = prefix
|
||||
for hookspec in hookspecs:
|
||||
self._addhooks(hookspec, prefix)
|
||||
|
||||
def _addhooks(self, hookspec, prefix):
|
||||
added = False
|
||||
isclass = int(inspect.isclass(hookspec))
|
||||
for name, method in vars(hookspec).items():
|
||||
if name.startswith(prefix):
|
||||
firstresult = getattr(method, 'firstresult', False)
|
||||
hc = HookCaller(self, name, firstresult=firstresult,
|
||||
argnames=varnames(method, startindex=isclass))
|
||||
setattr(self, name, hc)
|
||||
added = True
|
||||
#print ("setting new hook", name)
|
||||
if not added:
|
||||
raise ValueError("did not find new %r hooks in %r" %(
|
||||
prefix, hookspec,))
|
||||
|
||||
def _getcaller(self, name, plugins):
|
||||
caller = getattr(self, name)
|
||||
methods = self._pm.listattr(name, plugins=plugins)
|
||||
if methods:
|
||||
return caller.new_cached_caller(methods)
|
||||
return caller
|
||||
|
||||
def _scan_plugin(self, plugin):
|
||||
def fail(msg, *args):
|
||||
name = getattr(plugin, '__name__', plugin)
|
||||
raise PluginValidationError("plugin %r\n%s" %(name, msg % args))
|
||||
|
||||
for name in dir(plugin):
|
||||
if not name.startswith(self.prefix):
|
||||
continue
|
||||
hook = getattr(self, name, None)
|
||||
method = getattr(plugin, name)
|
||||
if hook is None:
|
||||
is_optional = getattr(method, 'optionalhook', False)
|
||||
if not isgenerichook(name) and not is_optional:
|
||||
fail("found unknown hook: %r", name)
|
||||
continue
|
||||
for arg in varnames(method):
|
||||
if arg not in hook.argnames:
|
||||
fail("argument %r not available\n"
|
||||
"actual definition: %s\n"
|
||||
"available hookargs: %s",
|
||||
arg, formatdef(method),
|
||||
", ".join(hook.argnames))
|
||||
yield hook
|
||||
|
||||
|
||||
class HookCaller:
|
||||
|
@ -505,10 +396,6 @@ class HookCaller:
|
|||
assert "self" not in argnames # sanity check
|
||||
self.methods = methods
|
||||
|
||||
def new_cached_caller(self, methods):
|
||||
return HookCaller(self.hookrelay, self.name, self.firstresult,
|
||||
argnames=self.argnames, methods=methods)
|
||||
|
||||
def __repr__(self):
|
||||
return "<HookCaller %r>" %(self.name,)
|
||||
|
||||
|
@ -529,13 +416,9 @@ class HookCaller:
|
|||
class PluginValidationError(Exception):
|
||||
""" plugin failed validation. """
|
||||
|
||||
def isgenerichook(name):
|
||||
return name == "pytest_plugins" or \
|
||||
name.startswith("pytest_funcarg__")
|
||||
|
||||
def formatdef(func):
|
||||
return "%s%s" % (
|
||||
func.__name__,
|
||||
inspect.formatargspec(*inspect.getargspec(func))
|
||||
)
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
def pytest_addhooks(pluginmanager):
|
||||
"""called at plugin load time to allow adding new hooks via a call to
|
||||
pluginmanager.registerhooks(module)."""
|
||||
pluginmanager.addhooks(module_or_class, prefix)."""
|
||||
|
||||
|
||||
def pytest_namespace():
|
||||
|
|
|
@ -160,7 +160,7 @@ class FSHookProxy(object):
|
|||
|
||||
def __getattr__(self, name):
|
||||
plugins = self.config._getmatchingplugins(self.fspath)
|
||||
x = self.config.hook._getcaller(name, plugins)
|
||||
x = self.config.pluginmanager.make_hook_caller(name, plugins)
|
||||
self.__dict__[name] = x
|
||||
return x
|
||||
|
||||
|
|
|
@ -1,236 +1,62 @@
|
|||
import pytest, py, os
|
||||
from _pytest.core import * # noqa
|
||||
from _pytest.config import get_plugin_manager
|
||||
from _pytest.config import get_plugin_manager, importplugin
|
||||
|
||||
|
||||
class TestBootstrapping:
|
||||
def test_consider_env_fails_to_import(self, monkeypatch):
|
||||
pluginmanager = PluginManager()
|
||||
monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
|
||||
pytest.raises(ImportError, lambda: pluginmanager.consider_env())
|
||||
@pytest.fixture
|
||||
def pm():
|
||||
return PluginManager("he")
|
||||
|
||||
def test_preparse_args(self):
|
||||
pluginmanager = PluginManager()
|
||||
pytest.raises(ImportError, lambda:
|
||||
pluginmanager.consider_preparse(["xyz", "-p", "hello123"]))
|
||||
@pytest.fixture
|
||||
def pytestpm():
|
||||
return PytestPluginManager()
|
||||
|
||||
def test_plugin_prevent_register(self):
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
|
||||
l1 = pluginmanager.getplugins()
|
||||
pluginmanager.register(42, name="abc")
|
||||
l2 = pluginmanager.getplugins()
|
||||
assert len(l2) == len(l1)
|
||||
|
||||
def test_plugin_prevent_register_unregistered_alredy_registered(self):
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager.register(42, name="abc")
|
||||
l1 = pluginmanager.getplugins()
|
||||
assert 42 in l1
|
||||
pluginmanager.consider_preparse(["xyz", "-p", "no:abc"])
|
||||
l2 = pluginmanager.getplugins()
|
||||
assert 42 not in l2
|
||||
|
||||
def test_plugin_double_register(self):
|
||||
pm = PluginManager()
|
||||
class TestPluginManager:
|
||||
def test_plugin_double_register(self, pm):
|
||||
pm.register(42, name="abc")
|
||||
with pytest.raises(ValueError):
|
||||
pm.register(42, name="abc")
|
||||
pytest.raises(ValueError, lambda: pm.register(42, name="abc"))
|
||||
|
||||
def test_plugin_skip(self, testdir, monkeypatch):
|
||||
p = testdir.makepyfile(skipping1="""
|
||||
import pytest
|
||||
pytest.skip("hello")
|
||||
""")
|
||||
p.copy(p.dirpath("skipping2.py"))
|
||||
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
|
||||
result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"WI1*skipped plugin*skipping1*hello*",
|
||||
"WI1*skipped plugin*skipping2*hello*",
|
||||
])
|
||||
|
||||
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch):
|
||||
pluginmanager = PluginManager()
|
||||
testdir.syspathinsert()
|
||||
testdir.makepyfile(xy123="#")
|
||||
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
|
||||
l1 = len(pluginmanager.getplugins())
|
||||
pluginmanager.consider_env()
|
||||
l2 = len(pluginmanager.getplugins())
|
||||
assert l2 == l1 + 1
|
||||
assert pluginmanager.getplugin('xy123')
|
||||
pluginmanager.consider_env()
|
||||
l3 = len(pluginmanager.getplugins())
|
||||
assert l2 == l3
|
||||
|
||||
def test_consider_setuptools_instantiation(self, monkeypatch):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
def my_iter(name):
|
||||
assert name == "pytest11"
|
||||
class EntryPoint:
|
||||
name = "pytest_mytestplugin"
|
||||
dist = None
|
||||
def load(self):
|
||||
class PseudoPlugin:
|
||||
x = 42
|
||||
return PseudoPlugin()
|
||||
return iter([EntryPoint()])
|
||||
|
||||
monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager.consider_setuptools_entrypoints()
|
||||
plugin = pluginmanager.getplugin("mytestplugin")
|
||||
assert plugin.x == 42
|
||||
|
||||
def test_consider_setuptools_not_installed(self, monkeypatch):
|
||||
monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
|
||||
py.std.types.ModuleType("pkg_resources"))
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager.consider_setuptools_entrypoints()
|
||||
# ok, we did not explode
|
||||
|
||||
def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
|
||||
testdir.makepyfile(pytest_x500="#")
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_hello(pytestconfig):
|
||||
plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
|
||||
assert plugin is not None
|
||||
""")
|
||||
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
|
||||
def test_import_plugin_importname(self, testdir):
|
||||
pluginmanager = PluginManager()
|
||||
pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwx.y")')
|
||||
|
||||
testdir.syspathinsert()
|
||||
pluginname = "pytest_hello"
|
||||
testdir.makepyfile(**{pluginname: ""})
|
||||
pluginmanager.import_plugin("pytest_hello")
|
||||
len1 = len(pluginmanager.getplugins())
|
||||
pluginmanager.import_plugin("pytest_hello")
|
||||
len2 = len(pluginmanager.getplugins())
|
||||
assert len1 == len2
|
||||
plugin1 = pluginmanager.getplugin("pytest_hello")
|
||||
assert plugin1.__name__.endswith('pytest_hello')
|
||||
plugin2 = pluginmanager.getplugin("pytest_hello")
|
||||
assert plugin2 is plugin1
|
||||
|
||||
def test_import_plugin_dotted_name(self, testdir):
|
||||
pluginmanager = PluginManager()
|
||||
pytest.raises(ImportError, 'pluginmanager.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pluginmanager.import_plugin("pytest_qweqwex.y")')
|
||||
|
||||
testdir.syspathinsert()
|
||||
testdir.mkpydir("pkg").join("plug.py").write("x=3")
|
||||
pluginname = "pkg.plug"
|
||||
pluginmanager.import_plugin(pluginname)
|
||||
mod = pluginmanager.getplugin("pkg.plug")
|
||||
assert mod.x == 3
|
||||
|
||||
def test_consider_module(self, testdir):
|
||||
pluginmanager = PluginManager()
|
||||
testdir.syspathinsert()
|
||||
testdir.makepyfile(pytest_p1="#")
|
||||
testdir.makepyfile(pytest_p2="#")
|
||||
mod = py.std.types.ModuleType("temp")
|
||||
mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
|
||||
pluginmanager.consider_module(mod)
|
||||
assert pluginmanager.getplugin("pytest_p1").__name__ == "pytest_p1"
|
||||
assert pluginmanager.getplugin("pytest_p2").__name__ == "pytest_p2"
|
||||
|
||||
def test_consider_module_import_module(self, testdir):
|
||||
mod = py.std.types.ModuleType("x")
|
||||
mod.pytest_plugins = "pytest_a"
|
||||
aplugin = testdir.makepyfile(pytest_a="#")
|
||||
pluginmanager = get_plugin_manager()
|
||||
reprec = testdir.make_hook_recorder(pluginmanager)
|
||||
#syspath.prepend(aplugin.dirpath())
|
||||
py.std.sys.path.insert(0, str(aplugin.dirpath()))
|
||||
pluginmanager.consider_module(mod)
|
||||
call = reprec.getcall(pluginmanager.hook.pytest_plugin_registered.name)
|
||||
assert call.plugin.__name__ == "pytest_a"
|
||||
|
||||
# check that it is not registered twice
|
||||
pluginmanager.consider_module(mod)
|
||||
l = reprec.getcalls("pytest_plugin_registered")
|
||||
assert len(l) == 1
|
||||
|
||||
def test_config_sets_conftesthandle_onimport(self, testdir):
|
||||
config = testdir.parseconfig([])
|
||||
assert config._conftest._onimport == config._onimportconftest
|
||||
|
||||
def test_consider_conftest_deps(self, testdir):
|
||||
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
|
||||
pp = PluginManager()
|
||||
pytest.raises(ImportError, lambda: pp.consider_conftest(mod))
|
||||
|
||||
def test_pm(self):
|
||||
pp = PluginManager()
|
||||
def test_pm(self, pm):
|
||||
class A: pass
|
||||
a1, a2 = A(), A()
|
||||
pp.register(a1)
|
||||
assert pp.isregistered(a1)
|
||||
pp.register(a2, "hello")
|
||||
assert pp.isregistered(a2)
|
||||
l = pp.getplugins()
|
||||
pm.register(a1)
|
||||
assert pm.isregistered(a1)
|
||||
pm.register(a2, "hello")
|
||||
assert pm.isregistered(a2)
|
||||
l = pm.getplugins()
|
||||
assert a1 in l
|
||||
assert a2 in l
|
||||
assert pp.getplugin('hello') == a2
|
||||
pp.unregister(a1)
|
||||
assert not pp.isregistered(a1)
|
||||
assert pm.getplugin('hello') == a2
|
||||
pm.unregister(a1)
|
||||
assert not pm.isregistered(a1)
|
||||
|
||||
def test_pm_ordering(self):
|
||||
pp = PluginManager()
|
||||
def test_pm_ordering(self, pm):
|
||||
class A: pass
|
||||
a1, a2 = A(), A()
|
||||
pp.register(a1)
|
||||
pp.register(a2, "hello")
|
||||
l = pp.getplugins()
|
||||
pm.register(a1)
|
||||
pm.register(a2, "hello")
|
||||
l = pm.getplugins()
|
||||
assert l.index(a1) < l.index(a2)
|
||||
a3 = A()
|
||||
pp.register(a3, prepend=True)
|
||||
l = pp.getplugins()
|
||||
pm.register(a3, prepend=True)
|
||||
l = pm.getplugins()
|
||||
assert l.index(a3) == 0
|
||||
|
||||
def test_register_imported_modules(self):
|
||||
pp = PluginManager()
|
||||
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||
pp.register(mod)
|
||||
assert pp.isregistered(mod)
|
||||
l = pp.getplugins()
|
||||
assert mod in l
|
||||
pytest.raises(ValueError, "pp.register(mod)")
|
||||
pytest.raises(ValueError, lambda: pp.register(mod))
|
||||
#assert not pp.isregistered(mod2)
|
||||
assert pp.getplugins() == l
|
||||
|
||||
def test_canonical_import(self, monkeypatch):
|
||||
mod = py.std.types.ModuleType("pytest_xyz")
|
||||
monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
|
||||
pp = PluginManager()
|
||||
pp.import_plugin('pytest_xyz')
|
||||
assert pp.getplugin('pytest_xyz') == mod
|
||||
assert pp.isregistered(mod)
|
||||
|
||||
def test_register_mismatch_method(self):
|
||||
pp = get_plugin_manager()
|
||||
pm = get_plugin_manager()
|
||||
class hello:
|
||||
def pytest_gurgel(self):
|
||||
pass
|
||||
pytest.raises(Exception, lambda: pp.register(hello()))
|
||||
pytest.raises(Exception, lambda: pm.register(hello()))
|
||||
|
||||
def test_register_mismatch_arg(self):
|
||||
pp = get_plugin_manager()
|
||||
pm = get_plugin_manager()
|
||||
class hello:
|
||||
def pytest_configure(self, asd):
|
||||
pass
|
||||
pytest.raises(Exception, lambda: pp.register(hello()))
|
||||
pytest.raises(Exception, lambda: pm.register(hello()))
|
||||
|
||||
def test_register(self):
|
||||
pm = get_plugin_manager()
|
||||
|
@ -250,7 +76,7 @@ class TestBootstrapping:
|
|||
assert pm.getplugins()[-1:] == [my2]
|
||||
|
||||
def test_listattr(self):
|
||||
plugins = PluginManager()
|
||||
plugins = PluginManager("xyz")
|
||||
class api1:
|
||||
x = 41
|
||||
class api2:
|
||||
|
@ -263,27 +89,6 @@ class TestBootstrapping:
|
|||
l = list(plugins.listattr('x'))
|
||||
assert l == [41, 42, 43]
|
||||
|
||||
def test_hook_tracing(self):
|
||||
pm = get_plugin_manager()
|
||||
saveindent = []
|
||||
class api1:
|
||||
x = 41
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
saveindent.append(pm.trace.root.indent)
|
||||
raise ValueError(42)
|
||||
l = []
|
||||
pm.set_tracing(l.append)
|
||||
indent = pm.trace.root.indent
|
||||
p = api1()
|
||||
pm.register(p)
|
||||
|
||||
assert pm.trace.root.indent == indent
|
||||
assert len(l) == 2
|
||||
assert 'pytest_plugin_registered' in l[0]
|
||||
assert 'finish' in l[1]
|
||||
pytest.raises(ValueError, lambda: pm.register(api1()))
|
||||
assert pm.trace.root.indent == indent
|
||||
assert saveindent[0] > indent
|
||||
|
||||
class TestPytestPluginInteractions:
|
||||
|
||||
|
@ -372,10 +177,33 @@ class TestPytestPluginInteractions:
|
|||
config.pluginmanager.register(A())
|
||||
assert len(l) == 2
|
||||
|
||||
def test_hook_tracing(self):
|
||||
pytestpm = get_plugin_manager() # fully initialized with plugins
|
||||
saveindent = []
|
||||
class api1:
|
||||
x = 41
|
||||
def pytest_plugin_registered(self, plugin):
|
||||
saveindent.append(pytestpm.trace.root.indent)
|
||||
raise ValueError(42)
|
||||
l = []
|
||||
pytestpm.set_tracing(l.append)
|
||||
indent = pytestpm.trace.root.indent
|
||||
p = api1()
|
||||
pytestpm.register(p)
|
||||
|
||||
assert pytestpm.trace.root.indent == indent
|
||||
assert len(l) == 2
|
||||
assert 'pytest_plugin_registered' in l[0]
|
||||
assert 'finish' in l[1]
|
||||
with pytest.raises(ValueError):
|
||||
pytestpm.register(api1())
|
||||
assert pytestpm.trace.root.indent == indent
|
||||
assert saveindent[0] > indent
|
||||
|
||||
# lower level API
|
||||
|
||||
def test_listattr(self):
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager = PluginManager("xyz")
|
||||
class My2:
|
||||
x = 42
|
||||
pluginmanager.register(My2())
|
||||
|
@ -395,7 +223,7 @@ class TestPytestPluginInteractions:
|
|||
def m(self):
|
||||
return 19
|
||||
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager = PluginManager("xyz")
|
||||
p1 = P1()
|
||||
p2 = P2()
|
||||
p3 = P3()
|
||||
|
@ -572,7 +400,7 @@ class TestMultiCall:
|
|||
def m(self):
|
||||
return 19
|
||||
|
||||
pluginmanager = PluginManager()
|
||||
pluginmanager = PluginManager("xyz")
|
||||
p1 = P1()
|
||||
p2 = P2()
|
||||
p3 = P3()
|
||||
|
@ -624,11 +452,12 @@ class TestMultiCall:
|
|||
|
||||
|
||||
class TestHookRelay:
|
||||
def test_happypath(self):
|
||||
def test_hapmypath(self):
|
||||
class Api:
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
pm = PluginManager([Api], prefix="he")
|
||||
pm = PluginManager("he")
|
||||
pm.addhooks(Api)
|
||||
hook = pm.hook
|
||||
assert hasattr(hook, 'hello')
|
||||
assert repr(hook.hello).find("hello") != -1
|
||||
|
@ -647,7 +476,8 @@ class TestHookRelay:
|
|||
class Api:
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
pm = PluginManager(Api, prefix="he")
|
||||
pm = PluginManager("he")
|
||||
pm.addhooks(Api)
|
||||
class Plugin:
|
||||
def hello(self, argwrong):
|
||||
return arg + 1
|
||||
|
@ -656,19 +486,20 @@ class TestHookRelay:
|
|||
assert "argwrong" in str(exc.value)
|
||||
|
||||
def test_only_kwargs(self):
|
||||
pm = PluginManager()
|
||||
pm = PluginManager("he")
|
||||
class Api:
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
|
||||
pytest.raises(TypeError, lambda: mcm.hello(3))
|
||||
pm.addhooks(Api)
|
||||
pytest.raises(TypeError, lambda: pm.hook.hello(3))
|
||||
|
||||
def test_firstresult_definition(self):
|
||||
class Api:
|
||||
def hello(self, arg):
|
||||
"api hook 1"
|
||||
hello.firstresult = True
|
||||
pm = PluginManager([Api], "he")
|
||||
pm = PluginManager("he")
|
||||
pm.addhooks(Api)
|
||||
class Plugin:
|
||||
def hello(self, arg):
|
||||
return arg + 1
|
||||
|
@ -779,7 +610,7 @@ def test_importplugin_issue375(testdir):
|
|||
assert "aaaa" in str(excinfo.value)
|
||||
|
||||
class TestWrapMethod:
|
||||
def test_basic_happypath(self):
|
||||
def test_basic_hapmypath(self):
|
||||
class A:
|
||||
def f(self):
|
||||
return "A.f"
|
||||
|
@ -880,3 +711,182 @@ class TestWrapMethod:
|
|||
with pytest.raises(ValueError):
|
||||
A().error()
|
||||
assert l == [1]
|
||||
|
||||
|
||||
### to be shifted to own test file
|
||||
from _pytest.config import PytestPluginManager
|
||||
|
||||
class TestPytestPluginManager:
|
||||
def test_register_imported_modules(self):
|
||||
pm = PytestPluginManager()
|
||||
mod = py.std.types.ModuleType("x.y.pytest_hello")
|
||||
pm.register(mod)
|
||||
assert pm.isregistered(mod)
|
||||
l = pm.getplugins()
|
||||
assert mod in l
|
||||
pytest.raises(ValueError, "pm.register(mod)")
|
||||
pytest.raises(ValueError, lambda: pm.register(mod))
|
||||
#assert not pm.isregistered(mod2)
|
||||
assert pm.getplugins() == l
|
||||
|
||||
def test_canonical_import(self, monkeypatch):
|
||||
mod = py.std.types.ModuleType("pytest_xyz")
|
||||
monkeypatch.setitem(py.std.sys.modules, 'pytest_xyz', mod)
|
||||
pm = PytestPluginManager()
|
||||
pm.import_plugin('pytest_xyz')
|
||||
assert pm.getplugin('pytest_xyz') == mod
|
||||
assert pm.isregistered(mod)
|
||||
|
||||
def test_consider_module(self, testdir, pytestpm):
|
||||
testdir.syspathinsert()
|
||||
testdir.makepyfile(pytest_p1="#")
|
||||
testdir.makepyfile(pytest_p2="#")
|
||||
mod = py.std.types.ModuleType("temp")
|
||||
mod.pytest_plugins = ["pytest_p1", "pytest_p2"]
|
||||
pytestpm.consider_module(mod)
|
||||
assert pytestpm.getplugin("pytest_p1").__name__ == "pytest_p1"
|
||||
assert pytestpm.getplugin("pytest_p2").__name__ == "pytest_p2"
|
||||
|
||||
def test_consider_module_import_module(self, testdir):
|
||||
pytestpm = get_plugin_manager()
|
||||
mod = py.std.types.ModuleType("x")
|
||||
mod.pytest_plugins = "pytest_a"
|
||||
aplugin = testdir.makepyfile(pytest_a="#")
|
||||
reprec = testdir.make_hook_recorder(pytestpm)
|
||||
#syspath.prepend(aplugin.dirpath())
|
||||
py.std.sys.path.insert(0, str(aplugin.dirpath()))
|
||||
pytestpm.consider_module(mod)
|
||||
call = reprec.getcall(pytestpm.hook.pytest_plugin_registered.name)
|
||||
assert call.plugin.__name__ == "pytest_a"
|
||||
|
||||
# check that it is not registered twice
|
||||
pytestpm.consider_module(mod)
|
||||
l = reprec.getcalls("pytest_plugin_registered")
|
||||
assert len(l) == 1
|
||||
|
||||
def test_consider_env_fails_to_import(self, monkeypatch, pytestpm):
|
||||
monkeypatch.setenv('PYTEST_PLUGINS', 'nonexisting', prepend=",")
|
||||
with pytest.raises(ImportError):
|
||||
pytestpm.consider_env()
|
||||
|
||||
def test_plugin_skip(self, testdir, monkeypatch):
|
||||
p = testdir.makepyfile(skipping1="""
|
||||
import pytest
|
||||
pytest.skip("hello")
|
||||
""")
|
||||
p.copy(p.dirpath("skipping2.py"))
|
||||
monkeypatch.setenv("PYTEST_PLUGINS", "skipping2")
|
||||
result = testdir.runpytest("-rw", "-p", "skipping1", "--traceconfig")
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines([
|
||||
"WI1*skipped plugin*skipping1*hello*",
|
||||
"WI1*skipped plugin*skipping2*hello*",
|
||||
])
|
||||
|
||||
def test_consider_env_plugin_instantiation(self, testdir, monkeypatch, pytestpm):
|
||||
testdir.syspathinsert()
|
||||
testdir.makepyfile(xy123="#")
|
||||
monkeypatch.setitem(os.environ, 'PYTEST_PLUGINS', 'xy123')
|
||||
l1 = len(pytestpm.getplugins())
|
||||
pytestpm.consider_env()
|
||||
l2 = len(pytestpm.getplugins())
|
||||
assert l2 == l1 + 1
|
||||
assert pytestpm.getplugin('xy123')
|
||||
pytestpm.consider_env()
|
||||
l3 = len(pytestpm.getplugins())
|
||||
assert l2 == l3
|
||||
|
||||
def test_consider_setuptools_instantiation(self, monkeypatch, pytestpm):
|
||||
pkg_resources = pytest.importorskip("pkg_resources")
|
||||
def my_iter(name):
|
||||
assert name == "pytest11"
|
||||
class EntryPoint:
|
||||
name = "pytest_mytestplugin"
|
||||
dist = None
|
||||
def load(self):
|
||||
class PseudoPlugin:
|
||||
x = 42
|
||||
return PseudoPlugin()
|
||||
return iter([EntryPoint()])
|
||||
|
||||
monkeypatch.setattr(pkg_resources, 'iter_entry_points', my_iter)
|
||||
pytestpm.consider_setuptools_entrypoints()
|
||||
plugin = pytestpm.getplugin("mytestplugin")
|
||||
assert plugin.x == 42
|
||||
|
||||
def test_consider_setuptools_not_installed(self, monkeypatch, pytestpm):
|
||||
monkeypatch.setitem(py.std.sys.modules, 'pkg_resources',
|
||||
py.std.types.ModuleType("pkg_resources"))
|
||||
pytestpm.consider_setuptools_entrypoints()
|
||||
# ok, we did not explode
|
||||
|
||||
def test_pluginmanager_ENV_startup(self, testdir, monkeypatch):
|
||||
testdir.makepyfile(pytest_x500="#")
|
||||
p = testdir.makepyfile("""
|
||||
import pytest
|
||||
def test_hello(pytestconfig):
|
||||
plugin = pytestconfig.pluginmanager.getplugin('pytest_x500')
|
||||
assert plugin is not None
|
||||
""")
|
||||
monkeypatch.setenv('PYTEST_PLUGINS', 'pytest_x500', prepend=",")
|
||||
result = testdir.runpytest(p)
|
||||
assert result.ret == 0
|
||||
result.stdout.fnmatch_lines(["*1 passed in*"])
|
||||
|
||||
def test_import_plugin_importname(self, testdir, pytestpm):
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwx.y")')
|
||||
|
||||
testdir.syspathinsert()
|
||||
pluginname = "pytest_hello"
|
||||
testdir.makepyfile(**{pluginname: ""})
|
||||
pytestpm.import_plugin("pytest_hello")
|
||||
len1 = len(pytestpm.getplugins())
|
||||
pytestpm.import_plugin("pytest_hello")
|
||||
len2 = len(pytestpm.getplugins())
|
||||
assert len1 == len2
|
||||
plugin1 = pytestpm.getplugin("pytest_hello")
|
||||
assert plugin1.__name__.endswith('pytest_hello')
|
||||
plugin2 = pytestpm.getplugin("pytest_hello")
|
||||
assert plugin2 is plugin1
|
||||
|
||||
def test_import_plugin_dotted_name(self, testdir, pytestpm):
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("qweqwex.y")')
|
||||
pytest.raises(ImportError, 'pytestpm.import_plugin("pytest_qweqwex.y")')
|
||||
|
||||
testdir.syspathinsert()
|
||||
testdir.mkpydir("pkg").join("plug.py").write("x=3")
|
||||
pluginname = "pkg.plug"
|
||||
pytestpm.import_plugin(pluginname)
|
||||
mod = pytestpm.getplugin("pkg.plug")
|
||||
assert mod.x == 3
|
||||
|
||||
def test_config_sets_conftesthandle_onimport(self, testdir):
|
||||
config = testdir.parseconfig([])
|
||||
assert config._conftest._onimport == config._onimportconftest
|
||||
|
||||
def test_consider_conftest_deps(self, testdir, pytestpm):
|
||||
mod = testdir.makepyfile("pytest_plugins='xyz'").pyimport()
|
||||
with pytest.raises(ImportError):
|
||||
pytestpm.consider_conftest(mod)
|
||||
|
||||
|
||||
class TestPytestPluginManagerBootstrapming:
|
||||
def test_preparse_args(self, pytestpm):
|
||||
pytest.raises(ImportError, lambda:
|
||||
pytestpm.consider_preparse(["xyz", "-p", "hello123"]))
|
||||
|
||||
def test_plugin_prevent_register(self, pytestpm):
|
||||
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
|
||||
l1 = pytestpm.getplugins()
|
||||
pytestpm.register(42, name="abc")
|
||||
l2 = pytestpm.getplugins()
|
||||
assert len(l2) == len(l1)
|
||||
|
||||
def test_plugin_prevent_register_unregistered_alredy_registered(self, pytestpm):
|
||||
pytestpm.register(42, name="abc")
|
||||
l1 = pytestpm.getplugins()
|
||||
assert 42 in l1
|
||||
pytestpm.consider_preparse(["xyz", "-p", "no:abc"])
|
||||
l2 = pytestpm.getplugins()
|
||||
assert 42 not in l2
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import pytest
|
||||
import os
|
||||
from _pytest.pytester import HookRecorder
|
||||
from _pytest.core import PluginManager
|
||||
from _pytest.config import PytestPluginManager
|
||||
from _pytest.main import EXIT_OK, EXIT_TESTSFAILED
|
||||
|
||||
|
||||
|
@ -93,8 +93,8 @@ def make_holder():
|
|||
|
||||
@pytest.mark.parametrize("holder", make_holder())
|
||||
def test_hookrecorder_basic(holder):
|
||||
pm = PluginManager()
|
||||
pm.hook._addhooks(holder, "pytest_")
|
||||
pm = PytestPluginManager()
|
||||
pm.addhooks(holder)
|
||||
rec = HookRecorder(pm)
|
||||
pm.hook.pytest_xyz(arg=123)
|
||||
call = rec.popcall("pytest_xyz")
|
||||
|
|
|
@ -457,7 +457,9 @@ class TestTerminalFunctional:
|
|||
])
|
||||
assert result.ret == 1
|
||||
|
||||
pytestconfig.pluginmanager.skipifmissing("xdist")
|
||||
if not pytestconfig.pluginmanager.hasplugin("xdist"):
|
||||
pytest.skip("xdist plugin not installed")
|
||||
|
||||
result = testdir.runpytest(p1, '-v', '-n 1')
|
||||
result.stdout.fnmatch_lines([
|
||||
"*FAIL*test_verbose_reporting.py::test_fail*",
|
||||
|
|
Loading…
Reference in New Issue