removed unnccessary indirections in the PluginManager,

also fixed a bug in _core.varnames(), which probably considerably
speeds up hook calls.

--HG--
branch : trunk
This commit is contained in:
holger krekel 2010-10-13 11:12:27 +02:00
parent 270deb015e
commit 3a5d28f3fe
5 changed files with 147 additions and 185 deletions

View File

@ -1,23 +1,26 @@
import sys, os
import inspect
import py import py
from pytest import hookspec
assert py.__version__.split(".")[:2] >= ['2', '0'], ("installation problem: " assert py.__version__.split(".")[:2] >= ['2', '0'], ("installation problem: "
"%s is too old, remove or upgrade 'py'" % (py.__version__)) "%s is too old, remove or upgrade 'py'" % (py.__version__))
import sys, os
default_plugins = ( default_plugins = (
"config session terminal python runner pdb capture mark skipping tmpdir " "config session terminal python runner pdb capture mark skipping tmpdir "
"monkeypatch recwarn pastebin unittest helpconfig nose assertion genscript " "monkeypatch recwarn pastebin unittest helpconfig nose assertion genscript "
"junitxml doctest").split() "junitxml doctest").split()
IMPORTPREFIX = "pytest_"
class PluginManager(object): class PluginManager(object):
def __init__(self): def __init__(self, load=False):
from pytest import hookspec
self.registry = Registry()
self._name2plugin = {} self._name2plugin = {}
self._plugins = []
self._hints = [] self._hints = []
self.hook = HookRelay([hookspec], registry=self.registry) self.hook = HookRelay([hookspec], pm=self)
self.register(self) self.register(self)
if load:
for spec in default_plugins: for spec in default_plugins:
self.import_plugin(spec) self.import_plugin(spec)
@ -31,19 +34,22 @@ class PluginManager(object):
def register(self, plugin, name=None, prepend=False): def register(self, plugin, name=None, prepend=False):
assert not self.isregistered(plugin), plugin assert not self.isregistered(plugin), plugin
assert not self.registry.isregistered(plugin), plugin assert not self.isregistered(plugin), plugin
name = self._getpluginname(plugin, name) name = self._getpluginname(plugin, name)
if name in self._name2plugin: if name in self._name2plugin:
return False return False
self._name2plugin[name] = plugin self._name2plugin[name] = plugin
self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self}) self.call_plugin(plugin, "pytest_addhooks", {'pluginmanager': self})
self.hook.pytest_plugin_registered(manager=self, plugin=plugin) self.hook.pytest_plugin_registered(manager=self, plugin=plugin)
self.registry.register(plugin, prepend=prepend) if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
return True return True
def unregister(self, plugin): def unregister(self, plugin):
self.hook.pytest_plugin_unregistered(plugin=plugin) self.hook.pytest_plugin_unregistered(plugin=plugin)
self.registry.unregister(plugin) self._plugins.remove(plugin)
for name, value in list(self._name2plugin.items()): for name, value in list(self._name2plugin.items()):
if value == plugin: if value == plugin:
del self._name2plugin[name] del self._name2plugin[name]
@ -59,7 +65,7 @@ class PluginManager(object):
self.hook._addhooks(spec, prefix="pytest_") self.hook._addhooks(spec, prefix="pytest_")
def getplugins(self): def getplugins(self):
return list(self.registry) return list(self._plugins)
def skipifmissing(self, name): def skipifmissing(self, name):
if not self.hasplugin(name): if not self.hasplugin(name):
@ -68,10 +74,9 @@ class PluginManager(object):
def hasplugin(self, name): def hasplugin(self, name):
try: try:
self.getplugin(name) self.getplugin(name)
return True
except KeyError: except KeyError:
return False return False
else:
return True
def getplugin(self, name): def getplugin(self, name):
try: try:
@ -145,35 +150,15 @@ class PluginManager(object):
self.register(mod, modname) self.register(mod, modname)
self.consider_module(mod) self.consider_module(mod)
def pytest_terminal_summary(self, terminalreporter): def pytest_plugin_registered(self, plugin):
tw = terminalreporter._tw dic = self.call_plugin(plugin, "pytest_namespace", {}) or {}
if terminalreporter.config.option.traceconfig: if dic:
for hint in self._hints: self._setns(py.test, dic)
tw.line("hint: %s" % hint) if hasattr(self, '_config'):
self.call_plugin(plugin, "pytest_addoption",
# {'parser': self._config._parser})
# self.call_plugin(plugin, "pytest_configure",
# API for interacting with registered and instantiated plugin objects {'config': self._config})
#
#
def listattr(self, attrname, plugins=None):
return self.registry.listattr(attrname, plugins=plugins)
def notify_exception(self, excinfo=None):
if excinfo is None:
excinfo = py.code.ExceptionInfo()
excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
res = self.hook.pytest_internalerror(excrepr=excrepr)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.flush()
def do_addoption(self, parser):
mname = "pytest_addoption"
methods = self.registry.listattr(mname, reverse=True)
mc = MultiCall(methods, {'parser': parser})
mc.execute()
def _setns(self, obj, dic): def _setns(self, obj, dic):
for name, value in dic.items(): for name, value in dic.items():
@ -191,20 +176,16 @@ class PluginManager(object):
setattr(obj, name, value) setattr(obj, name, value)
obj.__all__.append(name) obj.__all__.append(name)
def pytest_plugin_registered(self, plugin): def pytest_terminal_summary(self, terminalreporter):
dic = self.call_plugin(plugin, "pytest_namespace", {}) or {} tw = terminalreporter._tw
if dic: if terminalreporter.config.option.traceconfig:
self._setns(py.test, dic) for hint in self._hints:
if hasattr(self, '_config'): tw.line("hint: %s" % hint)
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): def do_addoption(self, parser):
return MultiCall( mname = "pytest_addoption"
methods=self.listattr(methname, plugins=[plugin]), methods = reversed(self.listattr(mname))
kwargs=kwargs, firstresult=True).execute() MultiCall(methods, {'parser': parser}).execute()
def do_configure(self, config): def do_configure(self, config):
assert not hasattr(self, '_config') assert not hasattr(self, '_config')
@ -217,11 +198,33 @@ class PluginManager(object):
config.hook.pytest_unconfigure(config=config) config.hook.pytest_unconfigure(config=config)
config.pluginmanager.unregister(self) config.pluginmanager.unregister(self)
def notify_exception(self, excinfo):
excrepr = excinfo.getrepr(funcargs=True, showlocals=True)
res = self.hook.pytest_internalerror(excrepr=excrepr)
if not py.builtin.any(res):
for line in str(excrepr).split("\n"):
sys.stderr.write("INTERNALERROR> %s\n" %line)
sys.stderr.flush()
def listattr(self, attrname, plugins=None):
if plugins is None:
plugins = self._plugins
l = []
for plugin in plugins:
try:
l.append(getattr(plugin, attrname))
except AttributeError:
continue
return l
def call_plugin(self, plugin, methname, kwargs):
return MultiCall(methods=self.listattr(methname, plugins=[plugin]),
kwargs=kwargs, firstresult=True).execute()
def canonical_importname(name): def canonical_importname(name):
name = name.lower() name = name.lower()
modprefix = "pytest_" if not name.startswith(IMPORTPREFIX):
if not name.startswith(modprefix): name = IMPORTPREFIX + name
name = modprefix + name
return name return name
def importplugin(importspec): def importplugin(importspec):
@ -247,11 +250,9 @@ def importplugin(importspec):
class MultiCall: class MultiCall:
""" execute a call into multiple python functions/methods. """ """ execute a call into multiple python functions/methods. """
def __init__(self, methods, kwargs, firstresult=False): def __init__(self, methods, kwargs, firstresult=False):
self.methods = methods[:] self.methods = list(methods)
self.kwargs = kwargs.copy() self.kwargs = kwargs
self.kwargs['__multicall__'] = self
self.results = [] self.results = []
self.firstresult = firstresult self.firstresult = firstresult
@ -277,65 +278,26 @@ class MultiCall:
try: try:
kwargs[argname] = self.kwargs[argname] kwargs[argname] = self.kwargs[argname]
except KeyError: except KeyError:
pass # might be optional param if argname == "__multicall__":
kwargs[argname] = self
return kwargs return kwargs
def varnames(func): def varnames(func, cache={}):
import inspect
if not inspect.isfunction(func) and not inspect.ismethod(func): if not inspect.isfunction(func) and not inspect.ismethod(func):
func = getattr(func, '__call__', func) func = getattr(func, '__call__', func)
ismethod = inspect.ismethod(func) ismethod = inspect.ismethod(func)
rawcode = py.code.getrawcode(func) rawcode = py.code.getrawcode(func)
try: try:
return rawcode.co_varnames[ismethod:] return rawcode.co_varnames[ismethod:rawcode.co_argcount]
except AttributeError: except AttributeError:
return () return ()
class Registry:
"""
Manage Plugins: register/unregister call calls to plugins.
"""
def __init__(self, plugins=None):
if plugins is None:
plugins = []
self._plugins = plugins
def register(self, plugin, prepend=False):
assert not isinstance(plugin, str)
assert not plugin in self._plugins
if not prepend:
self._plugins.append(plugin)
else:
self._plugins.insert(0, plugin)
def unregister(self, plugin):
self._plugins.remove(plugin)
def isregistered(self, plugin):
return plugin in self._plugins
def __iter__(self):
return iter(self._plugins)
def listattr(self, attrname, plugins=None, reverse=False):
l = []
if plugins is None:
plugins = self._plugins
for plugin in plugins:
try:
l.append(getattr(plugin, attrname))
except AttributeError:
continue
if reverse:
l.reverse()
return l
class HookRelay: class HookRelay:
def __init__(self, hookspecs, registry, prefix="pytest_"): def __init__(self, hookspecs, pm, prefix="pytest_"):
if not isinstance(hookspecs, list): if not isinstance(hookspecs, list):
hookspecs = [hookspecs] hookspecs = [hookspecs]
self._hookspecs = [] self._hookspecs = []
self._registry = registry self._pm = pm
for hookspec in hookspecs: for hookspec in hookspecs:
self._addhooks(hookspec, prefix) self._addhooks(hookspec, prefix)
@ -357,9 +319,6 @@ class HookRelay:
prefix, hookspecs,)) prefix, hookspecs,))
def _performcall(self, name, multicall):
return multicall.execute()
class HookCaller: class HookCaller:
def __init__(self, hookrelay, name, firstresult): def __init__(self, hookrelay, name, firstresult):
self.hookrelay = hookrelay self.hookrelay = hookrelay
@ -370,16 +329,16 @@ class HookCaller:
return "<HookCaller %r>" %(self.name,) return "<HookCaller %r>" %(self.name,)
def __call__(self, **kwargs): def __call__(self, **kwargs):
methods = self.hookrelay._registry.listattr(self.name) methods = self.hookrelay._pm.listattr(self.name)
mc = MultiCall(methods, kwargs, firstresult=self.firstresult) mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
return self.hookrelay._performcall(self.name, mc) return mc.execute()
def pcall(self, plugins, **kwargs): def pcall(self, plugins, **kwargs):
methods = self.hookrelay._registry.listattr(self.name, plugins=plugins) methods = self.hookrelay._pm.listattr(self.name, plugins=plugins)
mc = MultiCall(methods, kwargs, firstresult=self.firstresult) mc = MultiCall(methods, kwargs, firstresult=self.firstresult)
return self.hookrelay._performcall(self.name, mc) return mc.execute()
pluginmanager = PluginManager() # will trigger default plugin loading pluginmanager = PluginManager(load=True) # will trigger default plugin importing
def main(args=None): def main(args=None):
global pluginmanager global pluginmanager
@ -393,6 +352,6 @@ def main(args=None):
e = sys.exc_info()[1] e = sys.exc_info()[1]
sys.stderr.write("ERROR: %s\n" %(e.args[0],)) sys.stderr.write("ERROR: %s\n" %(e.args[0],))
exitstatus = 3 exitstatus = 3
pluginmanager = PluginManager() pluginmanager = PluginManager(load=True)
return exitstatus return exitstatus

View File

@ -246,7 +246,7 @@ class Config(object):
processopt=self._processopt, processopt=self._processopt,
) )
#: a pluginmanager instance #: a pluginmanager instance
self.pluginmanager = pluginmanager or PluginManager() self.pluginmanager = pluginmanager or PluginManager(load=True)
self._conftest = Conftest(onimport=self._onimportconftest) self._conftest = Conftest(onimport=self._onimportconftest)
self.hook = self.pluginmanager.hook self.hook = self.pluginmanager.hook

View File

@ -27,7 +27,7 @@ class PytestArg:
self.request = request self.request = request
def gethookrecorder(self, hook): def gethookrecorder(self, hook):
hookrecorder = HookRecorder(hook._registry) hookrecorder = HookRecorder(hook._pm)
hookrecorder.start_recording(hook._hookspecs) hookrecorder.start_recording(hook._hookspecs)
self.request.addfinalizer(hookrecorder.finish_recording) self.request.addfinalizer(hookrecorder.finish_recording)
return hookrecorder return hookrecorder
@ -45,8 +45,8 @@ class ParsedCall:
return "<ParsedCall %r(**%r)>" %(self._name, d) return "<ParsedCall %r(**%r)>" %(self._name, d)
class HookRecorder: class HookRecorder:
def __init__(self, registry): def __init__(self, pluginmanager):
self._registry = registry self._pluginmanager = pluginmanager
self.calls = [] self.calls = []
self._recorders = {} self._recorders = {}
@ -62,13 +62,13 @@ class HookRecorder:
setattr(RecordCalls, name, self._makecallparser(method)) setattr(RecordCalls, name, self._makecallparser(method))
recorder = RecordCalls() recorder = RecordCalls()
self._recorders[hookspec] = recorder self._recorders[hookspec] = recorder
self._registry.register(recorder) self._pluginmanager.register(recorder)
self.hook = HookRelay(hookspecs, registry=self._registry, self.hook = HookRelay(hookspecs, pm=self._pluginmanager,
prefix="pytest_") prefix="pytest_")
def finish_recording(self): def finish_recording(self):
for recorder in self._recorders.values(): for recorder in self._recorders.values():
self._registry.unregister(recorder) self._pluginmanager.unregister(recorder)
self._recorders.clear() self._recorders.clear()
def _makecallparser(self, method): def _makecallparser(self, method):
@ -493,8 +493,8 @@ class PseudoPlugin:
class ReportRecorder(object): class ReportRecorder(object):
def __init__(self, hook): def __init__(self, hook):
self.hook = hook self.hook = hook
self.registry = hook._registry self.pluginmanager = hook._pm
self.registry.register(self) self.pluginmanager.register(self)
def getcall(self, name): def getcall(self, name):
return self.hookrecorder.getcall(name) return self.hookrecorder.getcall(name)
@ -558,7 +558,7 @@ class ReportRecorder(object):
self.hookrecorder.calls[:] = [] self.hookrecorder.calls[:] = []
def unregister(self): def unregister(self):
self.registry.unregister(self) self.pluginmanager.unregister(self)
self.hookrecorder.finish_recording() self.hookrecorder.finish_recording()
class LineComp: class LineComp:

View File

@ -1,7 +1,7 @@
import py import py
import os, sys import os, sys
from pytest.plugin.pytester import LineMatcher, LineComp, HookRecorder from pytest.plugin.pytester import LineMatcher, LineComp, HookRecorder
from pytest._core import Registry from pytest._core import PluginManager
def test_reportrecorder(testdir): def test_reportrecorder(testdir):
item = testdir.getitem("def test_func(): pass") item = testdir.getitem("def test_func(): pass")
@ -72,7 +72,7 @@ def test_testdir_runs_with_plugin(testdir):
]) ])
def test_hookrecorder_basic(): def test_hookrecorder_basic():
rec = HookRecorder(Registry()) rec = HookRecorder(PluginManager())
class ApiClass: class ApiClass:
def pytest_xyz(self, arg): def pytest_xyz(self, arg):
"x" "x"
@ -84,7 +84,7 @@ def test_hookrecorder_basic():
py.test.raises(ValueError, "rec.popcall('abc')") py.test.raises(ValueError, "rec.popcall('abc')")
def test_hookrecorder_basic_no_args_hook(): def test_hookrecorder_basic_no_args_hook():
rec = HookRecorder(Registry()) rec = HookRecorder(PluginManager())
apimod = type(os)('api') apimod = type(os)('api')
def pytest_xyz(): def pytest_xyz():
"x" "x"
@ -97,17 +97,17 @@ def test_hookrecorder_basic_no_args_hook():
def test_functional(testdir, linecomp): def test_functional(testdir, linecomp):
reprec = testdir.inline_runsource(""" reprec = testdir.inline_runsource("""
import py import py
from pytest._core import HookRelay, Registry from pytest._core import HookRelay, PluginManager
pytest_plugins="pytester" pytest_plugins="pytester"
def test_func(_pytest): def test_func(_pytest):
class ApiClass: class ApiClass:
def pytest_xyz(self, arg): "x" def pytest_xyz(self, arg): "x"
hook = HookRelay([ApiClass], Registry()) hook = HookRelay([ApiClass], PluginManager(load=False))
rec = _pytest.gethookrecorder(hook) rec = _pytest.gethookrecorder(hook)
class Plugin: class Plugin:
def pytest_xyz(self, arg): def pytest_xyz(self, arg):
return arg + 1 return arg + 1
rec._registry.register(Plugin()) rec._pluginmanager.register(Plugin())
res = rec.hook.pytest_xyz(arg=41) res = rec.hook.pytest_xyz(arg=41)
assert res == [42] assert res == [42]
""") """)

View File

@ -1,6 +1,6 @@
import py, os import py, os
from pytest._core import PluginManager, canonical_importname from pytest._core import PluginManager, canonical_importname
from pytest._core import Registry, MultiCall, HookRelay, varnames from pytest._core import MultiCall, HookRelay, varnames
class TestBootstrapping: class TestBootstrapping:
@ -143,7 +143,7 @@ class TestBootstrapping:
pp = PluginManager() pp = PluginManager()
py.test.raises(ImportError, "pp.consider_conftest(mod)") py.test.raises(ImportError, "pp.consider_conftest(mod)")
def test_registry(self): def test_pm(self):
pp = PluginManager() pp = PluginManager()
class A: pass class A: pass
a1, a2 = A(), A() a1, a2 = A(), A()
@ -160,7 +160,7 @@ class TestBootstrapping:
pp.unregister(a2) pp.unregister(a2)
assert not pp.isregistered(a2) assert not pp.isregistered(a2)
def test_registry_ordering(self): def test_pm_ordering(self):
pp = PluginManager() pp = PluginManager()
class A: pass class A: pass
a1, a2 = A(), A() a1, a2 = A(), A()
@ -182,7 +182,7 @@ class TestBootstrapping:
assert mod in l assert mod in l
py.test.raises(AssertionError, "pp.register(mod)") py.test.raises(AssertionError, "pp.register(mod)")
mod2 = py.std.types.ModuleType("pytest_hello") mod2 = py.std.types.ModuleType("pytest_hello")
#pp.register(mod2) # double registry #pp.register(mod2) # double pm
py.test.raises(AssertionError, "pp.register(mod)") py.test.raises(AssertionError, "pp.register(mod)")
#assert not pp.isregistered(mod2) #assert not pp.isregistered(mod2)
assert pp.getplugins() == l assert pp.getplugins() == l
@ -197,14 +197,14 @@ class TestBootstrapping:
assert pp.isregistered(mod) assert pp.isregistered(mod)
def test_register_mismatch_method(self): def test_register_mismatch_method(self):
pp = PluginManager() pp = PluginManager(load=True)
class hello: class hello:
def pytest_gurgel(self): def pytest_gurgel(self):
pass pass
py.test.raises(Exception, "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(load=True)
class hello: class hello:
def pytest_configure(self, asd): def pytest_configure(self, asd):
pass pass
@ -228,6 +228,36 @@ class TestBootstrapping:
out, err = capfd.readouterr() out, err = capfd.readouterr()
assert not err assert not err
def test_register(self):
pm = PluginManager(load=False)
class MyPlugin:
pass
my = MyPlugin()
pm.register(my)
assert pm.getplugins()
my2 = MyPlugin()
pm.register(my2)
assert pm.getplugins()[1:] == [my, my2]
assert pm.isregistered(my)
assert pm.isregistered(my2)
pm.unregister(my)
assert not pm.isregistered(my)
assert pm.getplugins()[1:] == [my2]
def test_listattr(self):
plugins = PluginManager()
class api1:
x = 41
class api2:
x = 42
class api3:
x = 43
plugins.register(api1())
plugins.register(api2())
plugins.register(api3())
l = list(plugins.listattr('x'))
assert l == [41, 42, 43]
class TestPytestPluginInteractions: class TestPytestPluginInteractions:
@ -370,7 +400,7 @@ def test_namespace_has_default_and_env_plugins(testdir):
def test_varnames(): def test_varnames():
def f(x): def f(x):
pass i = 3
class A: class A:
def f(self, y): def f(self, y):
pass pass
@ -425,6 +455,14 @@ class TestMultiCall:
assert reslist == [24+23, 24] assert reslist == [24+23, 24]
assert "2 results" in repr(multicall) assert "2 results" in repr(multicall)
def test_keyword_args_with_defaultargs(self):
def f(x, z=1):
return x + z
reslist = MultiCall([f], dict(x=23, y=24)).execute()
assert reslist == [24]
reslist = MultiCall([f], dict(x=23, z=2)).execute()
assert reslist == [25]
def test_keywords_call_error(self): def test_keywords_call_error(self):
multicall = MultiCall([lambda x: x], {}) multicall = MultiCall([lambda x: x], {})
py.test.raises(TypeError, "multicall.execute()") py.test.raises(TypeError, "multicall.execute()")
@ -451,79 +489,44 @@ class TestMultiCall:
res = MultiCall([m1, m2], {}).execute() res = MultiCall([m1, m2], {}).execute()
assert res == [1] assert res == [1]
class TestRegistry:
def test_register(self):
registry = Registry()
class MyPlugin:
pass
my = MyPlugin()
registry.register(my)
assert list(registry) == [my]
my2 = MyPlugin()
registry.register(my2)
assert list(registry) == [my, my2]
assert registry.isregistered(my)
assert registry.isregistered(my2)
registry.unregister(my)
assert not registry.isregistered(my)
assert list(registry) == [my2]
def test_listattr(self):
plugins = Registry()
class api1:
x = 41
class api2:
x = 42
class api3:
x = 43
plugins.register(api1())
plugins.register(api2())
plugins.register(api3())
l = list(plugins.listattr('x'))
assert l == [41, 42, 43]
l = list(plugins.listattr('x', reverse=True))
assert l == [43, 42, 41]
class TestHookRelay: class TestHookRelay:
def test_happypath(self): def test_happypath(self):
registry = Registry() pm = PluginManager()
class Api: class Api:
def hello(self, arg): def hello(self, arg):
"api hook 1" "api hook 1"
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
assert hasattr(mcm, 'hello') assert hasattr(mcm, 'hello')
assert repr(mcm.hello).find("hello") != -1 assert repr(mcm.hello).find("hello") != -1
class Plugin: class Plugin:
def hello(self, arg): def hello(self, arg):
return arg + 1 return arg + 1
registry.register(Plugin()) pm.register(Plugin())
l = mcm.hello(arg=3) l = mcm.hello(arg=3)
assert l == [4] assert l == [4]
assert not hasattr(mcm, 'world') assert not hasattr(mcm, 'world')
def test_only_kwargs(self): def test_only_kwargs(self):
registry = Registry() pm = PluginManager()
class Api: class Api:
def hello(self, arg): def hello(self, arg):
"api hook 1" "api hook 1"
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
py.test.raises(TypeError, "mcm.hello(3)") py.test.raises(TypeError, "mcm.hello(3)")
def test_firstresult_definition(self): def test_firstresult_definition(self):
registry = Registry() pm = PluginManager()
class Api: class Api:
def hello(self, arg): def hello(self, arg):
"api hook 1" "api hook 1"
hello.firstresult = True hello.firstresult = True
mcm = HookRelay(hookspecs=Api, registry=registry, prefix="he") mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he")
class Plugin: class Plugin:
def hello(self, arg): def hello(self, arg):
return arg + 1 return arg + 1
registry.register(Plugin()) pm.register(Plugin())
res = mcm.hello(arg=3) res = mcm.hello(arg=3)
assert res == 4 assert res == 4