| import pytest |
| from pluggy import PluginValidationError, HookimplMarker, HookspecMarker |
| |
| |
| hookspec = HookspecMarker("example") |
| hookimpl = HookimplMarker("example") |
| |
| |
| def test_argmismatch(pm): |
| class Api(object): |
| @hookspec |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| |
| class Plugin(object): |
| @hookimpl |
| def hello(self, argwrong): |
| pass |
| |
| with pytest.raises(PluginValidationError) as exc: |
| pm.register(Plugin()) |
| |
| assert "argwrong" in str(exc.value) |
| |
| |
| def test_only_kwargs(pm): |
| class Api(object): |
| @hookspec |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| with pytest.raises(TypeError) as exc: |
| pm.hook.hello(3) |
| |
| comprehensible = "hook calling supports only keyword arguments" |
| assert comprehensible in str(exc.value) |
| |
| |
| def test_opt_in_args(pm): |
| """Verfiy that two hookimpls with mutex args can serve |
| under the same spec. |
| """ |
| |
| class Api(object): |
| @hookspec |
| def hello(self, arg1, arg2, common_arg): |
| "api hook 1" |
| |
| class Plugin1(object): |
| @hookimpl |
| def hello(self, arg1, common_arg): |
| return arg1 + common_arg |
| |
| class Plugin2(object): |
| @hookimpl |
| def hello(self, arg2, common_arg): |
| return arg2 + common_arg |
| |
| pm.add_hookspecs(Api) |
| pm.register(Plugin1()) |
| pm.register(Plugin2()) |
| |
| results = pm.hook.hello(arg1=1, arg2=2, common_arg=0) |
| assert results == [2, 1] |
| |
| |
| def test_call_order(pm): |
| class Api(object): |
| @hookspec |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| |
| class Plugin1(object): |
| @hookimpl |
| def hello(self, arg): |
| return 1 |
| |
| class Plugin2(object): |
| @hookimpl |
| def hello(self, arg): |
| return 2 |
| |
| class Plugin3(object): |
| @hookimpl |
| def hello(self, arg): |
| return 3 |
| |
| class Plugin4(object): |
| @hookimpl(hookwrapper=True) |
| def hello(self, arg): |
| assert arg == 0 |
| outcome = yield |
| assert outcome.get_result() == [3, 2, 1] |
| |
| pm.register(Plugin1()) |
| pm.register(Plugin2()) |
| pm.register(Plugin3()) |
| pm.register(Plugin4()) # hookwrapper should get same list result |
| res = pm.hook.hello(arg=0) |
| assert res == [3, 2, 1] |
| |
| |
| def test_firstresult_definition(pm): |
| class Api(object): |
| @hookspec(firstresult=True) |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| |
| class Plugin1(object): |
| @hookimpl |
| def hello(self, arg): |
| return arg + 1 |
| |
| class Plugin2(object): |
| @hookimpl |
| def hello(self, arg): |
| return arg - 1 |
| |
| class Plugin3(object): |
| @hookimpl |
| def hello(self, arg): |
| return None |
| |
| class Plugin4(object): |
| @hookimpl(hookwrapper=True) |
| def hello(self, arg): |
| assert arg == 3 |
| outcome = yield |
| assert outcome.get_result() == 2 |
| |
| pm.register(Plugin1()) # discarded - not the last registered plugin |
| pm.register(Plugin2()) # used as result |
| pm.register(Plugin3()) # None result is ignored |
| pm.register(Plugin4()) # hookwrapper should get same non-list result |
| res = pm.hook.hello(arg=3) |
| assert res == 2 |
| |
| |
| def test_firstresult_force_result(pm): |
| """Verify forcing a result in a wrapper. |
| """ |
| |
| class Api(object): |
| @hookspec(firstresult=True) |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| |
| class Plugin1(object): |
| @hookimpl |
| def hello(self, arg): |
| return arg + 1 |
| |
| class Plugin2(object): |
| @hookimpl(hookwrapper=True) |
| def hello(self, arg): |
| assert arg == 3 |
| outcome = yield |
| assert outcome.get_result() == 4 |
| outcome.force_result(0) |
| |
| class Plugin3(object): |
| @hookimpl |
| def hello(self, arg): |
| return None |
| |
| pm.register(Plugin1()) |
| pm.register(Plugin2()) # wrapper |
| pm.register(Plugin3()) # ignored since returns None |
| res = pm.hook.hello(arg=3) |
| assert res == 0 # this result is forced and not a list |
| |
| |
| def test_firstresult_returns_none(pm): |
| """If None results are returned by underlying implementations ensure |
| the multi-call loop returns a None value. |
| """ |
| |
| class Api(object): |
| @hookspec(firstresult=True) |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| |
| class Plugin1(object): |
| @hookimpl |
| def hello(self, arg): |
| return None |
| |
| pm.register(Plugin1()) |
| res = pm.hook.hello(arg=3) |
| assert res is None |
| |
| |
| def test_firstresult_no_plugin(pm): |
| """If no implementations/plugins have been registered for a firstresult |
| hook the multi-call loop should return a None value. |
| """ |
| |
| class Api(object): |
| @hookspec(firstresult=True) |
| def hello(self, arg): |
| "api hook 1" |
| |
| pm.add_hookspecs(Api) |
| res = pm.hook.hello(arg=3) |
| assert res is None |