1: -module(mongoose_service_SUITE).
    2: 
    3: -compile([export_all, nowarn_export_all]).
    4: 
    5: -include_lib("eunit/include/eunit.hrl").
    6: 
    7: all() ->
    8:     [starts_and_stops_services,
    9:      ensures_service,
   10:      reverts_config_when_service_fails_to_start,
   11:      does_not_change_config_when_service_fails_to_stop,
   12:      replaces_services,
   13:      replaces_services_with_new_deps,
   14:      replaces_services_with_old_deps,
   15:      replaces_services_with_same_deps].
   16: 
   17: init_per_suite(C) ->
   18:     C.
   19: 
   20: end_per_suite(_C) ->
   21:     ok.
   22: 
   23: init_per_testcase(_TC, C) ->
   24:     [mock_service(Service) || Service <- test_services()],
   25:     C.
   26: 
   27: end_per_testcase(_, _C) ->
   28:     mongoose_config:erase_opts(),
   29:     meck:unload(test_services()).
   30: 
   31: %% Test cases
   32: 
   33: starts_and_stops_services(_Config) ->
   34:     set_services(Services = #{service_a => #{}, service_b => [{opt, val}]}),
   35:     ok = mongoose_service:start(),
   36:     check_started(maps:to_list(Services)),
   37: 
   38:     ok = mongoose_service:stop(),
   39:     check_stopped([service_a, service_b]).
   40: 
   41: ensures_service(_Config) ->
   42:     set_services(#{}),
   43:     ?assertEqual({started, start_result}, mongoose_service:ensure_started(service_a, #{})),
   44:     ?assertEqual(#{service_a => #{}}, get_services()),
   45: 
   46:     ?assertEqual(already_started, mongoose_service:ensure_started(service_a, #{})),
   47:     ?assertEqual(#{service_a => #{}}, get_services()),
   48: 
   49:     ?assertEqual({restarted, #{}, start_result},
   50:                  mongoose_service:ensure_started(service_a, #{opt => val})),
   51:     ?assertEqual(#{service_a => #{opt => val}}, get_services()),
   52: 
   53:     ?assertEqual({stopped, #{opt => val}}, mongoose_service:ensure_stopped(service_a)),
   54:     ?assertEqual(#{}, get_services()),
   55: 
   56:     ?assertEqual(already_stopped, mongoose_service:ensure_stopped(service_a)),
   57:     ?assertEqual(#{}, get_services()).
   58: 
   59: reverts_config_when_service_fails_to_start(_Config) ->
   60:     set_services(#{}),
   61:     meck:expect(service_a, start, fun(_) -> error(something_awful) end),
   62:     ?assertError(something_awful, mongoose_service:ensure_started(service_a, #{})),
   63:     ?assertEqual(#{}, get_services()).
   64: 
   65: does_not_change_config_when_service_fails_to_stop(_Config) ->
   66:     set_services(#{service_a => #{}}),
   67:     meck:expect(service_a, stop, fun() -> error(something_awful) end),
   68:     ?assertError(something_awful, mongoose_service:ensure_stopped(service_a)),
   69:     ?assertEqual(#{service_a => #{}}, get_services()).
   70: 
   71: replaces_services(_Config) ->
   72:     set_services(Services = #{service_a => #{}, service_b => #{opt => val}, service_c => #{}}),
   73:     ok = mongoose_service:start(),
   74:     check_started(maps:to_list(Services)),
   75: 
   76:     %% Stop service_a, change opts for service_b, do not change service_c, start service_d
   77:     NewServices = #{service_b => #{new_opt => new_val}, service_c => #{}, service_d => #{}},
   78:     ok = mongoose_service:replace_services([service_a], NewServices),
   79:     check_stopped([service_a, service_b]),
   80:     check_not_stopped([service_c]),
   81:     check_started([{service_b, #{new_opt => new_val}}, {service_d, #{}}]),
   82:     ?assertEqual(NewServices, get_services()),
   83: 
   84:     ok = mongoose_service:stop(),
   85:     check_stopped([service_b, service_c, service_d]).
   86: 
   87: replaces_services_with_new_deps(_Config) ->
   88:     set_deps(#{service_b => [service_c]}),
   89:     set_services(Services = #{service_a => #{}}),
   90:     ok = mongoose_service:start(),
   91:     check_started(maps:to_list(Services)),
   92: 
   93:     %% Start service_b, which depends on service_c
   94:     ok = mongoose_service:replace_services([], #{service_b => #{}}),
   95:     check_not_stopped([service_a]),
   96:     check_started([{service_b, #{}}, {service_c, #{}}]),
   97:     ?assertEqual(Services#{service_b => #{}, service_c => #{}}, get_services()),
   98: 
   99:     ok = mongoose_service:stop(),
  100:     check_stopped([service_a, service_b, service_c]).
  101: 
  102: replaces_services_with_old_deps(_Config) ->
  103:     set_deps(#{service_a => [service_c]}),
  104:     set_services(Services = #{service_a => #{}, service_c => #{}}),
  105:     ok = mongoose_service:start(),
  106:     check_started(maps:to_list(Services)),
  107: 
  108:     %% Stop service_a, which depends on service_c, and start service_b
  109:     ok = mongoose_service:replace_services([service_a], #{service_b => #{}}),
  110:     check_stopped([service_a, service_c]),
  111:     check_started([{service_b, #{}}]),
  112:     ?assertEqual(#{service_b => #{}}, get_services()),
  113: 
  114:     ok = mongoose_service:stop(),
  115:     check_stopped([service_b]).
  116: 
  117: replaces_services_with_same_deps(_Config) ->
  118:     set_deps(#{service_a => [service_c], service_b => [service_c]}),
  119:     set_services(Services = #{service_a => #{}, service_c => #{}}),
  120:     ok = mongoose_service:start(),
  121:     check_started(maps:to_list(Services)),
  122: 
  123:     %% Stop service_a, and start service_b, both depending on service_c
  124:     ok = mongoose_service:replace_services([service_a], #{service_b => #{}}),
  125:     check_stopped([service_a]),
  126:     check_not_stopped([service_c]),
  127:     check_started([{service_b, #{}}]),
  128:     ?assertEqual(#{service_b => #{}, service_c => #{}}, get_services()),
  129: 
  130:     ok = mongoose_service:stop(),
  131:     check_stopped([service_b]).
  132: 
  133: %% Helpers
  134: 
  135: set_services(Services) ->
  136:     mongoose_config:set_opts(#{services => Services}).
  137: 
  138: get_services() ->
  139:     mongoose_config:get_opt(services).
  140: 
  141: check_started(ServicesWithOpts) ->
  142:     lists:foreach(fun({Service, Opts}) ->
  143:                           ?assert(meck:called(Service, start, [Opts]))
  144:                   end, ServicesWithOpts).
  145: 
  146: check_stopped(Services) ->
  147:     lists:foreach(fun(Service) ->
  148:                           ?assert(meck:called(Service, stop, []))
  149:                   end, Services).
  150: 
  151: check_not_stopped(Services) ->
  152:     lists:foreach(fun(Service) ->
  153:                           ?assertNot(meck:called(Service, stop, []))
  154:                   end, Services).
  155: 
  156: mock_service(Service) ->
  157:     meck:new(Service, [non_strict]),
  158:     meck:expect(Service, start, fun(_) -> start_result end),
  159:     meck:expect(Service, stop, fun() -> ok end).
  160: 
  161: set_deps(DepsMap) ->
  162:     maps:fold(fun(Service, Deps, _) -> meck:expect(Service, deps, fun() -> Deps end) end,
  163:               undefined, DepsMap).
  164: 
  165: test_services() ->
  166:     [service_a, service_b, service_c, service_d].