diff --git a/_pytest/core.py b/_pytest/core.py index 316a9102a..9273564ba 100644 --- a/_pytest/core.py +++ b/_pytest/core.py @@ -139,6 +139,7 @@ class PluginManager(object): self._name2plugin = {} self._plugins = [] self._conftestplugins = [] + self._plugin2hookcallers = {} self._warnings = [] self.trace = TagTracer().get("pluginmanage") self._plugin_distinfo = [] @@ -182,7 +183,8 @@ class PluginManager(object): reg = getattr(self, "_registercallback", None) if reg is not None: reg(plugin, name) # may call addhooks - self.hook._scan_plugin(plugin) + hookcallers = list(self.hook._scan_plugin(plugin)) + self._plugin2hookcallers[plugin] = hookcallers self._name2plugin[name] = plugin if conftest: self._conftestplugins.append(plugin) @@ -191,11 +193,12 @@ class PluginManager(object): self._plugins.append(plugin) else: self._plugins.insert(0, plugin) + # finally make sure that the methods of the new plugin take part + for hookcaller in hookcallers: + hookcaller.scan_methods() return True - def unregister(self, plugin=None, name=None): - if plugin is None: - plugin = self.getplugin(name=name) + def unregister(self, plugin): try: self._plugins.remove(plugin) except KeyError: @@ -203,6 +206,9 @@ class PluginManager(object): for name, value in list(self._name2plugin.items()): if value == plugin: del self._name2plugin[name] + hookcallers = self._plugin2hookcallers.pop(plugin) + for hookcaller in hookcallers: + hookcaller.scan_methods() def add_shutdown(self, func): self._shutdown.append(func) @@ -217,9 +223,7 @@ class PluginManager(object): def isregistered(self, plugin, name=None): if self.getplugin(name) is not None: return True - for val in self._name2plugin.values(): - if plugin == val: - return True + return plugin in self._plugins or plugin in self._conftestplugins def addhooks(self, spec, prefix="pytest_"): self.hook._addhooks(spec, prefix=prefix) @@ -281,8 +285,9 @@ class PluginManager(object): def consider_pluginarg(self, arg): if arg.startswith("no:"): name = arg[3:] - if self.getplugin(name) is not None: - self.unregister(None, name=name) + plugin = self.getplugin(name) + if plugin is not None: + self.unregister(plugin) self._name2plugin[name] = -1 else: if self.getplugin(arg) is None: @@ -485,18 +490,18 @@ class HookRelay: "available hookargs: %s", arg, formatdef(method), ", ".join(hook.argnames)) - getattr(self, name).clear_method_cache() + yield hook class HookCaller: - def __init__(self, hookrelay, name, firstresult, argnames, methods=None): + def __init__(self, hookrelay, name, firstresult, argnames, methods=()): self.hookrelay = hookrelay self.name = name self.firstresult = firstresult - self.methods = methods self.argnames = ["__multicall__"] self.argnames.extend(argnames) - assert "self" not in argnames # prevent oversights + assert "self" not in argnames # sanity check + self.methods = methods def new_cached_caller(self, methods): return HookCaller(self.hookrelay, self.name, self.firstresult, @@ -505,14 +510,11 @@ class HookCaller: def __repr__(self): return "" %(self.name,) - def clear_method_cache(self): - self.methods = None + def scan_methods(self): + self.methods = self.hookrelay._pm.listattr(self.name) def __call__(self, **kwargs): - methods = self.methods - if methods is None: - self.methods = methods = self.hookrelay._pm.listattr(self.name) - return self._docall(methods, kwargs) + return self._docall(self.methods, kwargs) def callextra(self, methods, **kwargs): return self._docall(self.methods + methods, kwargs) diff --git a/testing/test_core.py b/testing/test_core.py index 76b8fc5b3..1a6d20f06 100644 --- a/testing/test_core.py +++ b/testing/test_core.py @@ -184,8 +184,6 @@ class TestBootstrapping: assert pp.getplugin('hello') == a2 pp.unregister(a1) assert not pp.isregistered(a1) - pp.unregister(name="hello") - assert not pp.isregistered(a2) def test_pm_ordering(self): pp = PluginManager() @@ -612,21 +610,23 @@ class TestMultiCall: class TestHookRelay: def test_happypath(self): - pm = PluginManager() class Api: def hello(self, arg): "api hook 1" - - mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he") - assert hasattr(mcm, 'hello') - assert repr(mcm.hello).find("hello") != -1 + pm = PluginManager([Api], prefix="he") + hook = pm.hook + assert hasattr(hook, 'hello') + assert repr(hook.hello).find("hello") != -1 class Plugin: def hello(self, arg): return arg + 1 - pm.register(Plugin()) - l = mcm.hello(arg=3) + plugin = Plugin() + pm.register(plugin) + l = hook.hello(arg=3) assert l == [4] - assert not hasattr(mcm, 'world') + assert not hasattr(hook, 'world') + pm.unregister(plugin) + assert hook.hello(arg=3) == [] def test_argmismatch(self): class Api: @@ -649,18 +649,16 @@ class TestHookRelay: pytest.raises(TypeError, lambda: mcm.hello(3)) def test_firstresult_definition(self): - pm = PluginManager() class Api: def hello(self, arg): "api hook 1" hello.firstresult = True - - mcm = HookRelay(hookspecs=Api, pm=pm, prefix="he") + pm = PluginManager([Api], "he") class Plugin: def hello(self, arg): return arg + 1 pm.register(Plugin()) - res = mcm.hello(arg=3) + res = pm.hook.hello(arg=3) assert res == 4 class TestTracer: