1: -module(mongoose_modules_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("eunit/include/eunit.hrl").
    5: 
    6: -define(MODS, [mod_a, mod_b, mod_c, mod_d]).
    7: 
    8: -define(HOST, <<"localhost">>).
    9: 
   10: all() ->
   11:     [starts_and_stops_modules,
   12:      ensures_module,
   13:      reverts_config_when_module_fails_to_start,
   14:      does_not_change_config_when_module_fails_to_stop,
   15:      replaces_modules,
   16:      replaces_modules_with_new_deps,
   17:      replaces_modules_with_old_deps,
   18:      replaces_modules_with_same_deps].
   19: 
   20: init_per_suite(C) ->
   21:     [mongoose_config:set_opt(Opt, Val) || {Opt, Val} <- opts()],
   22:     C.
   23: 
   24: end_per_suite(_C) ->
   25:     [mongoose_config:unset_opt(Opt) || {Opt, _} <- opts()],
   26:     ok.
   27: 
   28: init_per_testcase(_TC, C) ->
   29:     meck:new(gen_mod, [passthrough]),
   30:     meck:expect(gen_mod, start_module, fun(_, _, _) -> {ok, start_result} end),
   31:     meck:expect(gen_mod, stop_module, fun(_, _) -> ok end),
   32:     meck:new(?MODS, [non_strict]),
   33:     C.
   34: 
   35: end_per_testcase(_, _C) ->
   36:     mongoose_config:unset_opt({modules, ?HOST}),
   37:     meck:unload(gen_mod),
   38:     meck:unload(?MODS).
   39: 
   40: starts_and_stops_modules(_Config) ->
   41:     set_modules(Modules = #{mod_a => [], mod_b => [{opt, val}]}),
   42:     ok = mongoose_modules:start(),
   43:     check_started(maps:to_list(Modules)),
   44: 
   45:     ok = mongoose_modules:stop(),
   46:     check_stopped([mod_a, mod_b]).
   47: 
   48: ensures_module(_Config) ->
   49:     set_modules(#{}),
   50:     ?assertEqual({started, start_result}, mongoose_modules:ensure_started(?HOST, mod_a, [])),
   51:     ?assertEqual(#{mod_a => []}, get_modules()),
   52: 
   53:     ?assertEqual(already_started, mongoose_modules:ensure_started(?HOST, mod_a, [])),
   54:     ?assertEqual(#{mod_a => []}, get_modules()),
   55: 
   56:     ?assertEqual({restarted, [], start_result},
   57:                  mongoose_modules:ensure_started(?HOST, mod_a, [{opt, val}])),
   58:     ?assertEqual(#{mod_a => [{opt, val}]}, get_modules()),
   59: 
   60:     ?assertEqual({stopped, [{opt, val}]}, mongoose_modules:ensure_stopped(?HOST, mod_a)),
   61:     ?assertEqual(#{}, get_modules()),
   62: 
   63:     ?assertEqual(already_stopped, mongoose_modules:ensure_stopped(?HOST, mod_a)),
   64:     ?assertEqual(#{}, get_modules()).
   65: 
   66: reverts_config_when_module_fails_to_start(_Config) ->
   67:     set_modules(#{}),
   68:     meck:expect(gen_mod, start_module, fun(_, _, _) -> error(something_awful) end),
   69:     ?assertError(something_awful, mongoose_modules:ensure_started(?HOST, mod_a, [])),
   70:     ?assertEqual(#{}, get_modules()).
   71: 
   72: does_not_change_config_when_module_fails_to_stop(_Config) ->
   73:     set_modules(#{mod_a => []}),
   74:     meck:expect(gen_mod, stop_module, fun(_, _) -> error(something_awful) end),
   75:     ?assertError(something_awful, mongoose_modules:ensure_stopped(?HOST, mod_a)),
   76:     ?assertEqual(#{mod_a => []}, get_modules()).
   77: 
   78: replaces_modules(_Config) ->
   79:     set_modules(Modules = #{mod_a => [], mod_b => [{opt, val}], mod_c => []}),
   80:     ok = mongoose_modules:start(),
   81:     check_started(maps:to_list(Modules)),
   82: 
   83:     %% Stop mod_a, change opts for mod_b, do not change mod_c, start mod_d
   84:     NewModules = #{mod_b => [{new_opt, new_val}], mod_c => [], mod_d => []},
   85:     ok = mongoose_modules:replace_modules(?HOST, [mod_a], NewModules),
   86:     check_stopped([mod_a, mod_b]),
   87:     check_not_stopped([mod_c]),
   88:     check_started([{mod_b, [{new_opt, new_val}]}, {mod_d, []}]),
   89:     ?assertEqual(NewModules, get_modules()),
   90: 
   91:     ok = mongoose_modules:stop(),
   92:     check_stopped([mod_b, mod_c, mod_d]).
   93: 
   94: replaces_modules_with_new_deps(_Config) ->
   95:     set_deps(#{mod_b => [{mod_c, hard}]}),
   96:     set_modules(Modules = #{mod_a => []}),
   97:     ok = mongoose_modules:start(),
   98:     check_started(maps:to_list(Modules)),
   99: 
  100:     %% Start mod_b, which depends on mod_c
  101:     ok = mongoose_modules:replace_modules(?HOST, [], #{mod_b => []}),
  102:     check_not_stopped([mod_a]),
  103:     check_started([{mod_b, []}, {mod_c, []}]),
  104:     ?assertEqual(Modules#{mod_b => [], mod_c => []}, get_modules()),
  105: 
  106:     ok = mongoose_modules:stop(),
  107:     check_stopped([mod_a, mod_b, mod_c]).
  108: 
  109: replaces_modules_with_old_deps(_Config) ->
  110:     set_deps(#{mod_a => [{mod_c, hard}]}),
  111:     set_modules(Modules = #{mod_a => [], mod_c => []}),
  112:     ok = mongoose_modules:start(),
  113:     check_started(maps:to_list(Modules)),
  114: 
  115:     %% Stop mod_a, which depends on mod_c, and start mod_b
  116:     ok = mongoose_modules:replace_modules(?HOST, [mod_a], #{mod_b => []}),
  117:     check_stopped([mod_a, mod_c]),
  118:     check_started([{mod_b, []}]),
  119:     ?assertEqual(#{mod_b => []}, get_modules()),
  120: 
  121:     ok = mongoose_modules:stop(),
  122:     check_stopped([mod_b]).
  123: 
  124: replaces_modules_with_same_deps(_Config) ->
  125:     set_deps(#{mod_a => [{mod_c, hard}], mod_b => [{mod_c, hard}]}),
  126:     set_modules(Modules = #{mod_a => [], mod_c => []}),
  127:     ok = mongoose_modules:start(),
  128:     check_started(maps:to_list(Modules)),
  129: 
  130:     %% Stop mod_a, and start mod_b, both depending on mod_c
  131:     ok = mongoose_modules:replace_modules(?HOST, [mod_a], #{mod_b => []}),
  132:     check_stopped([mod_a]),
  133:     check_not_stopped([mod_c]),
  134:     check_started([{mod_b, []}]),
  135:     ?assertEqual(#{mod_b => [], mod_c => []}, get_modules()),
  136: 
  137:     ok = mongoose_modules:stop(),
  138:     check_stopped([mod_b]).
  139: 
  140: check_started(ModulesWithOpts) ->
  141:     lists:foreach(fun({Mod, Opts}) ->
  142:                           ?assert(meck:called(gen_mod, start_module, [?HOST, Mod, Opts]))
  143:                   end, ModulesWithOpts).
  144: 
  145: check_stopped(Modules) ->
  146:     lists:foreach(fun(Mod) ->
  147:                           ?assert(meck:called(gen_mod, stop_module, [?HOST, Mod]))
  148:                   end, Modules).
  149: 
  150: check_not_stopped(Modules) ->
  151:     lists:foreach(fun(Mod) ->
  152:                           ?assertNot(meck:called(gen_mod, stop_module, [?HOST, Mod]))
  153:                   end, Modules).
  154: 
  155: check_modules(ExpectedModules) ->
  156:     ?assertEqual(ExpectedModules, gen_mod:loaded_modules_with_opts(?HOST)).
  157: 
  158: opts() ->
  159:     [{hosts, [?HOST]},
  160:      {host_types, []}].
  161: 
  162: set_modules(Modules) ->
  163:     mongoose_config:set_opt({modules, ?HOST}, Modules).
  164: 
  165: get_modules() ->
  166:     mongoose_config:get_opt({modules, ?HOST}).
  167: 
  168: set_deps(DepsMap) ->
  169:     maps:fold(fun(Mod, Deps, _) -> meck:expect(Mod, deps, fun(_, _) -> Deps end) end,
  170:               undefined, DepsMap).