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