123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450 |
- from typing import Callable
- from typing import List
- from typing import Sequence
- from typing import TypeVar
- import pytest
- from pluggy import HookimplMarker
- from pluggy import HookspecMarker
- from pluggy import PluginManager
- from pluggy import PluginValidationError
- from pluggy._hooks import _HookCaller
- from pluggy._hooks import HookImpl
- hookspec = HookspecMarker("example")
- hookimpl = HookimplMarker("example")
- @pytest.fixture
- def hc(pm: PluginManager) -> _HookCaller:
- class Hooks:
- @hookspec
- def he_method1(self, arg: object) -> None:
- pass
- pm.add_hookspecs(Hooks)
- return pm.hook.he_method1
- FuncT = TypeVar("FuncT", bound=Callable[..., object])
- class AddMeth:
- def __init__(self, hc: _HookCaller) -> None:
- self.hc = hc
- def __call__(
- self,
- tryfirst: bool = False,
- trylast: bool = False,
- hookwrapper: bool = False,
- wrapper: bool = False,
- ) -> Callable[[FuncT], FuncT]:
- def wrap(func: FuncT) -> FuncT:
- hookimpl(
- tryfirst=tryfirst,
- trylast=trylast,
- hookwrapper=hookwrapper,
- wrapper=wrapper,
- )(func)
- self.hc._add_hookimpl(
- HookImpl(None, "<temp>", func, func.example_impl), # type: ignore[attr-defined]
- )
- return func
- return wrap
- @pytest.fixture
- def addmeth(hc: _HookCaller) -> AddMeth:
- return AddMeth(hc)
- def funcs(hookmethods: Sequence[HookImpl]) -> List[Callable[..., object]]:
- return [hookmethod.function for hookmethod in hookmethods]
- def test_adding_nonwrappers(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth()
- def he_method1() -> None:
- pass
- @addmeth()
- def he_method2() -> None:
- pass
- @addmeth()
- def he_method3() -> None:
- pass
- assert funcs(hc.get_hookimpls()) == [he_method1, he_method2, he_method3]
- def test_adding_nonwrappers_trylast(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth()
- def he_method1_middle() -> None:
- pass
- @addmeth(trylast=True)
- def he_method1() -> None:
- pass
- @addmeth()
- def he_method1_b() -> None:
- pass
- assert funcs(hc.get_hookimpls()) == [he_method1, he_method1_middle, he_method1_b]
- def test_adding_nonwrappers_trylast3(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth()
- def he_method1_a() -> None:
- pass
- @addmeth(trylast=True)
- def he_method1_b() -> None:
- pass
- @addmeth()
- def he_method1_c() -> None:
- pass
- @addmeth(trylast=True)
- def he_method1_d() -> None:
- pass
- assert funcs(hc.get_hookimpls()) == [
- he_method1_d,
- he_method1_b,
- he_method1_a,
- he_method1_c,
- ]
- def test_adding_nonwrappers_trylast2(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth()
- def he_method1_middle() -> None:
- pass
- @addmeth()
- def he_method1_b() -> None:
- pass
- @addmeth(trylast=True)
- def he_method1() -> None:
- pass
- assert funcs(hc.get_hookimpls()) == [he_method1, he_method1_middle, he_method1_b]
- def test_adding_nonwrappers_tryfirst(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth(tryfirst=True)
- def he_method1() -> None:
- pass
- @addmeth()
- def he_method1_middle() -> None:
- pass
- @addmeth()
- def he_method1_b() -> None:
- pass
- assert funcs(hc.get_hookimpls()) == [he_method1_middle, he_method1_b, he_method1]
- def test_adding_wrappers_ordering(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth(hookwrapper=True)
- def he_method1():
- yield
- @addmeth(wrapper=True)
- def he_method1_fun():
- yield
- @addmeth()
- def he_method1_middle():
- return
- @addmeth(hookwrapper=True)
- def he_method3_fun():
- yield
- @addmeth(hookwrapper=True)
- def he_method3():
- yield
- assert funcs(hc.get_hookimpls()) == [
- he_method1_middle,
- he_method1,
- he_method1_fun,
- he_method3_fun,
- he_method3,
- ]
- def test_adding_wrappers_ordering_tryfirst(hc: _HookCaller, addmeth: AddMeth) -> None:
- @addmeth(hookwrapper=True, tryfirst=True)
- def he_method1():
- yield
- @addmeth(hookwrapper=True)
- def he_method2():
- yield
- @addmeth(wrapper=True, tryfirst=True)
- def he_method3():
- yield
- assert funcs(hc.get_hookimpls()) == [he_method2, he_method1, he_method3]
- def test_adding_wrappers_complex(hc: _HookCaller, addmeth: AddMeth) -> None:
- assert funcs(hc.get_hookimpls()) == []
- @addmeth(hookwrapper=True, trylast=True)
- def m1():
- yield
- assert funcs(hc.get_hookimpls()) == [m1]
- @addmeth()
- def m2() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m2, m1]
- @addmeth(trylast=True)
- def m3() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m3, m2, m1]
- @addmeth(hookwrapper=True)
- def m4() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m3, m2, m1, m4]
- @addmeth(wrapper=True, tryfirst=True)
- def m5():
- yield
- assert funcs(hc.get_hookimpls()) == [m3, m2, m1, m4, m5]
- @addmeth(tryfirst=True)
- def m6() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m3, m2, m6, m1, m4, m5]
- @addmeth()
- def m7() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m3, m2, m7, m6, m1, m4, m5]
- @addmeth(wrapper=True)
- def m8():
- yield
- assert funcs(hc.get_hookimpls()) == [m3, m2, m7, m6, m1, m4, m8, m5]
- @addmeth(trylast=True)
- def m9() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m1, m4, m8, m5]
- @addmeth(tryfirst=True)
- def m10() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m10, m1, m4, m8, m5]
- @addmeth(hookwrapper=True, trylast=True)
- def m11() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [m9, m3, m2, m7, m6, m10, m11, m1, m4, m8, m5]
- @addmeth(wrapper=True)
- def m12():
- yield
- assert funcs(hc.get_hookimpls()) == [
- m9,
- m3,
- m2,
- m7,
- m6,
- m10,
- m11,
- m1,
- m4,
- m8,
- m12,
- m5,
- ]
- @addmeth()
- def m13() -> None:
- ...
- assert funcs(hc.get_hookimpls()) == [
- m9,
- m3,
- m2,
- m7,
- m13,
- m6,
- m10,
- m11,
- m1,
- m4,
- m8,
- m12,
- m5,
- ]
- def test_hookspec(pm: PluginManager) -> None:
- class HookSpec:
- @hookspec()
- def he_myhook1(arg1) -> None:
- pass
- @hookspec(firstresult=True)
- def he_myhook2(arg1) -> None:
- pass
- @hookspec(firstresult=False)
- def he_myhook3(arg1) -> None:
- pass
- pm.add_hookspecs(HookSpec)
- assert pm.hook.he_myhook1.spec is not None
- assert not pm.hook.he_myhook1.spec.opts["firstresult"]
- assert pm.hook.he_myhook2.spec is not None
- assert pm.hook.he_myhook2.spec.opts["firstresult"]
- assert pm.hook.he_myhook3.spec is not None
- assert not pm.hook.he_myhook3.spec.opts["firstresult"]
- @pytest.mark.parametrize("name", ["hookwrapper", "optionalhook", "tryfirst", "trylast"])
- @pytest.mark.parametrize("val", [True, False])
- def test_hookimpl(name: str, val: bool) -> None:
- @hookimpl(**{name: val}) # type: ignore[misc,call-overload]
- def he_myhook1(arg1) -> None:
- pass
- if val:
- assert he_myhook1.example_impl.get(name)
- else:
- assert not hasattr(he_myhook1, name)
- def test_hookrelay_registry(pm: PluginManager) -> None:
- """Verify hook caller instances are registered by name onto the relay
- and can be likewise unregistered."""
- class Api:
- @hookspec
- def hello(self, arg: object) -> None:
- "api hook 1"
- pm.add_hookspecs(Api)
- hook = pm.hook
- assert hasattr(hook, "hello")
- assert repr(hook.hello).find("hello") != -1
- class Plugin:
- @hookimpl
- def hello(self, arg):
- return arg + 1
- plugin = Plugin()
- pm.register(plugin)
- out = hook.hello(arg=3)
- assert out == [4]
- assert not hasattr(hook, "world")
- pm.unregister(plugin)
- assert hook.hello(arg=3) == []
- def test_hookrelay_registration_by_specname(pm: PluginManager) -> None:
- """Verify hook caller instances may also be registered by specifying a
- specname option to the hookimpl"""
- class Api:
- @hookspec
- def hello(self, arg: object) -> None:
- "api hook 1"
- pm.add_hookspecs(Api)
- hook = pm.hook
- assert hasattr(hook, "hello")
- assert len(pm.hook.hello.get_hookimpls()) == 0
- class Plugin:
- @hookimpl(specname="hello")
- def foo(self, arg: int) -> int:
- return arg + 1
- plugin = Plugin()
- pm.register(plugin)
- out = hook.hello(arg=3)
- assert out == [4]
- def test_hookrelay_registration_by_specname_raises(pm: PluginManager) -> None:
- """Verify using specname still raises the types of errors during registration as it
- would have without using specname."""
- class Api:
- @hookspec
- def hello(self, arg: object) -> None:
- "api hook 1"
- pm.add_hookspecs(Api)
- # make sure a bad signature still raises an error when using specname
- class Plugin:
- @hookimpl(specname="hello")
- def foo(self, arg: int, too, many, args) -> int:
- return arg + 1
- with pytest.raises(PluginValidationError):
- pm.register(Plugin())
- # make sure check_pending still fails if specname doesn't have a
- # corresponding spec. EVEN if the function name matches one.
- class Plugin2:
- @hookimpl(specname="bar")
- def hello(self, arg: int) -> int:
- return arg + 1
- pm.register(Plugin2())
- with pytest.raises(PluginValidationError):
- pm.check_pending()
- def test_hook_conflict(pm: PluginManager) -> None:
- class Api1:
- @hookspec
- def conflict(self) -> None:
- """Api1 hook"""
- class Api2:
- @hookspec
- def conflict(self) -> None:
- """Api2 hook"""
- pm.add_hookspecs(Api1)
- with pytest.raises(ValueError) as exc:
- pm.add_hookspecs(Api2)
- assert str(exc.value) == (
- "Hook 'conflict' is already registered within namespace "
- "<class '__tests__.test_hookcaller.test_hook_conflict.<locals>.Api1'>"
- )
|