1: %%%-------------------------------------------------------------------
    2: %%% @doc
    3: %%%
    4: %%% @end
    5: %%%-------------------------------------------------------------------
    6: -module(mongoose_service_SUITE).
    7: -compile([export_all, nowarn_export_all]).
    8: -include_lib("common_test/include/ct.hrl").
    9: -include_lib("eunit/include/eunit.hrl").
   10: 
   11: 
   12: all() ->
   13:     [
   14:         starts_service,
   15:         ensure,
   16:         verify_deps,
   17:         start_deps,
   18:         module_deps,
   19:         misconfigured
   20:     ].
   21: 
   22: services() -> [service_a, service_b, service_c, service_d, service_e, service_f, service_g,
   23:                service_h, service_i, service_j, service_k, service_l].
   24: 
   25: dep_services() -> [service_c, service_d, service_e, service_f, service_g, service_h].
   26: 
   27: %%      c
   28: %%     / \
   29: %%    d   e
   30: %%   / \ / \
   31: %%  f   g   h
   32: %%  ^       |
   33: %%  |       |
   34: %%   -------
   35: 
   36: %%    i
   37: %%   / \
   38: %%  j   k <--
   39: %%       \   |
   40: %%        l--
   41: 
   42: service_deps() ->
   43:     [
   44:         {service_c, [service_d, service_e]},
   45:         {service_d, [service_f, service_g]},
   46:         {service_e, [service_g, service_h]},
   47:         {service_h, [service_f]},
   48:         % and now for something completely cicrular
   49:         {service_i, [service_j, service_k]},
   50:         {service_k, [service_l]},
   51:         {service_l, [service_k]}
   52:     ].
   53: 
   54: init_per_testcase(misconfigured, C) ->
   55:     mongoose_service:start(),
   56:     mongoose_config:set_opt(services, [{service_a, []}]),
   57:     meck:new(service_a, [non_strict]),
   58:     meck:expect(service_a, deps, fun() -> [service_b] end),
   59:     C;
   60: 
   61: init_per_testcase(module_deps, C) ->
   62:     init_per_testcase(generic, C),
   63:     mongoose_config:set_opt(hosts, [<<"localhost">>]),
   64:     gen_mod:start(),
   65:     meck:new(module_a, [non_strict]),
   66:     meck:expect(module_a, deps, fun(_, _) -> [{service, service_d}, {service, service_h}] end),
   67:     meck:expect(module_a, start, fun(_, _) -> ok end),
   68:     meck:expect(module_a, stop, fun(_) -> ok end),
   69:     C;
   70: init_per_testcase(_, C) ->
   71:     mongoose_service:start(),
   72:     ets:new(testservice, [named_table]),
   73:     meck:new(services(), [non_strict]),
   74:     lists:map(fun(S) ->
   75:                   meck:expect(S, start, fun(_Opts) -> increment(S), done end)
   76:               end, services()),
   77:     lists:map(fun(S) ->
   78:                   meck:expect(S, stop, fun() -> decrement(S) end)
   79:               end, services()),
   80:     mongoose_config:set_opt(services, [{Serv, []} || Serv <- services()]),
   81:     lists:map(fun(Serv) ->
   82:                   meck:expect(Serv, deps, fun() -> proplists:get_value(Serv, service_deps()) end)
   83:               end,
   84:               proplists:get_keys(service_deps())),
   85:     C.
   86: 
   87: end_per_testcase(misconfigured, C) ->
   88:     mongoose_config:unset_opt(services),
   89:     meck:unload(service_a),
   90:     C;
   91: 
   92: end_per_testcase(module_deps, C) ->
   93:     mongoose_config:unset_opt(hosts),
   94:     meck:unload(module_a),
   95:     end_per_testcase(generic, C);
   96: end_per_testcase(_, C) ->
   97:     mongoose_config:unset_opt(services),
   98:     meck:unload(services()),
   99:     lists:foreach(fun mongoose_service:stop_service/1, services()),
  100:     mongoose_service:stop(),
  101:     C.
  102: 
  103: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  104: %%
  105: %% tests
  106: %%
  107: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  108: 
  109: starts_service(_C) ->
  110:     {ok, done} = mongoose_service:start_service(service_a, []),
  111:     {error, already_started} = mongoose_service:start_service(service_a, []),
  112:     ?assertEqual(1, read(service_a)),
  113:     {ok, done} = mongoose_service:start_service(service_b, []),
  114:     {error, already_started} = mongoose_service:start_service(service_b, []),
  115:     ?assertEqual(1, read(service_b)),
  116:     ok = mongoose_service:stop_service(service_a),
  117:     ?assertEqual(0, read(service_a)),
  118:     {error, not_running} = mongoose_service:stop_service(service_a),
  119:     ?assertEqual(0, read(service_a)),
  120:     ok = mongoose_service:stop_service(service_b),
  121:     ?assertEqual(0, read(service_b)),
  122:     {error, not_running} = mongoose_service:stop_service(service_b),
  123:     ?assertEqual(0, read(service_b)),
  124:     ok.
  125: 
  126: ensure(_C) ->
  127:     ok = mongoose_service:ensure_loaded(service_a),
  128:     ok = mongoose_service:ensure_loaded(service_a),
  129:     ok = mongoose_service:ensure_loaded(service_a),
  130:     ?assertEqual(1, read(service_a)),
  131:     ok = mongoose_service:stop_service(service_a),
  132:     ?assertEqual(0, read(service_a)),
  133:     ok.
  134: 
  135: verify_deps(_) ->
  136:     mongoose_service:check_deps(service_c),
  137:     mongoose_service:check_deps(service_d),
  138:     mongoose_service:check_deps(service_e),
  139:     mongoose_service:check_deps(service_f),
  140:     mongoose_service:check_deps(service_g),
  141:     mongoose_service:check_deps(service_g),
  142:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_i)),
  143:     mongoose_service:check_deps(service_j),
  144:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_k)),
  145:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_l)),
  146:     ok.
  147: 
  148: start_deps(_) ->
  149:     assert_loaded([]),
  150:     mongoose_service:ensure_loaded(service_f),
  151:     assert_loaded([service_f]),
  152:     mongoose_service:ensure_loaded(service_d),
  153:     assert_loaded([service_d, service_f, service_g]),
  154:     mongoose_service:ensure_loaded(service_e),
  155:     assert_loaded([service_d, service_e, service_f, service_g, service_h]),
  156:     mongoose_service:ensure_loaded(service_c),
  157:     assert_loaded(dep_services()),
  158:     lists:foreach(fun mongoose_service:stop_service/1, services()),
  159:     assert_loaded([]),
  160:     mongoose_service:ensure_loaded(service_c),
  161:     assert_loaded(dep_services()),
  162:     ok.
  163: 
  164: module_deps(_) ->
  165:     assert_loaded([]),
  166:     meck:new(gen_mod, [passthrough]),
  167:     meck:expect(gen_mod, is_app_running, fun(_Mooo) -> true end),
  168:     ?assertError({service_not_loaded, _}, gen_mod:start_module(<<"localhost">>, module_a, [])),
  169:     meck:unload(gen_mod),
  170:     mongoose_service:ensure_loaded(service_c),
  171:     gen_mod_deps:start_modules(<<"localhost">>, [{module_a, []}]),
  172:     ?assert(gen_mod:is_loaded(<<"localhost">>, module_a)),
  173:     ok.
  174: 
  175: misconfigured(_) ->
  176:     ?assertError({service_not_configured, service_b}, mongoose_service:ensure_loaded(service_a)),
  177:     ok.
  178: 
  179: 
  180: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  181: %%
  182: %% helpers
  183: %%
  184: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  185: 
  186: increment(S) ->
  187:     Ni = case ets:lookup(testservice, S) of
  188:              [{S, I}] -> I + 1;
  189:              [] -> 1
  190:          end,
  191:     ets:insert(testservice, {S, Ni}).
  192: 
  193: decrement(S) ->
  194:     Ni = case ets:lookup(testservice, S) of
  195:              [{S, I}] -> I - 1;
  196:              [] -> -1
  197:          end,
  198:     ets:insert(testservice, {S, Ni}).
  199: 
  200: read(S) ->
  201:     case ets:lookup(testservice, S) of
  202:         [{S, I}] -> I;
  203:         [] -> 0
  204:     end.
  205: 
  206: assert_loaded(Loaded) ->
  207:     _ = [{S, mongoose_service:is_loaded(S)} || S <- services()],
  208:     NotLoaded = sets:to_list(
  209:                     sets:subtract(
  210:                         sets:from_list(dep_services()),
  211:                         sets:from_list(Loaded))),
  212:     ?assert(lists:all(fun mongoose_service:is_loaded/1, Loaded)),
  213:     ?assert(lists:all(fun(S) -> not mongoose_service:is_loaded(S) end, NotLoaded)),
  214:     ok.