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