simplify pluginmanager, move plugin validation code to plugin, remove unused code

--HG--
branch : trunk
This commit is contained in:
holger krekel 2009-12-29 10:59:01 +01:00
parent 0361b73d75
commit 8737254a74
5 changed files with 89 additions and 118 deletions

View File

@ -44,8 +44,7 @@ class PluginManager(object):
if name in self._name2plugin:
return False
self._name2plugin[name] = plugin
self.hook.pytest_plugin_registered(plugin=plugin)
self._checkplugin(plugin)
self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
self.comregistry.register(plugin)
return True
@ -138,46 +137,6 @@ class PluginManager(object):
def _warn(self, 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
@ -224,9 +183,6 @@ class PluginManager(object):
config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self)
#
# XXX old code to automatically load classes
#
def canonical_importname(name):
name = name.lower()
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

View File

@ -159,7 +159,7 @@ def pytest_looponfailinfo(failreports, rootdirs):
# error handling and internal debugging hooks
# -------------------------------------------------------------------------
def pytest_plugin_registered(plugin):
def pytest_plugin_registered(plugin, manager):
""" a new py lib plugin got registered. """
def pytest_plugin_unregistered(plugin):

View File

@ -1,7 +1,7 @@
""" provide version info, conftest/environment config names.
"""
import py
import sys
import inspect, sys
def pytest_addoption(parser):
group = parser.getgroup('debugconfig')
@ -61,3 +61,73 @@ conftest_options = (
('collect_ignore', '(relative) paths ignored during collection'),
('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))
)

View File

@ -1,4 +1,5 @@
import py, os
from py.plugin.pytest_helpconfig import collectattr
def test_version(testdir):
assert py.version == py.__version__
@ -16,3 +17,15 @@ def test_helpconfig(testdir):
"*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']

View File

@ -1,5 +1,5 @@
import py, os
from py.impl.test.pluginmanager import PluginManager, canonical_importname, collectattr
from py.impl.test.pluginmanager import PluginManager, canonical_importname
class TestBootstrapping:
def test_consider_env_fails_to_import(self, monkeypatch):
@ -185,14 +185,14 @@ class TestBootstrapping:
class hello:
def pytest_gurgel(self):
pass
py.test.raises(pp.Error, "pp.register(hello())")
py.test.raises(Exception, "pp.register(hello())")
def test_register_mismatch_arg(self):
pp = PluginManager()
class hello:
def pytest_configure(self, asd):
pass
excinfo = py.test.raises(pp.Error, "pp.register(hello())")
excinfo = py.test.raises(Exception, "pp.register(hello())")
def test_canonical_importname(self):
for name in 'xyz', 'pytest_xyz', 'pytest_Xyz', 'Xyz':
@ -270,18 +270,6 @@ class TestPytestPluginInteractions:
assert not pluginmanager.listattr("hello")
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
def test_namespace_has_default_and_env_plugins(testdir):
p = testdir.makepyfile("""