""" managing loading and interacting with pytest plugins. """ import py from py.__.test.plugin import hookspec 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.MultiCall = self.comregistry.MultiCall self.impname2plugin = {} self.hook = py._com.Hooks( 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) assert name not in self.impname2plugin 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 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 mod = importplugin(modname) check_old_use(mod, modname) self.register(mod) self.consider_module(mod) def _checkplugin(self, plugin): # ===================================================== # check plugin hooks # ===================================================== methods = collectattr(plugin) hooks = collectattr(hookspec) stringio = py.std.StringIO.StringIO() 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 '__call__' in method_args: method_args.remove('__call__') hook = hooks[name] hookargs = getargs(hook) for arg, hookarg in zip(method_args, hookargs): if arg != hookarg: Print("argument mismatch: %r != %r" %(arg, hookarg)) Print("actual : %s" %(formatdef(method))) Print("required:", formatdef(hook)) 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): methods = self.comregistry.listattr("pytest_addoption", 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) def call_plugin(self, plugin, methname, **kwargs): return self.MultiCall(self.listattr(methname, plugins=[plugin]), **kwargs).execute(firstresult=True) def do_configure(self, config): assert not hasattr(self, '_config') config.pluginmanager.register(self) self._config = config config.hook.pytest_configure(config=self._config) 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: if str(e).find(importspec) == -1: raise try: return __import__("py.__.test.plugin.%s" %(importspec), None, None, '__doc__') except ImportError, e: 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__") or \ name.startswith("pytest_option_") def getargs(func): args = py.std.inspect.getargs(func.func_code)[0] startindex = hasattr(func, 'im_self') 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.func_name, py.std.inspect.formatargspec(*py.std.inspect.getargspec(func)) )