test_invocations.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  1. import pytest
  2. from pluggy import PluginValidationError, HookimplMarker, HookspecMarker
  3. hookspec = HookspecMarker("example")
  4. hookimpl = HookimplMarker("example")
  5. def test_argmismatch(pm):
  6. class Api(object):
  7. @hookspec
  8. def hello(self, arg):
  9. "api hook 1"
  10. pm.add_hookspecs(Api)
  11. class Plugin(object):
  12. @hookimpl
  13. def hello(self, argwrong):
  14. pass
  15. with pytest.raises(PluginValidationError) as exc:
  16. pm.register(Plugin())
  17. assert "argwrong" in str(exc.value)
  18. def test_only_kwargs(pm):
  19. class Api(object):
  20. @hookspec
  21. def hello(self, arg):
  22. "api hook 1"
  23. pm.add_hookspecs(Api)
  24. with pytest.raises(TypeError) as exc:
  25. pm.hook.hello(3)
  26. comprehensible = "hook calling supports only keyword arguments"
  27. assert comprehensible in str(exc.value)
  28. def test_opt_in_args(pm):
  29. """Verfiy that two hookimpls with mutex args can serve
  30. under the same spec.
  31. """
  32. class Api(object):
  33. @hookspec
  34. def hello(self, arg1, arg2, common_arg):
  35. "api hook 1"
  36. class Plugin1(object):
  37. @hookimpl
  38. def hello(self, arg1, common_arg):
  39. return arg1 + common_arg
  40. class Plugin2(object):
  41. @hookimpl
  42. def hello(self, arg2, common_arg):
  43. return arg2 + common_arg
  44. pm.add_hookspecs(Api)
  45. pm.register(Plugin1())
  46. pm.register(Plugin2())
  47. results = pm.hook.hello(arg1=1, arg2=2, common_arg=0)
  48. assert results == [2, 1]
  49. def test_call_order(pm):
  50. class Api(object):
  51. @hookspec
  52. def hello(self, arg):
  53. "api hook 1"
  54. pm.add_hookspecs(Api)
  55. class Plugin1(object):
  56. @hookimpl
  57. def hello(self, arg):
  58. return 1
  59. class Plugin2(object):
  60. @hookimpl
  61. def hello(self, arg):
  62. return 2
  63. class Plugin3(object):
  64. @hookimpl
  65. def hello(self, arg):
  66. return 3
  67. class Plugin4(object):
  68. @hookimpl(hookwrapper=True)
  69. def hello(self, arg):
  70. assert arg == 0
  71. outcome = yield
  72. assert outcome.get_result() == [3, 2, 1]
  73. pm.register(Plugin1())
  74. pm.register(Plugin2())
  75. pm.register(Plugin3())
  76. pm.register(Plugin4()) # hookwrapper should get same list result
  77. res = pm.hook.hello(arg=0)
  78. assert res == [3, 2, 1]
  79. def test_firstresult_definition(pm):
  80. class Api(object):
  81. @hookspec(firstresult=True)
  82. def hello(self, arg):
  83. "api hook 1"
  84. pm.add_hookspecs(Api)
  85. class Plugin1(object):
  86. @hookimpl
  87. def hello(self, arg):
  88. return arg + 1
  89. class Plugin2(object):
  90. @hookimpl
  91. def hello(self, arg):
  92. return arg - 1
  93. class Plugin3(object):
  94. @hookimpl
  95. def hello(self, arg):
  96. return None
  97. class Plugin4(object):
  98. @hookimpl(hookwrapper=True)
  99. def hello(self, arg):
  100. assert arg == 3
  101. outcome = yield
  102. assert outcome.get_result() == 2
  103. pm.register(Plugin1()) # discarded - not the last registered plugin
  104. pm.register(Plugin2()) # used as result
  105. pm.register(Plugin3()) # None result is ignored
  106. pm.register(Plugin4()) # hookwrapper should get same non-list result
  107. res = pm.hook.hello(arg=3)
  108. assert res == 2
  109. def test_firstresult_force_result(pm):
  110. """Verify forcing a result in a wrapper.
  111. """
  112. class Api(object):
  113. @hookspec(firstresult=True)
  114. def hello(self, arg):
  115. "api hook 1"
  116. pm.add_hookspecs(Api)
  117. class Plugin1(object):
  118. @hookimpl
  119. def hello(self, arg):
  120. return arg + 1
  121. class Plugin2(object):
  122. @hookimpl(hookwrapper=True)
  123. def hello(self, arg):
  124. assert arg == 3
  125. outcome = yield
  126. assert outcome.get_result() == 4
  127. outcome.force_result(0)
  128. class Plugin3(object):
  129. @hookimpl
  130. def hello(self, arg):
  131. return None
  132. pm.register(Plugin1())
  133. pm.register(Plugin2()) # wrapper
  134. pm.register(Plugin3()) # ignored since returns None
  135. res = pm.hook.hello(arg=3)
  136. assert res == 0 # this result is forced and not a list
  137. def test_firstresult_returns_none(pm):
  138. """If None results are returned by underlying implementations ensure
  139. the multi-call loop returns a None value.
  140. """
  141. class Api(object):
  142. @hookspec(firstresult=True)
  143. def hello(self, arg):
  144. "api hook 1"
  145. pm.add_hookspecs(Api)
  146. class Plugin1(object):
  147. @hookimpl
  148. def hello(self, arg):
  149. return None
  150. pm.register(Plugin1())
  151. res = pm.hook.hello(arg=3)
  152. assert res is None
  153. def test_firstresult_no_plugin(pm):
  154. """If no implementations/plugins have been registered for a firstresult
  155. hook the multi-call loop should return a None value.
  156. """
  157. class Api(object):
  158. @hookspec(firstresult=True)
  159. def hello(self, arg):
  160. "api hook 1"
  161. pm.add_hookspecs(Api)
  162. res = pm.hook.hello(arg=3)
  163. assert res is None