1: -module(mongoose_instrument_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include("log_helper.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: -include_lib("common_test/include/ct.hrl").
    7: 
    8: -define(HANDLER, mongoose_instrument_test_handler).
    9: -define(INACTIVE_HANDLER, mongoose_instrument_inactive_handler).
   10: -define(ADDED_HANDLER, mongoose_instrument_added_handler).
   11: -define(FAILING_HANDLER, mongoose_instrument_failing_handler).
   12: -define(LABELS, #{key => value}).
   13: -define(CFG, #{metrics => #{time => histogram}}).
   14: -define(MEASUREMENTS, #{count => 1}).
   15: 
   16: %% Setup and teardown
   17: 
   18: all() ->
   19:     [{group, persistent},
   20:      {group, not_persistent}
   21:     ].
   22: 
   23: groups() ->
   24:     [{persistent, [parallel], api_test_cases()},
   25:      {not_persistent, [parallel], api_test_cases()}].
   26: 
   27: api_test_cases() ->
   28:     [set_up_and_execute,
   29:      set_up_multiple_and_execute_one,
   30:      set_up_fails_when_already_registered,
   31:      set_up_fails_for_inconsistent_labels,
   32:      set_up_and_tear_down,
   33:      set_up_and_tear_down_multiple,
   34:      execute_fails_when_not_set_up,
   35:      set_up_and_span,
   36:      set_up_and_span_with_arg,
   37:      set_up_and_span_with_error,
   38:      span_fails_when_not_set_up,
   39:      unexpected_events,
   40:      add_and_remove_handler,
   41:      cannot_add_existing_handler,
   42:      cannot_remove_non_existing_handler].
   43: 
   44: init_per_suite(Config) ->
   45:     log_helper:set_up(),
   46:     mongoose_config:set_opts(opts()),
   47:     maps:map(fun mock_handler/2, mocked_handlers()),
   48:     Config.
   49: 
   50: mock_handler(Module, {SetUpResult, HandleEventF}) ->
   51:     meck:new(Module, [non_strict, no_link]),
   52:     meck:expect(Module, set_up, fun(_Event, #{}, #{}) -> SetUpResult end),
   53:     meck:expect(Module, handle_event, fun(_Event, #{}, #{}, #{}) -> HandleEventF() end).
   54: 
   55: end_per_suite(_Config) ->
   56:     lists:foreach(fun meck:unload/1, maps:keys(mocked_handlers())),
   57:     mongoose_config:erase_opts(),
   58:     log_helper:tear_down().
   59: 
   60: init_per_group(Group, Config) ->
   61:     lists:foreach(fun meck:reset/1, maps:keys(mocked_handlers())),
   62:     Config1 = async_helper:start(Config, mongoose_instrument, start_link, []),
   63:     set_up_persistence(Group),
   64:     Config1.
   65: 
   66: init_per_testcase(Case, Config) ->
   67:     log_helper:subscribe(),
   68:     [{event, event_name(Case)} | Config].
   69: 
   70: end_per_testcase(_Case, _Config) ->
   71:     log_helper:unsubscribe().
   72: 
   73: mocked_handlers() ->
   74:     #{?HANDLER => {true, fun() -> ok end},
   75:       ?INACTIVE_HANDLER => {false, fun() -> ok end},
   76:       ?ADDED_HANDLER => {true, fun() -> ok end},
   77:       ?FAILING_HANDLER => {true, fun() -> error(failed) end}}.
   78: 
   79: set_up_persistence(persistent) ->
   80:     mongoose_instrument:persist(),
   81:     ?assertEqual(#{}, persistent_term:get(mongoose_instrument));
   82: set_up_persistence(not_persistent) ->
   83:     ?assertError(badarg, persistent_term:get(mongoose_instrument)).
   84: 
   85: end_per_group(_Group, Config) ->
   86:     async_helper:stop_all(Config),
   87:     mongoose_helper:wait_until(fun() -> whereis(mongoose_instrument) end, undefined),
   88:     ?assertError(badarg, persistent_term:get(mongoose_instrument)).
   89: 
   90: opts() ->
   91:     #{instrumentation => #{test_handler => #{}, inactive_handler => #{}, failing_handler => #{}}}.
   92: 
   93: event_name(Case) ->
   94:     list_to_atom(atom_to_list(Case) ++ "_event").
   95: 
   96: %% Test cases
   97: 
   98: set_up_and_execute(Config) ->
   99:     Event = ?config(event, Config),
  100:     Measurements = ?MEASUREMENTS,
  101:     Labels = ?LABELS,
  102:     ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
  103:     ?assertEqual([{[Event, Labels, ?CFG], true}], history(?HANDLER, set_up, Event)),
  104:     ?assertEqual([{[Event, Labels, ?CFG], false}], history(?INACTIVE_HANDLER, set_up, Event)),
  105:     ?assertEqual([{[Event, Labels, ?CFG], true}], history(?FAILING_HANDLER, set_up, Event)),
  106: 
  107:     ok = mongoose_instrument:execute(Event, Labels, Measurements),
  108:     ?assertEqual([{[Event, Labels, ?CFG, Measurements], ok}],
  109:                  history(?HANDLER, handle_event, Event)),
  110:     ?assertEqual([], history(?INACTIVE_HANDLER, handle_event, Event)),
  111:     HandlerFun = fun ?FAILING_HANDLER:handle_event/4,
  112:     ?assertLog(error, #{what := event_handler_failed,
  113:                         class := error, reason := failed, stacktrace := _,
  114:                         event_name := Event, labels := Labels, measurements := Measurements,
  115:                         handler_fun := HandlerFun}).
  116: 
  117: set_up_multiple_and_execute_one(Config) ->
  118:     Event = ?config(event, Config),
  119:     Specs = [{Event, Labels1 = #{key => value1}, ?CFG},
  120:              {Event, Labels2 = #{key => value2}, ?CFG}],
  121:     ok = mongoose_instrument:set_up(Specs),
  122:     ?assertEqual([{[Event, Labels1, ?CFG], true}, {[Event, Labels2, ?CFG], true}],
  123:                  history(?HANDLER, set_up, Event)),
  124:     ok = mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS),
  125:     ?assertEqual([{[Event, Labels1, ?CFG, ?MEASUREMENTS], ok}],
  126:                  history(?HANDLER, handle_event, Event)).
  127: 
  128: set_up_fails_when_already_registered(Config) ->
  129:     Event = ?config(event, Config),
  130:     ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
  131:     ?assertError(#{what := event_already_registered},
  132:                  mongoose_instrument:set_up(Event, ?LABELS, ?CFG)),
  133:     ?assertError(#{what := event_already_registered},
  134:                  mongoose_instrument:set_up(Event, ?LABELS, #{})).
  135: 
  136: set_up_fails_for_inconsistent_labels(Config) ->
  137:     Event = ?config(event, Config),
  138:     Labels = ?LABELS,
  139:     ok = mongoose_instrument:set_up(Event, Labels, ?CFG),
  140:     ?assertError(#{what := inconsistent_labels},
  141:                  mongoose_instrument:set_up(Event, #{}, ?CFG)),
  142:     ?assertError(#{what := inconsistent_labels},
  143:                  mongoose_instrument:set_up(Event, Labels#{additional_key => value}, ?CFG)),
  144:     ?assertError(#{what := inconsistent_labels},
  145:                  mongoose_instrument:set_up(Event, #{different_key => value}, ?CFG)).
  146: 
  147: set_up_and_tear_down(Config) ->
  148:     Event = ?config(event, Config),
  149:     ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
  150:     ok = mongoose_instrument:tear_down(Event, ?LABELS),
  151:     ?assertError(#{what := event_not_registered},
  152:                  mongoose_instrument:execute(Event, ?LABELS, ?MEASUREMENTS)),
  153:     [] = history(?HANDLER, handle_event, Event),
  154:     ok = mongoose_instrument:tear_down(Event, ?LABELS). % idempotent
  155: 
  156: set_up_and_tear_down_multiple(Config) ->
  157:     Event = ?config(event, Config),
  158:     Specs = [{Event, Labels1 = #{key => value1}, ?CFG},
  159:              {Event, Labels2 = #{key => value2}, ?CFG}],
  160:     ok = mongoose_instrument:set_up(Specs),
  161:     ok = mongoose_instrument:tear_down(Specs),
  162:     ?assertError(#{what := event_not_registered},
  163:                  mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS)),
  164:     ?assertError(#{what := event_not_registered},
  165:                  mongoose_instrument:execute(Event, Labels2, ?MEASUREMENTS)),
  166:     [] = history(?HANDLER, handle_event, Event).
  167: 
  168: execute_fails_when_not_set_up(Config) ->
  169:     Event = ?config(event, Config),
  170:     ?assertError(#{what := event_not_registered},
  171:                  mongoose_instrument:execute(Event, ?LABELS, ?MEASUREMENTS)),
  172:     [] = history(?HANDLER, handle_event, Event).
  173: 
  174: set_up_and_span(Config) ->
  175:     {Event, Labels, InstrConfig} = {?config(event, Config), ?LABELS, ?CFG},
  176:     ok = mongoose_instrument:set_up(Event, Labels, InstrConfig),
  177:     ok = mongoose_instrument:span(Event, Labels, fun test_op/0, fun measure_test_op/2),
  178:     [{[Event, Labels, InstrConfig, #{time := Time, result := ok}], ok}] =
  179:         history(?HANDLER, handle_event, Event),
  180:     ?assert(Time >= 1000).
  181: 
  182: set_up_and_span_with_arg(Config) ->
  183:     {Event, Labels, InstrConfig} = {?config(event, Config), ?LABELS, ?CFG},
  184:     ok = mongoose_instrument:set_up(Event, Labels, InstrConfig),
  185:     ok = mongoose_instrument:span(Event, Labels, fun test_op/1, [2], fun measure_test_op/2),
  186:     [{[Event, Labels, InstrConfig, #{time := Time, result := ok}], ok}] =
  187:         history(?HANDLER, handle_event, Event),
  188:     ?assert(Time >= 2000).
  189: 
  190: set_up_and_span_with_error(Config) ->
  191:     Event = ?config(event, Config),
  192:     ok = mongoose_instrument:set_up(Event, ?LABELS, ?CFG),
  193:     ?assertError(simulated_error,
  194:                  mongoose_instrument:span(Event, ?LABELS, fun crashing_op/0, fun measure_test_op/2)),
  195:     [] = history(?HANDLER, handle_event, Event).
  196: 
  197: span_fails_when_not_set_up(Config) ->
  198:     Event = ?config(event, Config),
  199:     Labels = #{key => value},
  200:     ?assertError(#{what := event_not_registered},
  201:                  mongoose_instrument:span(Event, Labels, fun test_op/0, fun measure_test_op/2)),
  202:     %% Also checks that the function is not executed - otherwise 'simulated_error' would be raised
  203:     ?assertError(#{what := event_not_registered},
  204:                  mongoose_instrument:span(Event, Labels, fun crashing_op/0, fun measure_test_op/2)),
  205:     [] = history(?HANDLER, handle_event, Event).
  206: 
  207: unexpected_events(_Config) ->
  208:     Pid = whereis(mongoose_instrument),
  209:     {error, #{what := unexpected_call}} = gen_server:call(mongoose_instrument, bad_call),
  210:     gen_server:cast(mongoose_instrument, bad_cast),
  211:     mongoose_instrument ! bad_info,
  212:     ?assertEqual(Pid, whereis(mongoose_instrument)). %% It should be still working
  213: 
  214: add_and_remove_handler(Config) ->
  215:     Event = ?config(event, Config),
  216:     ok = mongoose_instrument:set_up(Event, Labels1 = #{key => value1}, ?CFG),
  217: 
  218:     %% Add handler
  219:     ok = mongoose_instrument:add_handler(added_handler, #{cfg_key => cfg_val}),
  220:     ?assertEqual(#{cfg_key => cfg_val}, mongoose_config:get_opt([instrumentation, added_handler])),
  221:     ?assertEqual([{[Event, Labels1, ?CFG], true}], history(?ADDED_HANDLER, set_up, Event)),
  222:     %% Make sure it's still possible to set up new events
  223:     ok = mongoose_instrument:set_up(Event, Labels2 = #{key => value2}, ?CFG),
  224: 
  225:     ok = mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS),
  226:     ok = mongoose_instrument:execute(Event, Labels2, ?MEASUREMENTS),
  227:     History = history(?ADDED_HANDLER, handle_event, Event),
  228:     ?assertEqual([{[Event, Labels1, ?CFG, ?MEASUREMENTS], ok},
  229:                   {[Event, Labels2, ?CFG, ?MEASUREMENTS], ok}], History),
  230: 
  231:     %% Remove handler
  232:     ok = mongoose_instrument:remove_handler(added_handler),
  233:     ?assertEqual({error, not_found}, mongoose_config:lookup_opt([instrumentation, added_handler])),
  234:     ok = mongoose_instrument:execute(Event, Labels1, ?MEASUREMENTS),
  235:     ok = mongoose_instrument:execute(Event, Labels2, ?MEASUREMENTS),
  236:     ?assertEqual(History, history(?ADDED_HANDLER, handle_event, Event)). % no new events
  237: 
  238: cannot_add_existing_handler(_Config) ->
  239:     ?assertError(#{what := handler_already_configured},
  240:                  mongoose_instrument:add_handler(test_handler, #{})).
  241: 
  242: cannot_remove_non_existing_handler(_Config) ->
  243:     ?assertError(#{what := handler_not_configured},
  244:                  mongoose_instrument:remove_handler(unknown_handler)).
  245: 
  246: %% Helpers
  247: 
  248: history(HandlerMod, WantedF, WantedEvent) ->
  249:     [{Args, Result} || {_Pid, {_M, CalledF, [Event|_] = Args}, Result} <- meck:history(HandlerMod),
  250:                        WantedF =:= CalledF andalso WantedEvent =:= Event].
  251: 
  252: test_op(Delay) ->
  253:     timer:sleep(Delay).
  254: 
  255: test_op() ->
  256:     test_op(1).
  257: 
  258: crashing_op() ->
  259:     error(simulated_error).
  260: 
  261: measure_test_op(Time, Result) ->
  262:     #{time => Time, result => Result}.