123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454 |
- from typing import Callable
- from typing import List
- from typing import Mapping
- from typing import Sequence
- from typing import Type
- from typing import Union
- import pytest
- from pluggy import HookCallError
- from pluggy import HookimplMarker
- from pluggy import HookspecMarker
- from pluggy._callers import _multicall
- from pluggy._hooks import HookImpl
- hookspec = HookspecMarker("example")
- hookimpl = HookimplMarker("example")
- def MC(
- methods: Sequence[Callable[..., object]],
- kwargs: Mapping[str, object],
- firstresult: bool = False,
- ) -> Union[object, List[object]]:
- caller = _multicall
- hookfuncs = []
- for method in methods:
- f = HookImpl(None, "<temp>", method, method.example_impl) # type: ignore[attr-defined]
- hookfuncs.append(f)
- return caller("foo", hookfuncs, kwargs, firstresult)
- def test_keyword_args() -> None:
- @hookimpl
- def f(x):
- return x + 1
- class A:
- @hookimpl
- def f(self, x, y):
- return x + y
- reslist = MC([f, A().f], dict(x=23, y=24))
- assert reslist == [24 + 23, 24]
- def test_keyword_args_with_defaultargs() -> None:
- @hookimpl
- def f(x, z=1):
- return x + z
- reslist = MC([f], dict(x=23, y=24))
- assert reslist == [24]
- def test_tags_call_error() -> None:
- @hookimpl
- def f(x):
- return x
- with pytest.raises(HookCallError):
- MC([f], {})
- def test_call_none_is_no_result() -> None:
- @hookimpl
- def m1():
- return 1
- @hookimpl
- def m2():
- return None
- res = MC([m1, m2], {}, firstresult=True)
- assert res == 1
- res = MC([m1, m2], {}, firstresult=False)
- assert res == [1]
- def test_hookwrapper() -> None:
- out = []
- @hookimpl(hookwrapper=True)
- def m1():
- out.append("m1 init")
- yield None
- out.append("m1 finish")
- @hookimpl
- def m2():
- out.append("m2")
- return 2
- res = MC([m2, m1], {})
- assert res == [2]
- assert out == ["m1 init", "m2", "m1 finish"]
- out[:] = []
- res = MC([m2, m1], {}, firstresult=True)
- assert res == 2
- assert out == ["m1 init", "m2", "m1 finish"]
- def test_hookwrapper_two_yields() -> None:
- @hookimpl(hookwrapper=True)
- def m():
- yield
- yield
- with pytest.raises(RuntimeError, match="has second yield"):
- MC([m], {})
- def test_wrapper() -> None:
- out = []
- @hookimpl(wrapper=True)
- def m1():
- out.append("m1 init")
- result = yield
- out.append("m1 finish")
- return result * 2
- @hookimpl
- def m2():
- out.append("m2")
- return 2
- res = MC([m2, m1], {})
- assert res == [2, 2]
- assert out == ["m1 init", "m2", "m1 finish"]
- out[:] = []
- res = MC([m2, m1], {}, firstresult=True)
- assert res == 4
- assert out == ["m1 init", "m2", "m1 finish"]
- def test_wrapper_two_yields() -> None:
- @hookimpl(wrapper=True)
- def m():
- yield
- yield
- with pytest.raises(RuntimeError, match="has second yield"):
- MC([m], {})
- def test_hookwrapper_order() -> None:
- out = []
- @hookimpl(hookwrapper=True)
- def m1():
- out.append("m1 init")
- yield 1
- out.append("m1 finish")
- @hookimpl(wrapper=True)
- def m2():
- out.append("m2 init")
- result = yield 2
- out.append("m2 finish")
- return result
- @hookimpl(hookwrapper=True)
- def m3():
- out.append("m3 init")
- yield 3
- out.append("m3 finish")
- @hookimpl(hookwrapper=True)
- def m4():
- out.append("m4 init")
- yield 4
- out.append("m4 finish")
- res = MC([m4, m3, m2, m1], {})
- assert res == []
- assert out == [
- "m1 init",
- "m2 init",
- "m3 init",
- "m4 init",
- "m4 finish",
- "m3 finish",
- "m2 finish",
- "m1 finish",
- ]
- def test_hookwrapper_not_yield() -> None:
- @hookimpl(hookwrapper=True)
- def m1():
- pass
- with pytest.raises(TypeError):
- MC([m1], {})
- def test_hookwrapper_yield_not_executed() -> None:
- @hookimpl(hookwrapper=True)
- def m1():
- if False:
- yield # type: ignore[unreachable]
- with pytest.raises(RuntimeError, match="did not yield"):
- MC([m1], {})
- def test_hookwrapper_too_many_yield() -> None:
- @hookimpl(hookwrapper=True)
- def m1():
- yield 1
- yield 2
- with pytest.raises(RuntimeError) as ex:
- MC([m1], {})
- assert "m1" in str(ex.value)
- assert (__file__ + ":") in str(ex.value)
- def test_wrapper_yield_not_executed() -> None:
- @hookimpl(wrapper=True)
- def m1():
- if False:
- yield # type: ignore[unreachable]
- with pytest.raises(RuntimeError, match="did not yield"):
- MC([m1], {})
- def test_wrapper_too_many_yield() -> None:
- out = []
- @hookimpl(wrapper=True)
- def m1():
- try:
- yield 1
- yield 2
- finally:
- out.append("cleanup")
- with pytest.raises(RuntimeError) as ex:
- try:
- MC([m1], {})
- finally:
- out.append("finally")
- assert "m1" in str(ex.value)
- assert (__file__ + ":") in str(ex.value)
- assert out == ["cleanup", "finally"]
- @pytest.mark.parametrize("exc", [ValueError, SystemExit])
- def test_hookwrapper_exception(exc: "Type[BaseException]") -> None:
- out = []
- @hookimpl(hookwrapper=True)
- def m1():
- out.append("m1 init")
- result = yield
- assert isinstance(result.exception, exc)
- assert result.excinfo[0] == exc
- out.append("m1 finish")
- @hookimpl
- def m2():
- raise exc
- with pytest.raises(exc):
- MC([m2, m1], {})
- assert out == ["m1 init", "m1 finish"]
- def test_hookwrapper_force_exception() -> None:
- out = []
- @hookimpl(hookwrapper=True)
- def m1():
- out.append("m1 init")
- result = yield
- try:
- result.get_result()
- except BaseException as exc:
- result.force_exception(exc)
- out.append("m1 finish")
- @hookimpl(hookwrapper=True)
- def m2():
- out.append("m2 init")
- result = yield
- try:
- result.get_result()
- except BaseException as exc:
- new_exc = OSError("m2")
- new_exc.__cause__ = exc
- result.force_exception(new_exc)
- out.append("m2 finish")
- @hookimpl(hookwrapper=True)
- def m3():
- out.append("m3 init")
- yield
- out.append("m3 finish")
- @hookimpl
- def m4():
- raise ValueError("m4")
- with pytest.raises(OSError, match="m2") as excinfo:
- MC([m4, m3, m2, m1], {})
- assert out == [
- "m1 init",
- "m2 init",
- "m3 init",
- "m3 finish",
- "m2 finish",
- "m1 finish",
- ]
- assert excinfo.value.__cause__ is not None
- assert str(excinfo.value.__cause__) == "m4"
- @pytest.mark.parametrize("exc", [ValueError, SystemExit])
- def test_wrapper_exception(exc: "Type[BaseException]") -> None:
- out = []
- @hookimpl(wrapper=True)
- def m1():
- out.append("m1 init")
- try:
- result = yield
- except BaseException as e:
- assert isinstance(e, exc)
- raise
- finally:
- out.append("m1 finish")
- return result
- @hookimpl
- def m2():
- out.append("m2 init")
- raise exc
- with pytest.raises(exc):
- MC([m2, m1], {})
- assert out == ["m1 init", "m2 init", "m1 finish"]
- def test_wrapper_exception_chaining() -> None:
- @hookimpl
- def m1():
- raise Exception("m1")
- @hookimpl(wrapper=True)
- def m2():
- try:
- yield
- except Exception:
- raise Exception("m2")
- @hookimpl(wrapper=True)
- def m3():
- yield
- return 10
- @hookimpl(wrapper=True)
- def m4():
- try:
- yield
- except Exception as e:
- raise Exception("m4") from e
- with pytest.raises(Exception) as excinfo:
- MC([m1, m2, m3, m4], {})
- assert str(excinfo.value) == "m4"
- assert excinfo.value.__cause__ is not None
- assert str(excinfo.value.__cause__) == "m2"
- assert excinfo.value.__cause__.__context__ is not None
- assert str(excinfo.value.__cause__.__context__) == "m1"
- def test_unwind_inner_wrapper_teardown_exc() -> None:
- out = []
- @hookimpl(wrapper=True)
- def m1():
- out.append("m1 init")
- try:
- yield
- out.append("m1 unreachable")
- except BaseException:
- out.append("m1 teardown")
- raise
- finally:
- out.append("m1 cleanup")
- @hookimpl(wrapper=True)
- def m2():
- out.append("m2 init")
- yield
- out.append("m2 raise")
- raise ValueError()
- with pytest.raises(ValueError):
- try:
- MC([m2, m1], {})
- finally:
- out.append("finally")
- assert out == [
- "m1 init",
- "m2 init",
- "m2 raise",
- "m1 teardown",
- "m1 cleanup",
- "finally",
- ]
- def test_suppress_inner_wrapper_teardown_exc() -> None:
- out = []
- @hookimpl(wrapper=True)
- def m1():
- out.append("m1 init")
- result = yield
- out.append("m1 finish")
- return result
- @hookimpl(wrapper=True)
- def m2():
- out.append("m2 init")
- try:
- yield
- out.append("m2 unreachable")
- except ValueError:
- out.append("m2 suppress")
- return 22
- @hookimpl(wrapper=True)
- def m3():
- out.append("m3 init")
- yield
- out.append("m3 raise")
- raise ValueError()
- assert MC([m3, m2, m1], {}) == 22
- assert out == [
- "m1 init",
- "m2 init",
- "m3 init",
- "m3 raise",
- "m2 suppress",
- "m1 finish",
- ]
|