test_multicall.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. import pytest
  2. from pluggy import HookCallError, HookspecMarker, HookimplMarker
  3. from pluggy.hooks import HookImpl
  4. from pluggy.callers import _multicall, _legacymulticall
  5. hookspec = HookspecMarker("example")
  6. hookimpl = HookimplMarker("example")
  7. def MC(methods, kwargs, firstresult=False):
  8. caller = _multicall
  9. hookfuncs = []
  10. for method in methods:
  11. f = HookImpl(None, "<temp>", method, method.example_impl)
  12. hookfuncs.append(f)
  13. if "__multicall__" in f.argnames:
  14. caller = _legacymulticall
  15. return caller(hookfuncs, kwargs, firstresult=firstresult)
  16. def test_call_passing():
  17. class P1(object):
  18. @hookimpl
  19. def m(self, __multicall__, x):
  20. assert len(__multicall__.results) == 1
  21. assert not __multicall__.hook_impls
  22. return 17
  23. class P2(object):
  24. @hookimpl
  25. def m(self, __multicall__, x):
  26. assert __multicall__.results == []
  27. assert __multicall__.hook_impls
  28. return 23
  29. p1 = P1()
  30. p2 = P2()
  31. reslist = MC([p1.m, p2.m], {"x": 23})
  32. assert len(reslist) == 2
  33. # ensure reversed order
  34. assert reslist == [23, 17]
  35. def test_keyword_args():
  36. @hookimpl
  37. def f(x):
  38. return x + 1
  39. class A(object):
  40. @hookimpl
  41. def f(self, x, y):
  42. return x + y
  43. reslist = MC([f, A().f], dict(x=23, y=24))
  44. assert reslist == [24 + 23, 24]
  45. def test_keyword_args_with_defaultargs():
  46. @hookimpl
  47. def f(x, z=1):
  48. return x + z
  49. reslist = MC([f], dict(x=23, y=24))
  50. assert reslist == [24]
  51. def test_tags_call_error():
  52. @hookimpl
  53. def f(x):
  54. return x
  55. with pytest.raises(HookCallError):
  56. MC([f], {})
  57. def test_call_subexecute():
  58. @hookimpl
  59. def m(__multicall__):
  60. subresult = __multicall__.execute()
  61. return subresult + 1
  62. @hookimpl
  63. def n():
  64. return 1
  65. res = MC([n, m], {}, firstresult=True)
  66. assert res == 2
  67. def test_call_none_is_no_result():
  68. @hookimpl
  69. def m1():
  70. return 1
  71. @hookimpl
  72. def m2():
  73. return None
  74. res = MC([m1, m2], {}, firstresult=True)
  75. assert res == 1
  76. res = MC([m1, m2], {}, {})
  77. assert res == [1]
  78. def test_hookwrapper():
  79. out = []
  80. @hookimpl(hookwrapper=True)
  81. def m1():
  82. out.append("m1 init")
  83. yield None
  84. out.append("m1 finish")
  85. @hookimpl
  86. def m2():
  87. out.append("m2")
  88. return 2
  89. res = MC([m2, m1], {})
  90. assert res == [2]
  91. assert out == ["m1 init", "m2", "m1 finish"]
  92. out[:] = []
  93. res = MC([m2, m1], {}, firstresult=True)
  94. assert res == 2
  95. assert out == ["m1 init", "m2", "m1 finish"]
  96. def test_hookwrapper_order():
  97. out = []
  98. @hookimpl(hookwrapper=True)
  99. def m1():
  100. out.append("m1 init")
  101. yield 1
  102. out.append("m1 finish")
  103. @hookimpl(hookwrapper=True)
  104. def m2():
  105. out.append("m2 init")
  106. yield 2
  107. out.append("m2 finish")
  108. res = MC([m2, m1], {})
  109. assert res == []
  110. assert out == ["m1 init", "m2 init", "m2 finish", "m1 finish"]
  111. def test_hookwrapper_not_yield():
  112. @hookimpl(hookwrapper=True)
  113. def m1():
  114. pass
  115. with pytest.raises(TypeError):
  116. MC([m1], {})
  117. def test_hookwrapper_too_many_yield():
  118. @hookimpl(hookwrapper=True)
  119. def m1():
  120. yield 1
  121. yield 2
  122. with pytest.raises(RuntimeError) as ex:
  123. MC([m1], {})
  124. assert "m1" in str(ex.value)
  125. assert (__file__ + ":") in str(ex.value)
  126. @pytest.mark.parametrize("exc", [ValueError, SystemExit])
  127. def test_hookwrapper_exception(exc):
  128. out = []
  129. @hookimpl(hookwrapper=True)
  130. def m1():
  131. out.append("m1 init")
  132. yield None
  133. out.append("m1 finish")
  134. @hookimpl
  135. def m2():
  136. raise exc
  137. with pytest.raises(exc):
  138. MC([m2, m1], {})
  139. assert out == ["m1 init", "m1 finish"]