simplify pluginmanager, move plugin validation code to plugin, remove unused code
--HG-- branch : trunk
This commit is contained in:
parent
0361b73d75
commit
8737254a74
|
@ -44,8 +44,7 @@ class PluginManager(object):
|
||||||
if name in self._name2plugin:
|
if name in self._name2plugin:
|
||||||
return False
|
return False
|
||||||
self._name2plugin[name] = plugin
|
self._name2plugin[name] = plugin
|
||||||
self.hook.pytest_plugin_registered(plugin=plugin)
|
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
|
||||||
self._checkplugin(plugin)
|
|
||||||
self.comregistry.register(plugin)
|
self.comregistry.register(plugin)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -138,46 +137,6 @@ class PluginManager(object):
|
||||||
def _warn(self, msg):
|
def _warn(self, msg):
|
||||||
print ("===WARNING=== %s" % (msg,))
|
print ("===WARNING=== %s" % (msg,))
|
||||||
|
|
||||||
def _checkplugin(self, plugin):
|
|
||||||
# =====================================================
|
|
||||||
# check plugin hooks
|
|
||||||
# =====================================================
|
|
||||||
methods = collectattr(plugin)
|
|
||||||
hooks = collectattr(hookspec)
|
|
||||||
stringio = py.io.TextIO()
|
|
||||||
def Print(*args):
|
|
||||||
if args:
|
|
||||||
stringio.write(" ".join(map(str, args)))
|
|
||||||
stringio.write("\n")
|
|
||||||
|
|
||||||
fail = False
|
|
||||||
while methods:
|
|
||||||
name, method = methods.popitem()
|
|
||||||
#print "checking", name
|
|
||||||
if isgenerichook(name):
|
|
||||||
continue
|
|
||||||
if name not in hooks:
|
|
||||||
Print("found unknown hook:", name)
|
|
||||||
fail = True
|
|
||||||
else:
|
|
||||||
method_args = getargs(method)
|
|
||||||
if '__multicall__' in method_args:
|
|
||||||
method_args.remove('__multicall__')
|
|
||||||
hook = hooks[name]
|
|
||||||
hookargs = getargs(hook)
|
|
||||||
for arg in method_args:
|
|
||||||
if arg not in hookargs:
|
|
||||||
Print("argument %r not available" %(arg, ))
|
|
||||||
Print("actual definition: %s" %(formatdef(method)))
|
|
||||||
Print("available hook arguments: %s" %
|
|
||||||
", ".join(hookargs))
|
|
||||||
fail = True
|
|
||||||
break
|
|
||||||
#if not fail:
|
|
||||||
# print "matching hook:", formatdef(method)
|
|
||||||
if fail:
|
|
||||||
name = getattr(plugin, '__name__', plugin)
|
|
||||||
raise self.Error("%s:\n%s" %(name, stringio.getvalue()))
|
|
||||||
#
|
#
|
||||||
#
|
#
|
||||||
# API for interacting with registered and instantiated plugin objects
|
# API for interacting with registered and instantiated plugin objects
|
||||||
|
@ -224,9 +183,6 @@ class PluginManager(object):
|
||||||
config.hook.pytest_unconfigure(config=config)
|
config.hook.pytest_unconfigure(config=config)
|
||||||
config.pluginmanager.unregister(self)
|
config.pluginmanager.unregister(self)
|
||||||
|
|
||||||
#
|
|
||||||
# XXX old code to automatically load classes
|
|
||||||
#
|
|
||||||
def canonical_importname(name):
|
def canonical_importname(name):
|
||||||
name = name.lower()
|
name = name.lower()
|
||||||
modprefix = "pytest_"
|
modprefix = "pytest_"
|
||||||
|
@ -254,59 +210,3 @@ def importplugin(importspec):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def isgenerichook(name):
|
|
||||||
return name == "pytest_plugins" or \
|
|
||||||
name.startswith("pytest_funcarg__")
|
|
||||||
|
|
||||||
def getargs(func):
|
|
||||||
args = py.std.inspect.getargs(py.code.getrawcode(func))[0]
|
|
||||||
startindex = py.std.inspect.ismethod(func) and 1 or 0
|
|
||||||
return args[startindex:]
|
|
||||||
|
|
||||||
def collectattr(obj, prefixes=("pytest_",)):
|
|
||||||
methods = {}
|
|
||||||
for apiname in dir(obj):
|
|
||||||
for prefix in prefixes:
|
|
||||||
if apiname.startswith(prefix):
|
|
||||||
methods[apiname] = getattr(obj, apiname)
|
|
||||||
return methods
|
|
||||||
|
|
||||||
def formatdef(func):
|
|
||||||
return "%s%s" %(
|
|
||||||
func.__name__,
|
|
||||||
py.std.inspect.formatargspec(*py.std.inspect.getargspec(func))
|
|
||||||
)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import py.plugin
|
|
||||||
basedir = py._dir.join('_plugin')
|
|
||||||
name2text = {}
|
|
||||||
for p in basedir.listdir("pytest_*"):
|
|
||||||
if p.ext == ".py" or (
|
|
||||||
p.check(dir=1) and p.join("__init__.py").check()):
|
|
||||||
impname = p.purebasename
|
|
||||||
if impname.find("__") != -1:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
plugin = importplugin(impname)
|
|
||||||
except (ImportError, py.impl.test.outcome.Skipped):
|
|
||||||
name2text[impname] = "IMPORT ERROR"
|
|
||||||
else:
|
|
||||||
doc = plugin.__doc__ or ""
|
|
||||||
doc = doc.strip()
|
|
||||||
name2text[impname] = doc
|
|
||||||
|
|
||||||
for name in sorted(name2text.keys()):
|
|
||||||
text = name2text[name]
|
|
||||||
if name[0] == "_":
|
|
||||||
continue
|
|
||||||
print ("%-20s %s" % (name, text.split("\n")[0]))
|
|
||||||
|
|
||||||
#text = py.std.textwrap.wrap(name2text[name],
|
|
||||||
# width = 80,
|
|
||||||
# initial_indent="%s: " % name,
|
|
||||||
# replace_whitespace = False)
|
|
||||||
#for line in text:
|
|
||||||
# print line
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -159,7 +159,7 @@ def pytest_looponfailinfo(failreports, rootdirs):
|
||||||
# error handling and internal debugging hooks
|
# error handling and internal debugging hooks
|
||||||
# -------------------------------------------------------------------------
|
# -------------------------------------------------------------------------
|
||||||
|
|
||||||
def pytest_plugin_registered(plugin):
|
def pytest_plugin_registered(plugin, manager):
|
||||||
""" a new py lib plugin got registered. """
|
""" a new py lib plugin got registered. """
|
||||||
|
|
||||||
def pytest_plugin_unregistered(plugin):
|
def pytest_plugin_unregistered(plugin):
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
""" provide version info, conftest/environment config names.
|
""" provide version info, conftest/environment config names.
|
||||||
"""
|
"""
|
||||||
import py
|
import py
|
||||||
import sys
|
import inspect, sys
|
||||||
|
|
||||||
def pytest_addoption(parser):
|
def pytest_addoption(parser):
|
||||||
group = parser.getgroup('debugconfig')
|
group = parser.getgroup('debugconfig')
|
||||||
|
@ -61,3 +61,73 @@ conftest_options = (
|
||||||
('collect_ignore', '(relative) paths ignored during collection'),
|
('collect_ignore', '(relative) paths ignored during collection'),
|
||||||
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
|
('rsyncdirs', 'to-be-rsynced directories for dist-testing'),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# =====================================================
|
||||||
|
# validate plugin syntax and hooks
|
||||||
|
# =====================================================
|
||||||
|
|
||||||
|
def pytest_plugin_registered(manager, plugin):
|
||||||
|
hookspec = manager.hook._hookspecs
|
||||||
|
methods = collectattr(plugin)
|
||||||
|
hooks = collectattr(hookspec)
|
||||||
|
stringio = py.io.TextIO()
|
||||||
|
def Print(*args):
|
||||||
|
if args:
|
||||||
|
stringio.write(" ".join(map(str, args)))
|
||||||
|
stringio.write("\n")
|
||||||
|
|
||||||
|
fail = False
|
||||||
|
while methods:
|
||||||
|
name, method = methods.popitem()
|
||||||
|
#print "checking", name
|
||||||
|
if isgenerichook(name):
|
||||||
|
continue
|
||||||
|
if name not in hooks:
|
||||||
|
Print("found unknown hook:", name)
|
||||||
|
fail = True
|
||||||
|
else:
|
||||||
|
method_args = getargs(method)
|
||||||
|
if '__multicall__' in method_args:
|
||||||
|
method_args.remove('__multicall__')
|
||||||
|
hook = hooks[name]
|
||||||
|
hookargs = getargs(hook)
|
||||||
|
for arg in method_args:
|
||||||
|
if arg not in hookargs:
|
||||||
|
Print("argument %r not available" %(arg, ))
|
||||||
|
Print("actual definition: %s" %(formatdef(method)))
|
||||||
|
Print("available hook arguments: %s" %
|
||||||
|
", ".join(hookargs))
|
||||||
|
fail = True
|
||||||
|
break
|
||||||
|
#if not fail:
|
||||||
|
# print "matching hook:", formatdef(method)
|
||||||
|
if fail:
|
||||||
|
name = getattr(plugin, '__name__', plugin)
|
||||||
|
raise PluginValidationError("%s:\n%s" %(name, stringio.getvalue()))
|
||||||
|
|
||||||
|
class PluginValidationError(Exception):
|
||||||
|
""" plugin failed validation. """
|
||||||
|
|
||||||
|
def isgenerichook(name):
|
||||||
|
return name == "pytest_plugins" or \
|
||||||
|
name.startswith("pytest_funcarg__")
|
||||||
|
|
||||||
|
def getargs(func):
|
||||||
|
args = inspect.getargs(py.code.getrawcode(func))[0]
|
||||||
|
startindex = inspect.ismethod(func) and 1 or 0
|
||||||
|
return args[startindex:]
|
||||||
|
|
||||||
|
def collectattr(obj, prefixes=("pytest_",)):
|
||||||
|
methods = {}
|
||||||
|
for apiname in dir(obj):
|
||||||
|
for prefix in prefixes:
|
||||||
|
if apiname.startswith(prefix):
|
||||||
|
methods[apiname] = getattr(obj, apiname)
|
||||||
|
return methods
|
||||||
|
|
||||||
|
def formatdef(func):
|
||||||
|
return "%s%s" %(
|
||||||
|
func.__name__,
|
||||||
|
inspect.formatargspec(*inspect.getargspec(func))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import py, os
|
import py, os
|
||||||
|
from py.plugin.pytest_helpconfig import collectattr
|
||||||
|
|
||||||
def test_version(testdir):
|
def test_version(testdir):
|
||||||
assert py.version == py.__version__
|
assert py.version == py.__version__
|
||||||
|
@ -16,3 +17,15 @@ def test_helpconfig(testdir):
|
||||||
"*cmdline*conftest*ENV*",
|
"*cmdline*conftest*ENV*",
|
||||||
])
|
])
|
||||||
|
|
||||||
|
def test_collectattr():
|
||||||
|
class A:
|
||||||
|
def pytest_hello(self):
|
||||||
|
pass
|
||||||
|
class B(A):
|
||||||
|
def pytest_world(self):
|
||||||
|
pass
|
||||||
|
methods = py.builtin.sorted(collectattr(B))
|
||||||
|
assert list(methods) == ['pytest_hello', 'pytest_world']
|
||||||
|
methods = py.builtin.sorted(collectattr(B()))
|
||||||
|
assert list(methods) == ['pytest_hello', 'pytest_world']
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import py, os
|
import py, os
|
||||||
from py.impl.test.pluginmanager import PluginManager, canonical_importname, collectattr
|
from py.impl.test.pluginmanager import PluginManager, canonical_importname
|
||||||
|
|
||||||
class TestBootstrapping:
|
class TestBootstrapping:
|
||||||
def test_consider_env_fails_to_import(self, monkeypatch):
|
def test_consider_env_fails_to_import(self, monkeypatch):
|
||||||
|
@ -185,14 +185,14 @@ class TestBootstrapping:
|
||||||
class hello:
|
class hello:
|
||||||
def pytest_gurgel(self):
|
def pytest_gurgel(self):
|
||||||
pass
|
pass
|
||||||
py.test.raises(pp.Error, "pp.register(hello())")
|
py.test.raises(Exception, "pp.register(hello())")
|
||||||
|
|
||||||
def test_register_mismatch_arg(self):
|
def test_register_mismatch_arg(self):
|
||||||
pp = PluginManager()
|
pp = PluginManager()
|
||||||
class hello:
|
class hello:
|
||||||
def pytest_configure(self, asd):
|
def pytest_configure(self, asd):
|
||||||
pass
|
pass
|
||||||
excinfo = py.test.raises(pp.Error, "pp.register(hello())")
|
excinfo = py.test.raises(Exception, "pp.register(hello())")
|
||||||
|
|
||||||
def test_canonical_importname(self):
|
def test_canonical_importname(self):
|
||||||
for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
|
for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
|
||||||
|
@ -270,18 +270,6 @@ class TestPytestPluginInteractions:
|
||||||
assert not pluginmanager.listattr("hello")
|
assert not pluginmanager.listattr("hello")
|
||||||
assert pluginmanager.listattr("x") == [42]
|
assert pluginmanager.listattr("x") == [42]
|
||||||
|
|
||||||
def test_collectattr():
|
|
||||||
class A:
|
|
||||||
def pytest_hello(self):
|
|
||||||
pass
|
|
||||||
class B(A):
|
|
||||||
def pytest_world(self):
|
|
||||||
pass
|
|
||||||
methods = py.builtin.sorted(collectattr(B))
|
|
||||||
assert list(methods) == ['pytest_hello', 'pytest_world']
|
|
||||||
methods = py.builtin.sorted(collectattr(B()))
|
|
||||||
assert list(methods) == ['pytest_hello', 'pytest_world']
|
|
||||||
|
|
||||||
@py.test.mark.xfail
|
@py.test.mark.xfail
|
||||||
def test_namespace_has_default_and_env_plugins(testdir):
|
def test_namespace_has_default_and_env_plugins(testdir):
|
||||||
p = testdir.makepyfile("""
|
p = testdir.makepyfile("""
|
||||||
|
|
Loading…
Reference in New Issue