emqx_hooks_SUITE.erl 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. %%--------------------------------------------------------------------
  2. %% Copyright (c) 2018-2024 EMQ Technologies Co., Ltd. All Rights Reserved.
  3. %%
  4. %% Licensed under the Apache License, Version 2.0 (the "License");
  5. %% you may not use this file except in compliance with the License.
  6. %% You may obtain a copy of the License at
  7. %%
  8. %% http://www.apache.org/licenses/LICENSE-2.0
  9. %%
  10. %% Unless required by applicable law or agreed to in writing, software
  11. %% distributed under the License is distributed on an "AS IS" BASIS,
  12. %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. %% See the License for the specific language governing permissions and
  14. %% limitations under the License.
  15. %%--------------------------------------------------------------------
  16. -module(emqx_hooks_SUITE).
  17. -compile(export_all).
  18. -compile(nowarn_export_all).
  19. -include_lib("proper/include/proper.hrl").
  20. -include_lib("eunit/include/eunit.hrl").
  21. all() -> emqx_common_test_helpers:all(?MODULE).
  22. init_per_testcase(_, Config) ->
  23. {ok, _} = emqx_hooks:start_link(),
  24. ok = emqx_hookpoints:register_hookpoints(
  25. [
  26. test_hook,
  27. emqx_hook,
  28. foldl_hook,
  29. foldl_hook2,
  30. foreach_hook,
  31. foreach_filter1_hook,
  32. foldl_filter2_hook
  33. ]
  34. ),
  35. Config.
  36. end_per_testcase(_) ->
  37. ok = emqx_hookpoints:register_hookpoints(),
  38. catch emqx_hooks:stop().
  39. t_add_hook_order(_) ->
  40. ?assert(
  41. proper:quickcheck(
  42. add_hook_order_prop(),
  43. [
  44. {numtests, 1000}
  45. ]
  46. )
  47. ).
  48. add_hook_order_prop() ->
  49. %% Note: order is inversed, since higher prio hooks run first:
  50. Comparator = fun({Prio1, M1, F1}, {Prio2, M2, F2}) ->
  51. Prio1 > Prio2 orelse
  52. (Prio1 =:= Prio2 andalso {M1, F1} =< {M2, F2})
  53. end,
  54. ?FORALL(
  55. Hooks,
  56. hooks(),
  57. try
  58. _ = emqx_hooks:start_link(),
  59. ok = emqx_hookpoints:register_hookpoints([prop_hook]),
  60. [ok = emqx_hooks:add(prop_hook, {M, F, []}, Prio) || {Prio, M, F} <- Hooks],
  61. Callbacks = emqx_hooks:lookup(prop_hook),
  62. Order = [{Prio, M, F} || {callback, {M, F, _}, _Filter, Prio} <- Callbacks],
  63. ?assertEqual(
  64. lists:sort(Comparator, Hooks),
  65. Order
  66. ),
  67. true
  68. after
  69. emqx_hooks:stop()
  70. end
  71. ).
  72. hooks() ->
  73. ?SUCHTHAT(
  74. L0,
  75. list({range(-1, 5), atom(), atom()}),
  76. begin
  77. %% Duplicate callbacks are ignored, so check that
  78. %% all callbacks are unique:
  79. L = [{M, F} || {_Prio, M, F} <- L0],
  80. length(lists:usort(L)) =:= length(L0)
  81. end
  82. ).
  83. t_add_put_del_hook(_) ->
  84. ok = emqx_hooks:add(test_hook, {?MODULE, hook_fun1, []}, 0),
  85. ok = emqx_hooks:add(test_hook, {?MODULE, hook_fun2, []}, 0),
  86. ?assertEqual(
  87. {error, already_exists},
  88. emqx_hooks:add(test_hook, {?MODULE, hook_fun2, []}, 0)
  89. ),
  90. Callbacks0 = [
  91. {callback, {?MODULE, hook_fun1, []}, undefined, 0},
  92. {callback, {?MODULE, hook_fun2, []}, undefined, 0}
  93. ],
  94. ?assertEqual(Callbacks0, emqx_hooks:lookup(test_hook)),
  95. ok = emqx_hooks:put(test_hook, {?MODULE, hook_fun1, [test]}, 0),
  96. ok = emqx_hooks:put(test_hook, {?MODULE, hook_fun2, [test]}, 0),
  97. Callbacks1 = [
  98. {callback, {?MODULE, hook_fun1, [test]}, undefined, 0},
  99. {callback, {?MODULE, hook_fun2, [test]}, undefined, 0}
  100. ],
  101. ?assertEqual(Callbacks1, emqx_hooks:lookup(test_hook)),
  102. ok = emqx_hooks:del(test_hook, {?MODULE, hook_fun1}),
  103. ok = emqx_hooks:del(test_hook, {?MODULE, hook_fun2}),
  104. timer:sleep(200),
  105. ?assertEqual([], emqx_hooks:lookup(test_hook)),
  106. ok = emqx_hooks:add(emqx_hook, {?MODULE, hook_fun8, []}, 8),
  107. ok = emqx_hooks:add(emqx_hook, {?MODULE, hook_fun2, []}, 2),
  108. ok = emqx_hooks:add(emqx_hook, {?MODULE, hook_fun10, []}, 10),
  109. ok = emqx_hooks:add(emqx_hook, {?MODULE, hook_fun9, []}, 9),
  110. Callbacks2 = [
  111. {callback, {?MODULE, hook_fun10, []}, undefined, 10},
  112. {callback, {?MODULE, hook_fun9, []}, undefined, 9},
  113. {callback, {?MODULE, hook_fun8, []}, undefined, 8},
  114. {callback, {?MODULE, hook_fun2, []}, undefined, 2}
  115. ],
  116. ?assertEqual(Callbacks2, emqx_hooks:lookup(emqx_hook)),
  117. ok = emqx_hooks:put(emqx_hook, {?MODULE, hook_fun8, [test]}, 3),
  118. ok = emqx_hooks:put(emqx_hook, {?MODULE, hook_fun2, [test]}, 4),
  119. ok = emqx_hooks:put(emqx_hook, {?MODULE, hook_fun10, [test]}, 1),
  120. ok = emqx_hooks:put(emqx_hook, {?MODULE, hook_fun9, [test]}, 2),
  121. Callbacks3 = [
  122. {callback, {?MODULE, hook_fun2, [test]}, undefined, 4},
  123. {callback, {?MODULE, hook_fun8, [test]}, undefined, 3},
  124. {callback, {?MODULE, hook_fun9, [test]}, undefined, 2},
  125. {callback, {?MODULE, hook_fun10, [test]}, undefined, 1}
  126. ],
  127. ?assertEqual(Callbacks3, emqx_hooks:lookup(emqx_hook)),
  128. ok = emqx_hooks:del(emqx_hook, {?MODULE, hook_fun2, [test]}),
  129. ok = emqx_hooks:del(emqx_hook, {?MODULE, hook_fun8, [test]}),
  130. ok = emqx_hooks:del(emqx_hook, {?MODULE, hook_fun9, [test]}),
  131. ok = emqx_hooks:del(emqx_hook, {?MODULE, hook_fun10, [test]}),
  132. timer:sleep(200),
  133. ?assertEqual([], emqx_hooks:lookup(emqx_hook)).
  134. t_run_hooks(_) ->
  135. ok = emqx_hooks:add(foldl_hook, {?MODULE, hook_fun3, [init]}, 0),
  136. ok = emqx_hooks:add(foldl_hook, {?MODULE, hook_fun4, [init]}, 0),
  137. ok = emqx_hooks:add(foldl_hook, {?MODULE, hook_fun5, [init]}, 0),
  138. [r5, r4] = emqx_hooks:run_fold(foldl_hook, [arg1, arg2], []),
  139. ?assertException(
  140. error,
  141. {invalid_hookpoint, unknown_hook},
  142. emqx_hooks:run_fold(unknown_hook, [], [])
  143. ),
  144. ?assertException(
  145. error,
  146. {invalid_hookpoint, unknown_hook},
  147. emqx_hooks:add(unknown_hook, {?MODULE, hook_fun5, [init]}, 0)
  148. ),
  149. ok = emqx_hooks:add(foldl_hook2, {?MODULE, hook_fun9, []}, 0),
  150. ok = emqx_hooks:add(foldl_hook2, {?MODULE, hook_fun10, []}, 0),
  151. %% Note: 10 is _less_ than 9 per lexicographic order
  152. [r10] = emqx_hooks:run_fold(foldl_hook2, [arg], []),
  153. ok = emqx_hooks:add(foreach_hook, {?MODULE, hook_fun6, [initArg]}, 0),
  154. {error, already_exists} = emqx_hooks:add(foreach_hook, {?MODULE, hook_fun6, [initArg]}, 0),
  155. ok = emqx_hooks:add(foreach_hook, {?MODULE, hook_fun7, [initArg]}, 0),
  156. ok = emqx_hooks:add(foreach_hook, {?MODULE, hook_fun8, [initArg]}, 0),
  157. ok = emqx_hooks:run(foreach_hook, [arg]),
  158. ok = emqx_hooks:add(
  159. foreach_filter1_hook, {?MODULE, hook_fun1, []}, 0, {?MODULE, hook_filter1, []}
  160. ),
  161. %% filter passed
  162. ?assertEqual(ok, emqx_hooks:run(foreach_filter1_hook, [arg])),
  163. %% filter failed
  164. ?assertEqual(ok, emqx_hooks:run(foreach_filter1_hook, [arg1])),
  165. ok = emqx_hooks:add(
  166. foldl_filter2_hook, {?MODULE, hook_fun2, []}, 0, {?MODULE, hook_filter2, [init_arg]}
  167. ),
  168. ok = emqx_hooks:add(
  169. foldl_filter2_hook, {?MODULE, hook_fun2_1, []}, 0, {?MODULE, hook_filter2_1, [init_arg]}
  170. ),
  171. ?assertEqual(3, emqx_hooks:run_fold(foldl_filter2_hook, [arg], 1)),
  172. ?assertEqual(2, emqx_hooks:run_fold(foldl_filter2_hook, [arg1], 1)).
  173. t_uncovered_func(_) ->
  174. Pid = erlang:whereis(emqx_hooks),
  175. gen_server:call(Pid, test),
  176. gen_server:cast(Pid, test),
  177. Pid ! test,
  178. ok = emqx_hooks:stop().
  179. %%--------------------------------------------------------------------
  180. %% Hook fun
  181. %%--------------------------------------------------------------------
  182. hook_fun1(arg) -> ok;
  183. hook_fun1(_) -> error.
  184. hook_fun2(arg) -> ok;
  185. hook_fun2(_) -> error.
  186. hook_fun2(_, Acc) -> {ok, Acc + 1}.
  187. hook_fun2_1(_, Acc) -> {ok, Acc + 1}.
  188. hook_fun3(arg1, arg2, _Acc, init) -> ok.
  189. hook_fun4(arg1, arg2, Acc, init) -> {ok, [r4 | Acc]}.
  190. hook_fun5(arg1, arg2, Acc, init) -> {ok, [r5 | Acc]}.
  191. hook_fun6(arg, initArg) -> ok.
  192. hook_fun7(arg, initArg) -> ok.
  193. hook_fun8(arg, initArg) -> ok.
  194. hook_fun9(arg, Acc) -> {stop, [r9 | Acc]}.
  195. hook_fun10(arg, Acc) -> {stop, [r10 | Acc]}.
  196. hook_filter1(arg) -> true;
  197. hook_filter1(_) -> false.
  198. hook_filter2(arg, _Acc, init_arg) -> true;
  199. hook_filter2(_, _Acc, _IntArg) -> false.
  200. hook_filter2_1(arg, _Acc, init_arg) -> true;
  201. hook_filter2_1(arg1, _Acc, init_arg) -> true;
  202. hook_filter2_1(_, _Acc, _IntArg) -> false.