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:     mongoose_config:set_opt(host_types, []),
   65:     mongoose_config:set_opt({modules, <<"localhost">>}, #{module_a => []}),
   66:     meck:new(module_a, [non_strict]),
   67:     meck:expect(module_a, deps, fun(_, _) -> [{service, service_d}, {service, service_h}] end),
   68:     meck:expect(module_a, start, fun(_, _) -> ok end),
   69:     meck:expect(module_a, stop, fun(_) -> ok end),
   70:     C;
   71: init_per_testcase(_, C) ->
   72:     mongoose_service:start(),
   73:     ets:new(testservice, [named_table]),
   74:     meck:new(services(), [non_strict]),
   75:     lists:map(fun(S) ->
   76:                   meck:expect(S, start, fun(_Opts) -> increment(S), done end)
   77:               end, services()),
   78:     lists:map(fun(S) ->
   79:                   meck:expect(S, stop, fun() -> decrement(S) end)
   80:               end, services()),
   81:     mongoose_config:set_opt(services, [{Serv, []} || Serv <- services()]),
   82:     lists:map(fun(Serv) ->
   83:                   meck:expect(Serv, deps, fun() -> proplists:get_value(Serv, service_deps()) end)
   84:               end,
   85:               proplists:get_keys(service_deps())),
   86:     C.
   87: 
   88: end_per_testcase(misconfigured, C) ->
   89:     mongoose_config:unset_opt(services),
   90:     meck:unload(service_a),
   91:     C;
   92: 
   93: end_per_testcase(module_deps, C) ->
   94:     mongoose_config:unset_opt(hosts),
   95:     mongoose_config:unset_opt(host_types),
   96:     mongoose_config:unset_opt({modules, <<"localhost">>}),
   97:     meck:unload(module_a),
   98:     end_per_testcase(generic, C);
   99: end_per_testcase(_, C) ->
  100:     mongoose_config:unset_opt(services),
  101:     meck:unload(services()),
  102:     lists:foreach(fun mongoose_service:stop_service/1, services()),
  103:     mongoose_service:stop(),
  104:     C.
  105: 
  106: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  107: %%
  108: %% tests
  109: %%
  110: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  111: 
  112: starts_service(_C) ->
  113:     {ok, done} = mongoose_service:start_service(service_a, []),
  114:     {error, already_started} = mongoose_service:start_service(service_a, []),
  115:     ?assertEqual(1, read(service_a)),
  116:     {ok, done} = mongoose_service:start_service(service_b, []),
  117:     {error, already_started} = mongoose_service:start_service(service_b, []),
  118:     ?assertEqual(1, read(service_b)),
  119:     ok = mongoose_service:stop_service(service_a),
  120:     ?assertEqual(0, read(service_a)),
  121:     {error, not_running} = mongoose_service:stop_service(service_a),
  122:     ?assertEqual(0, read(service_a)),
  123:     ok = mongoose_service:stop_service(service_b),
  124:     ?assertEqual(0, read(service_b)),
  125:     {error, not_running} = mongoose_service:stop_service(service_b),
  126:     ?assertEqual(0, read(service_b)),
  127:     ok.
  128: 
  129: ensure(_C) ->
  130:     ok = mongoose_service:ensure_loaded(service_a),
  131:     ok = mongoose_service:ensure_loaded(service_a),
  132:     ok = mongoose_service:ensure_loaded(service_a),
  133:     ?assertEqual(1, read(service_a)),
  134:     ok = mongoose_service:stop_service(service_a),
  135:     ?assertEqual(0, read(service_a)),
  136:     ok.
  137: 
  138: verify_deps(_) ->
  139:     mongoose_service:check_deps(service_c),
  140:     mongoose_service:check_deps(service_d),
  141:     mongoose_service:check_deps(service_e),
  142:     mongoose_service:check_deps(service_f),
  143:     mongoose_service:check_deps(service_g),
  144:     mongoose_service:check_deps(service_g),
  145:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_i)),
  146:     mongoose_service:check_deps(service_j),
  147:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_k)),
  148:     ?assertException(error, {circular_deps_detected, _}, mongoose_service:check_deps(service_l)),
  149:     ok.
  150: 
  151: start_deps(_) ->
  152:     assert_loaded([]),
  153:     mongoose_service:ensure_loaded(service_f),
  154:     assert_loaded([service_f]),
  155:     mongoose_service:ensure_loaded(service_d),
  156:     assert_loaded([service_d, service_f, service_g]),
  157:     mongoose_service:ensure_loaded(service_e),
  158:     assert_loaded([service_d, service_e, service_f, service_g, service_h]),
  159:     mongoose_service:ensure_loaded(service_c),
  160:     assert_loaded(dep_services()),
  161:     lists:foreach(fun mongoose_service:stop_service/1, services()),
  162:     assert_loaded([]),
  163:     mongoose_service:ensure_loaded(service_c),
  164:     assert_loaded(dep_services()),
  165:     ok.
  166: 
  167: module_deps(_) ->
  168:     assert_loaded([]),
  169:     meck:new(gen_mod, [passthrough]),
  170:     meck:expect(gen_mod, is_app_running, fun(_Mooo) -> true end),
  171:     ?assertError({service_not_loaded, _}, mongoose_modules:start()),
  172:     meck:unload(gen_mod),
  173:     mongoose_service:ensure_loaded(service_c),
  174:     mongoose_modules:start(),
  175:     ?assert(gen_mod:is_loaded(<<"localhost">>, module_a)),
  176:     ok.
  177: 
  178: misconfigured(_) ->
  179:     ?assertError({service_not_configured, service_b}, mongoose_service:ensure_loaded(service_a)),
  180:     ok.
  181: 
  182: 
  183: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  184: %%
  185: %% helpers
  186: %%
  187: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
  188: 
  189: increment(S) ->
  190:     Ni = case ets:lookup(testservice, S) of
  191:              [{S, I}] -> I + 1;
  192:              [] -> 1
  193:          end,
  194:     ets:insert(testservice, {S, Ni}).
  195: 
  196: decrement(S) ->
  197:     Ni = case ets:lookup(testservice, S) of
  198:              [{S, I}] -> I - 1;
  199:              [] -> -1
  200:          end,
  201:     ets:insert(testservice, {S, Ni}).
  202: 
  203: read(S) ->
  204:     case ets:lookup(testservice, S) of
  205:         [{S, I}] -> I;
  206:         [] -> 0
  207:     end.
  208: 
  209: assert_loaded(Loaded) ->
  210:     _ = [{S, mongoose_service:is_loaded(S)} || S <- services()],
  211:     NotLoaded = sets:to_list(
  212:                     sets:subtract(
  213:                         sets:from_list(dep_services()),
  214:                         sets:from_list(Loaded))),
  215:     ?assert(lists:all(fun mongoose_service:is_loaded/1, Loaded)),
  216:     ?assert(lists:all(fun(S) -> not mongoose_service:is_loaded(S) end, NotLoaded)),
  217:     ok.