1: -module(gen_mod_deps_SUITE).
    2: -compile([export_all, nowarn_export_all]).
    3: 
    4: -include_lib("common_test/include/ct.hrl").
    5: -include_lib("eunit/include/eunit.hrl").
    6: 
    7: -define(MODS, [mod_a, mod_b, mod_c, mod_d]).
    8: 
    9: all() ->
   10:     [
   11:      starts_modules,
   12:      starts_dependencies,
   13:      starts_dependency_chain,
   14:      starts_dependency_dag,
   15:      fails_on_dependency_cycle,
   16:      succeeds_on_cycle_with_soft_dep_in_path,
   17:      succeeds_on_adding_soft_dependency_cycle_edge,
   18:      forces_dependency_args,
   19:      appends_dependencies_args,
   20:      optional_dependencies_are_not_started_by_themselves,
   21:      optional_dependencies_add_arguments,
   22:      overrides_dependency_args,
   23:      merges_dependency_args,
   24:      replaces_modules,
   25:      reloads_modules_with_changed_args
   26:     ].
   27: 
   28: %% Fixtures
   29: 
   30: init_per_testcase(_, C) ->
   31:     meck:new(gen_mod, [passthrough]),
   32:     meck:new(?MODS, [non_strict]),
   33:     meck:expect(gen_mod, start_module,  fun(_, _, _) -> ok end),
   34:     meck:expect(gen_mod, stop_module,   fun(_, _)    -> ok end),
   35:     meck:expect(gen_mod, reload_module, fun(_, _, _) -> ok end),
   36:     meck:expect(gen_mod, get_deps, fun(Host, Mod, Opts) -> meck:passthrough([Host, Mod, Opts]) end),
   37:     C.
   38: 
   39: end_per_testcase(_, C) ->
   40:     meck:unload(gen_mod),
   41:     meck:unload(?MODS),
   42:     C.
   43: 
   44: %% Tests
   45: 
   46: starts_modules(_Config) ->
   47:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}, {mod_b, []}]),
   48:     check_started([mod_a, mod_b]).
   49: 
   50: 
   51: starts_dependencies(_Config) ->
   52:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, hard}]}),
   53:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   54:     check_started([mod_a, mod_b, mod_c]).
   55: 
   56: 
   57: starts_dependency_chain(_Config) ->
   58:     set_deps(#{mod_a => [{mod_b, hard}], mod_b => [{mod_c, hard}]}),
   59:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   60: 
   61:     check_started([mod_a, mod_b, mod_c]),
   62: 
   63:     StartOrder = [Mod || {_, {_, start_module, [_, Mod, _]}, _} <- meck:history(gen_mod)],
   64:     ?assertEqual([mod_c, mod_b, mod_a], StartOrder).
   65: 
   66: 
   67: starts_dependency_dag(_Config) ->
   68:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, hard}],
   69:                mod_b => [{mod_c, hard}]}),
   70:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   71:     check_started([mod_a, mod_b, mod_c]).
   72: 
   73: 
   74: fails_on_dependency_cycle(_Config) ->
   75:     set_deps(#{mod_a => [{mod_b, hard}],
   76:                mod_b => [{mod_c, hard}],
   77:                mod_c => [{mod_a, hard}]}),
   78:     ?assertError(_, gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}])).
   79: 
   80: 
   81: succeeds_on_cycle_with_soft_dep_in_path(_Config) ->
   82:     set_deps(#{mod_a => [{mod_b, hard}],
   83:                mod_b => [{mod_c, soft}],
   84:                mod_c => [{mod_a, hard}]}),
   85:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   86:     check_started([mod_a, mod_b, mod_c]).
   87: 
   88: 
   89: succeeds_on_adding_soft_dependency_cycle_edge(_Config) ->
   90:     set_deps(#{mod_a => [{mod_b, hard}], mod_b => [{mod_a, soft}]}),
   91:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   92:     check_started([mod_a, mod_b]).
   93: 
   94: 
   95: forces_dependency_args(_Config) ->
   96:     set_deps(#{mod_a => [{mod_b, [arg1, arg2], hard}]}),
   97:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
   98:     check_started({mod_b, [arg1, arg2]}).
   99: 
  100: 
  101: appends_dependencies_args(_Config) ->
  102:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, [arg1], hard}],
  103:                mod_b => [{mod_c, [arg2], hard}]}),
  104:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  105:     check_started({mod_c, [arg1, arg2]}).
  106: 
  107: 
  108: optional_dependencies_are_not_started_by_themselves(_Config) ->
  109:     set_deps(#{mod_a => [{mod_b, optional}]}),
  110:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  111:     ?assertNot(started(mod_b)).
  112: 
  113: 
  114: optional_dependencies_add_arguments(_Config) ->
  115:     set_deps(#{mod_a => [{mod_b, [arg1], optional}], mod_c => [{mod_b, hard}]}),
  116:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}, {mod_c, []}]),
  117:     check_started({mod_b, [arg1]}).
  118: 
  119: 
  120: overrides_dependency_args(_Config) ->
  121:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, [{arg, a}], hard}],
  122:                mod_b => [{mod_c, [{arg, b}], hard}]}),
  123:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  124: 
  125:     ?assert(meck:called(gen_mod, start_module, ['_', mod_c, [{arg, a}]]) orelse
  126:             meck:called(gen_mod, start_module, ['_', mod_c, [{arg, b}]])).
  127: 
  128: 
  129: merges_dependency_args(_Config) ->
  130:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, [{arg, a}], hard}],
  131:                mod_b => [{mod_c, [{arg, a}, arg2], hard}]}),
  132:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  133: 
  134:     check_started({mod_c, [{arg, a}, arg2]}).
  135: 
  136: 
  137: replaces_modules(_Config) ->
  138:     set_deps(#{mod_a => [{mod_b, hard}, {mod_c, hard}]}),
  139:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  140: 
  141:     meck:reset(gen_mod),
  142: 
  143:     gen_mod_deps:replace_modules(<<"host">>, [{mod_a, []}], [{mod_d, []}, {mod_b, []}]),
  144: 
  145:     check_stopped(mod_a),
  146:     ?assertNot(meck:called(gen_mod, replace_module, '_')),
  147:     check_started(mod_d).
  148: 
  149: 
  150: reloads_modules_with_changed_args(_Config) ->
  151:     set_deps(#{mod_a => [{mod_b, hard}]}),
  152:     gen_mod_deps:start_modules(<<"host">>, [{mod_a, []}]),
  153:     gen_mod_deps:replace_modules(<<"host">>, [{mod_a, []}], [{mod_b, [arg]}]),
  154:     check_reloaded({mod_b, [arg]}).
  155: 
  156: %% Helpers
  157: 
  158: set_deps(DepsMap) ->
  159:     maps:fold(fun(Mod, Deps, _) -> meck:expect(Mod, deps, fun(_, _) -> Deps end) end,
  160:               undefined, DepsMap).
  161: 
  162: 
  163: check_started([]) -> ok;
  164: check_started([ModNArgs | Mods]) ->
  165:     ?assert(started(ModNArgs)),
  166:     check_started(Mods);
  167: check_started(Mod) ->
  168:     check_started([Mod]).
  169: 
  170: started({Mod, Args}) ->
  171:     meck:called(gen_mod, start_module, ['_', Mod, Args]);
  172: started(Mod) ->
  173:     meck:called(gen_mod, start_module, ['_', Mod, '_']).
  174: 
  175: check_stopped([]) -> ok;
  176: check_stopped([Mod | Mods]) ->
  177:     ?assert(meck:called(gen_mod, stop_module, ['_', Mod])),
  178:     check_stopped(Mods);
  179: check_stopped(Mod) ->
  180:     check_stopped([Mod]).
  181: 
  182: 
  183: check_reloaded([]) -> ok;
  184: check_reloaded([{Mod, Args} | Mods]) ->
  185:     ?assert(meck:called(gen_mod, reload_module, ['_', Mod, Args])),
  186:     check_reloaded(Mods);
  187: check_reloaded([Mod | Mods]) ->
  188:     ?assert(meck:called(gen_mod, reload_module, ['_', Mod, '_'])),
  189:     check_reloaded(Mods);
  190: check_reloaded(Mod) ->
  191:     check_reloaded([Mod]).