1: -module(gen_hook_SUITE). 2: -compile([export_all, nowarn_export_all]). 3: 4: -include_lib("eunit/include/eunit.hrl"). 5: 6: -define(HOOK_TAG1, global). 7: -define(HOOK_TAG2, <<"some tag">>). 8: 9: -define(assertEqualLists(L1, L2), ?assertEqual(lists:sort(L1), lists:sort(L2))). 10: 11: all() -> 12: [single_handler_can_be_added_and_removed, 13: multiple_handlers_can_be_added_and_removed, 14: 15: local_fun_references_causes_error, 16: anonymous_fun_references_causes_error, 17: not_exported_external_fun_references_causes_error, 18: invalid_hook_handler_parameters_causes_error, 19: 20: run_fold_executes_handlers_in_the_right_order, 21: run_fold_stops_when_handler_returns_stop, 22: 23: errors_in_handlers_are_reported_but_ignored]. 24: 25: init_per_suite(Config) -> 26: application:ensure_all_started(exometer_core), 27: mongoose_config:set_opts(#{all_metrics_are_global => false}), 28: Config. 29: 30: end_per_suite(Config) -> 31: mongoose_config:erase_opts(), 32: application:stop(exometer_core), 33: Config. 34: 35: init_per_testcase(_, Config) -> 36: mongooseim_helper:start_link_loaded_hooks(), 37: Config. 38: 39: end_per_testcase(_, Config) -> 40: meck:unload(), 41: Config. 42: 43: 44: %%---------------------------------------------------------------- 45: %% test cases 46: %%---------------------------------------------------------------- 47: single_handler_can_be_added_and_removed(_) -> 48: meck:new([mod1, mod2], [non_strict]), 49: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 50: MultiplyHandlerFn = get_hook_handler(mod2, multiply, fun hook_handler_multiply/3), 51: %% check that there are no hook handlers added yet 52: ?assertEqual([], get_handlers_for_all_hooks()), 53: %% add various hook handlers 54: ?assertEqual(ok, gen_hook:add_handler(calculate, ?HOOK_TAG1, MultiplyHandlerFn, 55: #{id => 2}, 2)), 56: ?assertEqual(ok, gen_hook:add_handler(calculate, ?HOOK_TAG1, PlusHandlerFn, 57: #{id => 1}, 1)), 58: ?assertEqual(ok, gen_hook:add_handler(calculate, ?HOOK_TAG2, PlusHandlerFn, 59: #{id => 1}, 1)), 60: %% check that hook handlers are added 61: Tag1Handlers = [%% this list must be sorted by priority 62: {hook_handler, 1, PlusHandlerFn, 63: #{hook_name => calculate, hook_tag => ?HOOK_TAG1, id => 1}}, 64: {hook_handler, 2, MultiplyHandlerFn, 65: #{hook_name => calculate, hook_tag => ?HOOK_TAG1, id => 2}}], 66: AllHandlers = [{{calculate, ?HOOK_TAG1}, Tag1Handlers}, 67: {{calculate, ?HOOK_TAG2}, 68: [{hook_handler, 1, PlusHandlerFn, 69: #{hook_name => calculate, hook_tag => ?HOOK_TAG2, 70: host_type =>?HOOK_TAG2, id => 1}}]}], 71: ?assertEqualLists(AllHandlers, get_handlers_for_all_hooks()), 72: %% try to add some hook handler second time and check that nothing has changed 73: ?assertEqual(ok, gen_hook:add_handler(calculate, ?HOOK_TAG1, MultiplyHandlerFn, 74: #{id => 2}, 2)), 75: ?assertEqualLists(AllHandlers, get_handlers_for_all_hooks()), 76: %% try to remove hook handler for ?HOOK_TAG2 and check that it's removed 77: ?assertEqual(ok, gen_hook:delete_handler(calculate, ?HOOK_TAG2, PlusHandlerFn, 78: #{id => 1}, 1)), 79: ?assertEqualLists([{{calculate, ?HOOK_TAG1}, Tag1Handlers}, 80: {{calculate, ?HOOK_TAG2}, []}], 81: get_handlers_for_all_hooks()), 82: %% try to remove hook handler for ?HOOK_TAG2 second time 83: %% and check that nothing has changed 84: ?assertEqual(ok, gen_hook:delete_handler(calculate, ?HOOK_TAG2, PlusHandlerFn, 85: #{id => 1}, 1)), 86: ?assertEqualLists([{{calculate, ?HOOK_TAG1}, Tag1Handlers}, 87: {{calculate, ?HOOK_TAG2}, []}], 88: get_handlers_for_all_hooks()), 89: %% try to remove hook handlers for ?HOOK_TAG1 and check that they are removed 90: ?assertEqual(ok, gen_hook:delete_handler(calculate, ?HOOK_TAG1, MultiplyHandlerFn, 91: #{id => 2}, 2)), 92: ?assertEqual(ok, gen_hook:delete_handler(calculate, ?HOOK_TAG1, PlusHandlerFn, 93: #{id => 1}, 1)), 94: ?assertEqualLists([{{calculate, ?HOOK_TAG1}, []}, {{calculate, ?HOOK_TAG2}, []}], 95: get_handlers_for_all_hooks()). 96: 97: multiple_handlers_can_be_added_and_removed(_) -> 98: meck:new([mod1, mod2], [non_strict]), 99: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 100: MultiplyHandlerFn = get_hook_handler(mod2, multiply, fun hook_handler_multiply/3), 101: %% check that there are no hook handlers added yet 102: ?assertEqual([], get_handlers_for_all_hooks()), 103: %% add various hook handlers 104: HookHandlers = [{calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{id => 2}, 2}, 105: {calculate, ?HOOK_TAG2, PlusHandlerFn, #{id => 1}, 1}, 106: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{id => 1}, 1}], 107: ?assertEqual(ok, gen_hook:add_handlers(HookHandlers)), 108: %% check that hook handlers are added 109: Tag1Handlers = [%% this list must be sorted by priority 110: {hook_handler, 1, PlusHandlerFn, 111: #{hook_name => calculate, hook_tag => ?HOOK_TAG1, id => 1}}, 112: {hook_handler, 2, MultiplyHandlerFn, 113: #{hook_name => calculate, hook_tag => ?HOOK_TAG1, id => 2}}], 114: AllHandlers = [{{calculate, ?HOOK_TAG1}, Tag1Handlers}, 115: {{calculate, ?HOOK_TAG2}, 116: [{hook_handler, 1, PlusHandlerFn, 117: #{hook_name => calculate, hook_tag => ?HOOK_TAG2, 118: host_type =>?HOOK_TAG2, id => 1}}]}], 119: ?assertEqualLists(AllHandlers, get_handlers_for_all_hooks()), 120: %% try to add hook handlers second time and check that nothing has changed 121: ?assertEqual(ok, gen_hook:add_handlers(HookHandlers)), 122: ?assertEqualLists(AllHandlers, get_handlers_for_all_hooks()), 123: %% try to remove hook handlers and check that they are removed 124: ?assertEqual(ok, gen_hook:delete_handlers(HookHandlers)), 125: ?assertEqualLists([{{calculate, ?HOOK_TAG1}, []}, {{calculate, ?HOOK_TAG2}, []}], 126: get_handlers_for_all_hooks()), 127: %% try to remove hook handlers second time and check that nothing has changed 128: ?assertEqual(ok, gen_hook:delete_handlers(HookHandlers)), 129: ?assertEqualLists([{{calculate, ?HOOK_TAG1}, []}, {{calculate, ?HOOK_TAG2}, []}], 130: get_handlers_for_all_hooks()). 131: 132: local_fun_references_causes_error(_) -> 133: meck:new([mod1, mod2], [non_strict]), 134: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 135: MultiplyHandlerFn = get_hook_handler(mod2, multiply, fun hook_handler_multiply/3), 136: %% check that there are no hook handlers added yet 137: ?assertEqual([], get_handlers_for_all_hooks()), 138: %% try to add multiple hook handlers, when one of them uses local function reference 139: LocalFunctionReference = fun hook_handler_plus/3, 140: HookHandlers = [{calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{id => 2}, 2}, 141: {calculate, ?HOOK_TAG2, LocalFunctionReference, #{id => 1}, 1}, 142: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{id => 1}, 1}], 143: ?assertError(#{what := only_external_function_references_allowed, 144: function := LocalFunctionReference}, 145: gen_hook:add_handlers(HookHandlers)), 146: %% check that handlers in the list are partially added (till error occurs) 147: ?assertEqual([{{calculate, ?HOOK_TAG1}, 148: [{hook_handler, 2, MultiplyHandlerFn, 149: #{hook_name => calculate, hook_tag => ?HOOK_TAG1, id => 2}}]}], 150: get_handlers_for_all_hooks()), 151: %% try to remove the same list of handlers 152: ?assertError(#{what := only_external_function_references_allowed, 153: function := LocalFunctionReference}, 154: gen_hook:delete_handlers(HookHandlers)), 155: %% check that partially added handlers are removed 156: ?assertEqual([{{calculate, ?HOOK_TAG1}, []}], get_handlers_for_all_hooks()). 157: 158: anonymous_fun_references_causes_error(_) -> 159: %% check that there are no hook handlers added yet 160: ?assertEqual([], get_handlers_for_all_hooks()), 161: %% try to add hook handler using anonymous function reference 162: AnonymousFunctionReference = fun(Acc, _, _) -> {ok, Acc} end, 163: ?assertError(#{what := only_external_function_references_allowed, 164: function := AnonymousFunctionReference}, 165: gen_hook:add_handler(calculate, ?HOOK_TAG1, AnonymousFunctionReference, 166: #{id => 2}, 2)), 167: %% check that nothing is added 168: ?assertEqual([], get_handlers_for_all_hooks()). 169: 170: not_exported_external_fun_references_causes_error(_) -> 171: %% check that there are no hook handlers added yet 172: ?assertEqual([], get_handlers_for_all_hooks()), 173: %% try to add hook handler using function reference for a missing module 174: NotExportedExternalFunctionReference1 = fun missing_module:missing_function/3, 175: ?assertError(#{what := module_is_not_loaded, module := missing_module}, 176: gen_hook:add_handler(calculate, ?HOOK_TAG1, 177: NotExportedExternalFunctionReference1, 178: #{id => 2}, 2)), 179: %% try to add hook handler using function reference for a missing module 180: NotExportedExternalFunctionReference2 = fun ?MODULE:missing_function/3, 181: ?assertError(#{what := function_is_not_exported, 182: function := NotExportedExternalFunctionReference2}, 183: gen_hook:add_handler(calculate, ?HOOK_TAG1, 184: NotExportedExternalFunctionReference2, 185: #{id => 2}, 2)), 186: %% check that nothing is added 187: ?assertEqual([], get_handlers_for_all_hooks()). 188: 189: invalid_hook_handler_parameters_causes_error(_) -> 190: %% check that there are no hook handlers added yet 191: ?assertEqual([], get_handlers_for_all_hooks()), 192: HandlerFn = fun ?MODULE:hook_handler_stop/3, 193: InvalidHookHandlers = [{calculate, ?HOOK_TAG1, HandlerFn, invalid_extra_param, 2}, 194: {<<"invalid hook name">>, ?HOOK_TAG1, HandlerFn, #{}, 2}, 195: {calculate, ?HOOK_TAG1, HandlerFn, #{}, invalid_priority}, 196: {calculate, invalid_hook_tag, HandlerFn, #{}, 2}], 197: [?assertError(function_clause, gen_hook:add_handlers([HookHandler])) 198: || HookHandler <- InvalidHookHandlers], 199: ?assertEqual([], get_handlers_for_all_hooks()). 200: 201: run_fold_executes_handlers_in_the_right_order(_) -> 202: meck:new(mod1, [non_strict]), 203: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 204: MultiplyHandlerFn = get_hook_handler(mod1, multiply, fun hook_handler_multiply/3), 205: %% check that there are no hook handlers added yet 206: ?assertEqual([], get_handlers_for_all_hooks()), 207: %% add various hook handlers 208: HookHandlers = [{calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{n => 5}, 5}, 209: {calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{}, 2}, 210: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{n => 3}, 1}, 211: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{}, 4}], 212: ?assertEqual(ok, gen_hook:add_handlers(HookHandlers)), 213: %% run the hook 214: N = (((0 + 3) * 2) + 2) * 5, %% 40 215: ?assertEqual({ok, N}, gen_hook:run_fold(calculate, ?HOOK_TAG1, 0, #{n => 2})), 216: %% check hook handlers execution sequence 217: Self = self(), 218: ?assertEqual([{Self, 219: {mod1, plus, [0, #{n => 2}, #{hook_name => calculate, n => 3, 220: hook_tag => ?HOOK_TAG1}]}, 221: {ok, 3}}, 222: {Self, 223: {mod1, multiply, [3, #{n => 2}, #{hook_name => calculate, 224: hook_tag => ?HOOK_TAG1}]}, 225: {ok, 6}}, 226: {Self, 227: {mod1, plus, [6, #{n => 2}, #{hook_name => calculate, 228: hook_tag => ?HOOK_TAG1}]}, 229: {ok, 8}}, 230: {Self, 231: {mod1, multiply, [8, #{n => 2}, #{hook_name => calculate, n => 5, 232: hook_tag => ?HOOK_TAG1}]}, 233: {ok, 40}}], 234: meck:history(mod1)). 235: 236: run_fold_stops_when_handler_returns_stop(_) -> 237: meck:new(mod1, [non_strict]), 238: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 239: StopHandlerFn = get_hook_handler(mod1, stop, fun hook_handler_stop/3), 240: MultiplyHandlerFn = get_hook_handler(mod1, multiply, fun hook_handler_multiply/3), 241: %% check that there are no hook handlers added yet 242: ?assertEqual([], get_handlers_for_all_hooks()), 243: %% add various hook handlers 244: HookHandlers = [{calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{n => 5}, 5}, 245: {calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{}, 2}, 246: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{n => 3}, 1}, 247: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{}, 4}, 248: {calculate, ?HOOK_TAG1, StopHandlerFn, #{}, 3}], 249: ?assertEqual(ok, gen_hook:add_handlers(HookHandlers)), 250: %% run the hook 251: N = ((0 + 3) * 2), %% 6 252: ?assertEqual({stop, N}, gen_hook:run_fold(calculate, ?HOOK_TAG1, 0, #{n => 2})), 253: %% check hook handlers execution sequence 254: Self = self(), 255: ?assertEqual([{Self, 256: {mod1, plus, [0, #{n => 2}, #{hook_name => calculate, n => 3, 257: hook_tag => ?HOOK_TAG1}]}, 258: {ok, 3}}, 259: {Self, 260: {mod1, multiply, [3, #{n => 2}, #{hook_name => calculate, 261: hook_tag => ?HOOK_TAG1}]}, 262: {ok, 6}}, 263: {Self, 264: {mod1, stop, [6, #{n => 2}, #{hook_name => calculate, 265: hook_tag => ?HOOK_TAG1}]}, 266: {stop, 6}}], 267: meck:history(mod1)). 268: 269: errors_in_handlers_are_reported_but_ignored(_) -> 270: meck:new(mod1, [non_strict]), 271: meck:new(gen_hook, [passthrough]), 272: PlusHandlerFn = get_hook_handler(mod1, plus, fun hook_handler_plus/3), 273: ErrorHandlerFn = get_hook_handler(mod1, error, fun hook_handler_error/3), 274: MultiplyHandlerFn = get_hook_handler(mod1, multiply, fun hook_handler_multiply/3), 275: %% check that there are no hook handlers added yet 276: ?assertEqual([], get_handlers_for_all_hooks()), 277: %% add various hook handlers 278: HookHandlers = [{calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{n => 5}, 5}, 279: {calculate, ?HOOK_TAG1, MultiplyHandlerFn, #{}, 2}, 280: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{n => 3}, 1}, 281: {calculate, ?HOOK_TAG1, PlusHandlerFn, #{}, 4}, 282: {calculate, ?HOOK_TAG1, ErrorHandlerFn, #{}, 3}], 283: ?assertEqual(ok, gen_hook:add_handlers(HookHandlers)), 284: %% run the hook 285: N = (((0 + 3) * 2) + 2) * 5, %% 40 286: ?assertEqual({ok, N}, gen_hook:run_fold(calculate, ?HOOK_TAG1, 0, #{n => 2})), 287: %% check that error is reported 288: ?assertEqual(true, meck:called(gen_hook, error_running_hook, 289: [#{class => error, reason => some_error, stacktrace => '_'}, 290: {hook_handler, 3, ErrorHandlerFn, 291: #{hook_name => calculate, hook_tag => ?HOOK_TAG1}}, 292: 6, #{n => 2}, {calculate, ?HOOK_TAG1}])), 293: %% check hook handlers execution sequence 294: Self = self(), 295: ?assertMatch([{Self, 296: {mod1, plus, [0, #{n := 2}, #{hook_name := calculate, n := 3, 297: hook_tag := ?HOOK_TAG1}]}, 298: {ok, 3}}, 299: {Self, 300: {mod1, multiply, [3, #{n := 2}, #{hook_name := calculate, 301: hook_tag := ?HOOK_TAG1}]}, 302: {ok, 6}}, 303: {Self, 304: {mod1, error, [6, #{n := 2}, #{hook_name := calculate, 305: hook_tag := ?HOOK_TAG1}]}, 306: error, some_error, _}, 307: {Self, 308: {mod1, plus, [6, #{n := 2}, #{hook_name := calculate, 309: hook_tag := ?HOOK_TAG1}]}, 310: {ok, 8}}, 311: {Self, 312: {mod1, multiply, [8, #{n := 2}, #{hook_name := calculate, n := 5, 313: hook_tag := ?HOOK_TAG1}]}, 314: {ok, 40}}], 315: meck:history(mod1)). 316: 317: %%---------------------------------------------------------------- 318: %% helper functions 319: %%---------------------------------------------------------------- 320: hook_handler_plus(Acc, _, #{n := N}) -> 321: {ok, Acc + N}; 322: hook_handler_plus(Acc, #{n := N}, _) -> 323: {ok, Acc + N}. 324: 325: hook_handler_multiply(Acc, _, #{n := N}) -> 326: {ok, Acc * N}; 327: hook_handler_multiply(Acc, #{n := N}, _) -> 328: {ok, Acc * N}. 329: 330: hook_handler_stop(Acc, _, _) -> 331: {stop, Acc}. 332: 333: hook_handler_error(_, _, _) -> 334: error(some_error). 335: 336: get_hook_handler(ModName, FunName, Fun) when is_function(Fun, 3) -> 337: meck:expect(ModName, FunName, Fun), 338: fun ModName:FunName/3. 339: 340: get_handlers_for_all_hooks() -> 341: maps:to_list(persistent_term:get(gen_hook, #{})).