test_invocations.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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:
  7. @hookspec
  8. def hello(self, arg):
  9. "api hook 1"
  10. pm.add_hookspecs(Api)
  11. class Plugin:
  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:
  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:
  33. @hookspec
  34. def hello(self, arg1, arg2, common_arg):
  35. "api hook 1"
  36. class Plugin1:
  37. @hookimpl
  38. def hello(self, arg1, common_arg):
  39. return arg1 + common_arg
  40. class Plugin2:
  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:
  51. @hookspec
  52. def hello(self, arg):
  53. "api hook 1"
  54. pm.add_hookspecs(Api)
  55. class Plugin1:
  56. @hookimpl
  57. def hello(self, arg):
  58. return 1
  59. class Plugin2:
  60. @hookimpl
  61. def hello(self, arg):
  62. return 2
  63. class Plugin3:
  64. @hookimpl
  65. def hello(self, arg):
  66. return 3
  67. class Plugin4:
  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:
  81. @hookspec(firstresult=True)
  82. def hello(self, arg):
  83. "api hook 1"
  84. pm.add_hookspecs(Api)
  85. class Plugin1:
  86. @hookimpl
  87. def hello(self, arg):
  88. return arg + 1
  89. class Plugin2:
  90. @hookimpl
  91. def hello(self, arg):
  92. return arg - 1
  93. class Plugin3:
  94. @hookimpl
  95. def hello(self, arg):
  96. return None
  97. class Plugin4:
  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. class Api:
  112. @hookspec(firstresult=True)
  113. def hello(self, arg):
  114. "api hook 1"
  115. pm.add_hookspecs(Api)
  116. class Plugin1:
  117. @hookimpl
  118. def hello(self, arg):
  119. return arg + 1
  120. class Plugin2:
  121. @hookimpl(hookwrapper=True)
  122. def hello(self, arg):
  123. assert arg == 3
  124. outcome = yield
  125. assert outcome.get_result() == 4
  126. outcome.force_result(0)
  127. class Plugin3:
  128. @hookimpl
  129. def hello(self, arg):
  130. return None
  131. pm.register(Plugin1())
  132. pm.register(Plugin2()) # wrapper
  133. pm.register(Plugin3()) # ignored since returns None
  134. res = pm.hook.hello(arg=3)
  135. assert res == 0 # this result is forced and not a list
  136. def test_firstresult_returns_none(pm):
  137. """If None results are returned by underlying implementations ensure
  138. the multi-call loop returns a None value.
  139. """
  140. class Api:
  141. @hookspec(firstresult=True)
  142. def hello(self, arg):
  143. "api hook 1"
  144. pm.add_hookspecs(Api)
  145. class Plugin1:
  146. @hookimpl
  147. def hello(self, arg):
  148. return None
  149. pm.register(Plugin1())
  150. res = pm.hook.hello(arg=3)
  151. assert res is None
  152. def test_firstresult_no_plugin(pm):
  153. """If no implementations/plugins have been registered for a firstresult
  154. hook the multi-call loop should return a None value.
  155. """
  156. class Api:
  157. @hookspec(firstresult=True)
  158. def hello(self, arg):
  159. "api hook 1"
  160. pm.add_hookspecs(Api)
  161. res = pm.hook.hello(arg=3)
  162. assert res is None