test_ok1/py/test/pluginmanager.py

293 lines
9.7 KiB
Python

"""
managing loading and interacting with pytest plugins.
"""
import py
from py.__.test.plugin import hookspec
from py.__.test.outcome import Skipped
def check_old_use(mod, modname):
clsname = modname[len('pytest_'):].capitalize() + "Plugin"
assert not hasattr(mod, clsname), (mod, clsname)
class PluginManager(object):
class Error(Exception):
"""signals a plugin specific error."""
def __init__(self, comregistry=None):
if comregistry is None:
comregistry = py._com.Registry()
self.comregistry = comregistry
self.impname2plugin = {}
self.hook = py._com.HookRelay(
hookspecs=hookspec,
registry=self.comregistry)
def _getpluginname(self, plugin, name):
if name is None:
if hasattr(plugin, '__name__'):
name = plugin.__name__.split(".")[-1]
else:
name = id(plugin)
return name
def register(self, plugin, name=None):
assert not self.isregistered(plugin)
name = self._getpluginname(plugin, name)
if name in self.impname2plugin:
return False
self.impname2plugin[name] = plugin
self.hook.pytest_plugin_registered(plugin=plugin)
self._checkplugin(plugin)
self.comregistry.register(plugin)
return True
def unregister(self, plugin):
self.hook.pytest_plugin_unregistered(plugin=plugin)
self.comregistry.unregister(plugin)
for name, value in list(self.impname2plugin.items()):
if value == plugin:
del self.impname2plugin[name]
def isregistered(self, plugin, name=None):
return self._getpluginname(plugin, name) in self.impname2plugin
def getplugins(self):
return list(self.comregistry)
def getplugin(self, importname):
impname = canonical_importname(importname)
return self.impname2plugin[impname]
# API for bootstrapping
#
def _envlist(self, varname):
val = py.std.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_preparse(self, args):
for opt1,opt2 in zip(args, args[1:]):
if opt1 == "-p":
self.import_plugin(opt2)
def consider_conftest(self, conftestmodule):
cls = getattr(conftestmodule, 'ConftestPlugin', None)
if cls is not None:
raise ValueError("%r: 'ConftestPlugins' only existed till 1.0.0b1, "
"were removed in 1.0.0b2" % (cls,))
if self.register(conftestmodule, name=conftestmodule.__file__):
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, spec):
assert isinstance(spec, str)
modname = canonical_importname(spec)
if modname in self.impname2plugin:
return
try:
mod = importplugin(modname)
except KeyboardInterrupt:
raise
except Skipped:
e = py.std.sys.exc_info()[1]
self._warn("could not import plugin %r, reason: %r" %(
(modname, e.msg)))
else:
check_old_use(mod, modname)
self.register(mod)
self.consider_module(mod)
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
#
#
def listattr(self, attrname, plugins=None, extra=()):
return self.comregistry.listattr(attrname, plugins=plugins, extra=extra)
def notify_exception(self, excinfo=None):
if excinfo is None:
excinfo = py.code.ExceptionInfo()
excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
return self.hook.pytest_internalerror(excrepr=excrepr)
def do_addoption(self, parser):
mname = "pytest_addoption"
methods = self.comregistry.listattr(mname, reverse=True)
mc = py._com.MultiCall(methods, {'parser': parser})
mc.execute()
def pytest_plugin_registered(self, plugin):
if hasattr(self, '_config'):
self.call_plugin(plugin, "pytest_addoption",
{'parser': self._config._parser})
self.call_plugin(plugin, "pytest_configure",
{'config': self._config})
#dic = self.call_plugin(plugin, "pytest_namespace")
#self._updateext(dic)
def call_plugin(self, plugin, methname, kwargs):
return py._com.MultiCall(
methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def _updateext(self, dic):
if dic:
for name, value in dic.items():
setattr(py.test, name, value)
def do_configure(self, config):
assert not hasattr(self, '_config')
config.pluginmanager.register(self)
self._config = config
config.hook.pytest_configure(config=self._config)
for dic in config.hook.pytest_namespace() or []:
self._updateext(dic)
def do_unconfigure(self, config):
config = self._config
del self._config
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_"
if not name.startswith(modprefix):
name = modprefix + name
return name
def importplugin(importspec):
try:
return __import__(importspec)
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(importspec) == -1:
raise
try:
return __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__')
except ImportError:
e = py.std.sys.exc_info()[1]
if str(e).find(importspec) == -1:
raise
#print "syspath:", py.std.sys.path
#print "curdir:", py.std.os.getcwd()
return __import__(importspec) # show the original exception
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.__.test.plugin
basedir = py.path.local(py.__.test.plugin.__file__).dirpath()
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.__.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