123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600 |
- """
- ``PluginManager`` unit and public API testing.
- """
- import pytest
- import types
- from pluggy import (
- PluginManager,
- PluginValidationError,
- HookCallError,
- HookimplMarker,
- HookspecMarker,
- )
- from pluggy.manager import importlib_metadata
- hookspec = HookspecMarker("example")
- hookimpl = HookimplMarker("example")
- def test_plugin_double_register(pm):
- """Registering the same plugin more then once isn't allowed"""
- pm.register(42, name="abc")
- with pytest.raises(ValueError):
- pm.register(42, name="abc")
- with pytest.raises(ValueError):
- pm.register(42, name="def")
- def test_pm(pm):
- """Basic registration with objects"""
- class A(object):
- pass
- a1, a2 = A(), A()
- pm.register(a1)
- assert pm.is_registered(a1)
- pm.register(a2, "hello")
- assert pm.is_registered(a2)
- out = pm.get_plugins()
- assert a1 in out
- assert a2 in out
- assert pm.get_plugin("hello") == a2
- assert pm.unregister(a1) == a1
- assert not pm.is_registered(a1)
- out = pm.list_name_plugin()
- assert len(out) == 1
- assert out == [("hello", a2)]
- def test_has_plugin(pm):
- class A(object):
- pass
- a1 = A()
- pm.register(a1, "hello")
- assert pm.is_registered(a1)
- assert pm.has_plugin("hello")
- def test_register_dynamic_attr(he_pm):
- class A(object):
- def __getattr__(self, name):
- if name[0] != "_":
- return 42
- raise AttributeError()
- a = A()
- he_pm.register(a)
- assert not he_pm.get_hookcallers(a)
- def test_pm_name(pm):
- class A(object):
- pass
- a1 = A()
- name = pm.register(a1, name="hello")
- assert name == "hello"
- pm.unregister(a1)
- assert pm.get_plugin(a1) is None
- assert not pm.is_registered(a1)
- assert not pm.get_plugins()
- name2 = pm.register(a1, name="hello")
- assert name2 == name
- pm.unregister(name="hello")
- assert pm.get_plugin(a1) is None
- assert not pm.is_registered(a1)
- assert not pm.get_plugins()
- def test_set_blocked(pm):
- class A(object):
- pass
- a1 = A()
- name = pm.register(a1)
- assert pm.is_registered(a1)
- assert not pm.is_blocked(name)
- pm.set_blocked(name)
- assert pm.is_blocked(name)
- assert not pm.is_registered(a1)
- pm.set_blocked("somename")
- assert pm.is_blocked("somename")
- assert not pm.register(A(), "somename")
- pm.unregister(name="somename")
- assert pm.is_blocked("somename")
- def test_register_mismatch_method(he_pm):
- class hello(object):
- @hookimpl
- def he_method_notexists(self):
- pass
- plugin = hello()
- he_pm.register(plugin)
- with pytest.raises(PluginValidationError) as excinfo:
- he_pm.check_pending()
- assert excinfo.value.plugin is plugin
- def test_register_mismatch_arg(he_pm):
- class hello(object):
- @hookimpl
- def he_method1(self, qlwkje):
- pass
- plugin = hello()
- with pytest.raises(PluginValidationError) as excinfo:
- he_pm.register(plugin)
- assert excinfo.value.plugin is plugin
- def test_register(pm):
- class MyPlugin(object):
- pass
- my = MyPlugin()
- pm.register(my)
- assert my in pm.get_plugins()
- my2 = MyPlugin()
- pm.register(my2)
- assert set([my, my2]).issubset(pm.get_plugins())
- assert pm.is_registered(my)
- assert pm.is_registered(my2)
- pm.unregister(my)
- assert not pm.is_registered(my)
- assert my not in pm.get_plugins()
- def test_register_unknown_hooks(pm):
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- return arg + 1
- pname = pm.register(Plugin1())
- class Hooks(object):
- @hookspec
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- # assert not pm._unverified_hooks
- assert pm.hook.he_method1(arg=1) == [2]
- assert len(pm.get_hookcallers(pm.get_plugin(pname))) == 1
- def test_register_historic(pm):
- class Hooks(object):
- @hookspec(historic=True)
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- pm.hook.he_method1.call_historic(kwargs=dict(arg=1))
- out = []
- class Plugin(object):
- @hookimpl
- def he_method1(self, arg):
- out.append(arg)
- pm.register(Plugin())
- assert out == [1]
- class Plugin2(object):
- @hookimpl
- def he_method1(self, arg):
- out.append(arg * 10)
- pm.register(Plugin2())
- assert out == [1, 10]
- pm.hook.he_method1.call_historic(kwargs=dict(arg=12))
- assert out == [1, 10, 120, 12]
- @pytest.mark.parametrize("result_callback", [True, False])
- def test_with_result_memorized(pm, result_callback):
- """Verify that ``_HookCaller._maybe_apply_history()`
- correctly applies the ``result_callback`` function, when provided,
- to the result from calling each newly registered hook.
- """
- out = []
- if result_callback:
- def callback(res):
- out.append(res)
- else:
- callback = None
- class Hooks(object):
- @hookspec(historic=True)
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- return arg * 10
- pm.register(Plugin1())
- he_method1 = pm.hook.he_method1
- he_method1.call_historic(result_callback=callback, kwargs=dict(arg=1))
- class Plugin2(object):
- @hookimpl
- def he_method1(self, arg):
- return arg * 10
- pm.register(Plugin2())
- if result_callback:
- assert out == [10, 10]
- else:
- assert out == []
- def test_with_callbacks_immediately_executed(pm):
- class Hooks(object):
- @hookspec(historic=True)
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- return arg * 10
- class Plugin2(object):
- @hookimpl
- def he_method1(self, arg):
- return arg * 20
- class Plugin3(object):
- @hookimpl
- def he_method1(self, arg):
- return arg * 30
- out = []
- pm.register(Plugin1())
- pm.register(Plugin2())
- he_method1 = pm.hook.he_method1
- he_method1.call_historic(lambda res: out.append(res), dict(arg=1))
- assert out == [20, 10]
- pm.register(Plugin3())
- assert out == [20, 10, 30]
- def test_register_historic_incompat_hookwrapper(pm):
- class Hooks(object):
- @hookspec(historic=True)
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- out = []
- class Plugin(object):
- @hookimpl(hookwrapper=True)
- def he_method1(self, arg):
- out.append(arg)
- with pytest.raises(PluginValidationError):
- pm.register(Plugin())
- def test_call_extra(pm):
- class Hooks(object):
- @hookspec
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- def he_method1(arg):
- return arg * 10
- out = pm.hook.he_method1.call_extra([he_method1], dict(arg=1))
- assert out == [10]
- def test_call_with_too_few_args(pm):
- class Hooks(object):
- @hookspec
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- 0 / 0
- pm.register(Plugin1())
- with pytest.raises(HookCallError):
- with pytest.warns(UserWarning):
- pm.hook.he_method1()
- def test_subset_hook_caller(pm):
- class Hooks(object):
- @hookspec
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- out = []
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- out.append(arg)
- class Plugin2(object):
- @hookimpl
- def he_method1(self, arg):
- out.append(arg * 10)
- class PluginNo(object):
- pass
- plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo()
- pm.register(plugin1)
- pm.register(plugin2)
- pm.register(plugin3)
- pm.hook.he_method1(arg=1)
- assert out == [10, 1]
- out[:] = []
- hc = pm.subset_hook_caller("he_method1", [plugin1])
- hc(arg=2)
- assert out == [20]
- out[:] = []
- hc = pm.subset_hook_caller("he_method1", [plugin2])
- hc(arg=2)
- assert out == [2]
- out[:] = []
- pm.unregister(plugin1)
- hc(arg=2)
- assert out == []
- out[:] = []
- pm.hook.he_method1(arg=1)
- assert out == [10]
- def test_get_hookimpls(pm):
- class Hooks(object):
- @hookspec
- def he_method1(self, arg):
- pass
- pm.add_hookspecs(Hooks)
- assert pm.hook.he_method1.get_hookimpls() == []
- class Plugin1(object):
- @hookimpl
- def he_method1(self, arg):
- pass
- class Plugin2(object):
- @hookimpl
- def he_method1(self, arg):
- pass
- class PluginNo(object):
- pass
- plugin1, plugin2, plugin3 = Plugin1(), Plugin2(), PluginNo()
- pm.register(plugin1)
- pm.register(plugin2)
- pm.register(plugin3)
- hookimpls = pm.hook.he_method1.get_hookimpls()
- hook_plugins = [item.plugin for item in hookimpls]
- assert hook_plugins == [plugin1, plugin2]
- def test_add_hookspecs_nohooks(pm):
- with pytest.raises(ValueError):
- pm.add_hookspecs(10)
- def test_reject_prefixed_module(pm):
- """Verify that a module type attribute that contains the project
- prefix in its name (in this case `'example_*'` isn't collected
- when registering a module which imports it.
- """
- pm._implprefix = "example"
- conftest = types.ModuleType("conftest")
- src = """
- def example_hook():
- pass
- """
- exec(src, conftest.__dict__)
- conftest.example_blah = types.ModuleType("example_blah")
- with pytest.deprecated_call():
- name = pm.register(conftest)
- assert name == "conftest"
- assert getattr(pm.hook, "example_blah", None) is None
- assert getattr(
- pm.hook, "example_hook", None
- ) # conftest.example_hook should be collected
- with pytest.deprecated_call():
- assert pm.parse_hookimpl_opts(conftest, "example_blah") is None
- assert pm.parse_hookimpl_opts(conftest, "example_hook") == {}
- def test_load_setuptools_instantiation(monkeypatch, pm):
- class EntryPoint(object):
- name = "myname"
- group = "hello"
- value = "myname:foo"
- def load(self):
- class PseudoPlugin(object):
- x = 42
- return PseudoPlugin()
- class Distribution(object):
- entry_points = (EntryPoint(),)
- dist = Distribution()
- def my_distributions():
- return (dist,)
- monkeypatch.setattr(importlib_metadata, "distributions", my_distributions)
- num = pm.load_setuptools_entrypoints("hello")
- assert num == 1
- plugin = pm.get_plugin("myname")
- assert plugin.x == 42
- ret = pm.list_plugin_distinfo()
- # poor man's `assert ret == [(plugin, mock.ANY)]`
- assert len(ret) == 1
- assert len(ret[0]) == 2
- assert ret[0][0] == plugin
- assert ret[0][1]._dist == dist
- num = pm.load_setuptools_entrypoints("hello")
- assert num == 0 # no plugin loaded by this call
- def test_add_tracefuncs(he_pm):
- out = []
- class api1(object):
- @hookimpl
- def he_method1(self):
- out.append("he_method1-api1")
- class api2(object):
- @hookimpl
- def he_method1(self):
- out.append("he_method1-api2")
- he_pm.register(api1())
- he_pm.register(api2())
- def before(hook_name, hook_impls, kwargs):
- out.append((hook_name, list(hook_impls), kwargs))
- def after(outcome, hook_name, hook_impls, kwargs):
- out.append((outcome, hook_name, list(hook_impls), kwargs))
- undo = he_pm.add_hookcall_monitoring(before, after)
- he_pm.hook.he_method1(arg=1)
- assert len(out) == 4
- assert out[0][0] == "he_method1"
- assert len(out[0][1]) == 2
- assert isinstance(out[0][2], dict)
- assert out[1] == "he_method1-api2"
- assert out[2] == "he_method1-api1"
- assert len(out[3]) == 4
- assert out[3][1] == out[0][0]
- undo()
- he_pm.hook.he_method1(arg=1)
- assert len(out) == 4 + 2
- def test_hook_tracing(he_pm):
- saveindent = []
- class api1(object):
- @hookimpl
- def he_method1(self):
- saveindent.append(he_pm.trace.root.indent)
- class api2(object):
- @hookimpl
- def he_method1(self):
- saveindent.append(he_pm.trace.root.indent)
- raise ValueError()
- he_pm.register(api1())
- out = []
- he_pm.trace.root.setwriter(out.append)
- undo = he_pm.enable_tracing()
- try:
- indent = he_pm.trace.root.indent
- he_pm.hook.he_method1(arg=1)
- assert indent == he_pm.trace.root.indent
- assert len(out) == 2
- assert "he_method1" in out[0]
- assert "finish" in out[1]
- out[:] = []
- he_pm.register(api2())
- with pytest.raises(ValueError):
- he_pm.hook.he_method1(arg=1)
- assert he_pm.trace.root.indent == indent
- assert saveindent[0] > indent
- finally:
- undo()
- def test_implprefix_warning(recwarn):
- PluginManager(hookspec.project_name, "hello_")
- w = recwarn.pop(DeprecationWarning)
- assert "test_pluginmanager.py" in w.filename
- @pytest.mark.parametrize("include_hookspec", [True, False])
- def test_prefix_hookimpl(include_hookspec):
- with pytest.deprecated_call():
- pm = PluginManager(hookspec.project_name, "hello_")
- if include_hookspec:
- class HookSpec(object):
- @hookspec
- def hello_myhook(self, arg1):
- """ add to arg1 """
- pm.add_hookspecs(HookSpec)
- class Plugin(object):
- def hello_myhook(self, arg1):
- return arg1 + 1
- with pytest.deprecated_call():
- pm.register(Plugin())
- pm.register(Plugin())
- results = pm.hook.hello_myhook(arg1=17)
- assert results == [18, 18]
- def test_prefix_hookimpl_dontmatch_module():
- with pytest.deprecated_call():
- pm = PluginManager(hookspec.project_name, "hello_")
- class BadPlugin(object):
- hello_module = __import__("email")
- pm.register(BadPlugin())
- pm.check_pending()
|