1: -module(ejabberd_sm_SUITE).
    2: -include_lib("eunit/include/eunit.hrl").
    3: -include_lib("common_test/include/ct.hrl").
    4: 
    5: -include("ejabberd_c2s.hrl").
    6: -include("mongoose.hrl").
    7: -include_lib("jid/include/jid.hrl").
    8: -include_lib("session.hrl").
    9: -compile([export_all, nowarn_export_all]).
   10: 
   11: 
   12: -define(_eq(E, I), ?_assertEqual(E, I)).
   13: -define(eq(E, I), ?assertEqual(E, I)).
   14: -define(ne(E, I), ?assert(E =/= I)).
   15: 
   16: -define(B(C), (proplists:get_value(backend, C))).
   17: -define(MAX_USER_SESSIONS, 2).
   18: 
   19: 
   20: all() -> [{group, mnesia}, {group, redis}].
   21: 
   22: init_per_suite(C) ->
   23:     {ok, _} = application:ensure_all_started(jid),
   24:     application:ensure_all_started(exometer_core),
   25:     F = fun() ->
   26:         ejabberd_sm_backend_sup:start_link(),
   27:         receive stop -> ok end
   28:     end,
   29:     Pid = spawn(F),
   30:     [{pid, Pid} | C].
   31: 
   32: end_per_suite(C) ->
   33:     Pid = ?config(pid, C),
   34:     Pid ! stop,
   35:     application:stop(exometer),
   36:     application:stop(exometer_core).
   37: 
   38: groups() ->
   39:     [{mnesia, [], tests()},
   40:      {redis, [], tests()}].
   41: 
   42: tests() ->
   43:     [open_session,
   44:      get_full_session_list,
   45:      get_vh_session_list,
   46:      get_sessions_2,
   47:      get_sessions_3,
   48:      session_is_updated_when_created_twice,
   49:      delete_session,
   50:      clean_up,
   51:      too_much_sessions,
   52:      unique_count,
   53:      unique_count_while_removing_entries,
   54:      session_info_is_stored,
   55:      session_info_is_updated_if_keys_match,
   56:      session_info_is_extended_if_new_keys_present,
   57:      session_info_keys_not_truncated_if_session_opened_with_empty_infolist,
   58:      kv_can_be_stored_for_session,
   59:      kv_can_be_updated_for_session,
   60:      kv_can_be_removed_for_session,
   61:      store_info_sends_message_to_the_session_owner,
   62:      remove_info_sends_message_to_the_session_owner,
   63:      cannot_reproduce_race_condition_in_store_info
   64:     ].
   65: 
   66: init_per_group(mnesia, Config) ->
   67:     ok = mnesia:create_schema([node()]),
   68:     ok = mnesia:start(),
   69:     [{backend, ejabberd_sm_mnesia} | Config];
   70: init_per_group(redis, Config) ->
   71:     init_redis_group(is_redis_running(), Config).
   72: 
   73: init_redis_group(true, Config) ->
   74:     Self = self(),
   75:     proc_lib:spawn(fun() ->
   76:                   register(test_helper, self()),
   77:                   mongoose_wpool:ensure_started(),
   78:                   % This would be started via outgoing_pools in normal case
   79:                   Pool = {redis, global, default, [{strategy, random_worker}, {workers, 10}], []},
   80:                   mongoose_wpool:start_configured_pools([Pool], []),
   81:                   Self ! ready,
   82:                   receive stop -> ok end
   83:           end),
   84:     receive ready -> ok after timer:seconds(30) -> ct:fail(test_helper_not_ready) end,
   85:     [{backend, ejabberd_sm_redis} | Config];
   86: init_redis_group(_, _) ->
   87:     {skip, "redis not running"}.
   88: 
   89: end_per_group(mnesia, Config) ->
   90:     mnesia:stop(),
   91:     mnesia:delete_schema([node()]),
   92:     Config;
   93: end_per_group(_, Config) ->
   94:     whereis(test_helper) ! stop,
   95:     Config.
   96: 
   97: init_per_testcase(too_much_sessions, Config) ->
   98:     set_test_case_meck(?MAX_USER_SESSIONS),
   99:     setup_sm(Config),
  100:     Config;
  101: init_per_testcase(_, Config) ->
  102:     set_test_case_meck(infinity),
  103:     setup_sm(Config),
  104:     Config.
  105: 
  106: end_per_testcase(_, Config) ->
  107:     clean_sessions(Config),
  108:     terminate_sm(),
  109:     unload_meck(),
  110:     unset_opts(Config).
  111: 
  112: open_session(C) ->
  113:     {Sid, USR} = generate_random_user(<<"localhost">>),
  114:     given_session_opened(Sid, USR),
  115:     verify_session_opened(C, Sid, USR).
  116: 
  117: get_full_session_list(C) ->
  118:     ManyUsers = generate_many_random_users(5, [<<"localhost">>, <<"otherhost">>]),
  119:     ManyUsersLen = length(ManyUsers),
  120:     [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsers],
  121:     AllSessions = ejabberd_sm:get_full_session_list(),
  122:     AllSessionsLen = length(AllSessions),
  123:     AllSessionsLen = ManyUsersLen,
  124:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsers].
  125: 
  126: get_vh_session_list(C) ->
  127:     ManyUsersLocal = generate_many_random_users(5, [<<"localhost">>]),
  128:     ManyUsersOther = generate_many_random_users(5, [<<"otherhost">>]),
  129:     ManyUsersLocalLen = length(ManyUsersLocal),
  130:     [given_session_opened(Sid, USR) || {Sid, USR} <- ManyUsersLocal ++ ManyUsersOther],
  131:     LocalhostSessions = ejabberd_sm:get_vh_session_list(<<"localhost">>),
  132:     LocalhostSessionsLen = length(LocalhostSessions),
  133:     LocalhostSessionsLen = ManyUsersLocalLen,
  134:     ManyUsersLocalLen = ejabberd_sm:get_vh_session_number(<<"localhost">>),
  135:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- ManyUsersLocal].
  136: 
  137: get_sessions_2(C) ->
  138:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  139:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  140:     USDict = get_unique_us_dict(UsersWithManyResources),
  141:     [verify_session_opened(C, U, S, dict:fetch({U, S}, USDict)) || {U, S} <- dict:fetch_keys(USDict)],
  142:     [verify_session_opened(C, Sid, USR) || {Sid, USR} <- UsersWithManyResources].
  143: 
  144: 
  145: get_sessions_3(C) ->
  146:     UserRes = generate_many_random_res(1, 3, [<<"localhost">>]),
  147:     AllSessions = length(UserRes),
  148:     {_, {User, Server, _}} = hd(UserRes),
  149:     [given_session_opened(Sid, USR) || {Sid, USR} <- UserRes],
  150:     Sessions_2 = ?B(C):get_sessions(User, Server),
  151:     AllSessions = length(Sessions_2),
  152:     F = fun({Sid, {U, S, R} = USR}) ->
  153:         [#session{sid = Sid} = Session] = ?B(C):get_sessions(U, S, R),
  154:         Session = lists:keyfind(Sid, #session.sid, Sessions_2),
  155:         Session = lists:keyfind(USR, #session.usr, Sessions_2),
  156:         true
  157:     end,
  158:     true = lists:all(F, UserRes).
  159: 
  160: session_is_updated_when_created_twice(C) ->
  161:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  162:     given_session_opened(Sid, USR),
  163:     verify_session_opened(C, Sid, USR),
  164: 
  165:     given_session_opened(Sid, USR, 20),
  166:     verify_session_opened(C, Sid, USR),
  167: 
  168:     [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(),
  169:     [#session{usr = USR, sid = Sid, priority = 20}] = ?B(C):get_sessions(S),
  170:     [#session{priority = 20}] = ?B(C):get_sessions(U, S).
  171: 
  172: session_info_is_stored(C) ->
  173:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  174:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  175: 
  176:     [#session{sid = Sid, info = #{key1 := val1}}]
  177:      = ?B(C):get_sessions(U,S).
  178: 
  179: session_info_is_updated_if_keys_match(C) ->
  180:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  181:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  182: 
  183:     when_session_opened(Sid, USR, 1, [{key1, val2}]),
  184: 
  185:     [#session{sid = Sid, info = #{key1 := val2}}]
  186:      = ?B(C):get_sessions(U,S).
  187: 
  188: session_info_is_extended_if_new_keys_present(C) ->
  189:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  190:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  191: 
  192:     when_session_opened(Sid, USR, 1, [{key1, val1}, {key2, val2}]),
  193: 
  194:     [#session{sid = Sid, info = #{key1 := val1, key2 := val2}}]
  195:      = ?B(C):get_sessions(U,S).
  196: 
  197: session_info_keys_not_truncated_if_session_opened_with_empty_infolist(C) ->
  198:     {Sid, {U, S, _} = USR} = generate_random_user(<<"localhost">>),
  199:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  200: 
  201:     when_session_opened(Sid, USR, 1, []),
  202: 
  203:     [#session{sid = Sid, info = #{key1 := val1}}]
  204:      = ?B(C):get_sessions(U,S).
  205: 
  206: 
  207: kv_can_be_stored_for_session(C) ->
  208:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  209:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  210: 
  211:     when_session_info_stored(U, S, R, {key2, newval}),
  212: 
  213:     ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := newval}}],
  214:                  ?B(C):get_sessions(U,S)).
  215: 
  216: kv_can_be_updated_for_session(C) ->
  217:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  218:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  219: 
  220:     when_session_info_stored(U, S, R, {key2, newval}),
  221:     when_session_info_stored(U, S, R, {key2, override}),
  222: 
  223:     ?assertMatch([#session{sid = Sid, info = #{key1 := val1, key2 := override}}],
  224:                  ?B(C):get_sessions(U, S)).
  225: 
  226: kv_can_be_removed_for_session(C) ->
  227:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  228:     given_session_opened(Sid, USR, 1, [{key1, val1}]),
  229: 
  230:     when_session_info_stored(U, S, R, {key2, newval}),
  231: 
  232:     [#session{sid = Sid, info = #{key1 := val1, key2 := newval}}]
  233:      = ?B(C):get_sessions(U, S),
  234: 
  235:     when_session_info_removed(U, S, R, key2),
  236: 
  237:     [#session{sid = Sid, info = #{key1 := val1}}]
  238:      = ?B(C):get_sessions(U, S),
  239: 
  240:     when_session_info_removed(U, S, R, key1),
  241: 
  242:     [#session{sid = Sid, info = #{}}]
  243:      = ?B(C):get_sessions(U, S).
  244: 
  245: cannot_reproduce_race_condition_in_store_info(C) ->
  246:     ok = try_to_reproduce_race_condition(C).
  247: 
  248: store_info_sends_message_to_the_session_owner(C) ->
  249:     SID = {erlang:system_time(microsecond), self()},
  250:     U = <<"alice2">>,
  251:     S = <<"localhost">>,
  252:     R = <<"res1">>,
  253:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  254:     %% Create session in one process
  255:     ?B(C):create_session(U, S, R, Session),
  256:     %% but call store_info from another process
  257:     JID = jid:make_noprep(U, S, R),
  258:     spawn_link(fun() -> ejabberd_sm:store_info(JID, cc, undefined) end),
  259:     %% The original process receives a message
  260:     receive {store_session_info,
  261:              #jid{luser = User, lserver = Server, lresource = Resource},
  262:              K, V, _FromPid} ->
  263:         ?eq(U, User),
  264:         ?eq(S, Server),
  265:         ?eq(R, Resource),
  266:         ?eq({cc, undefined}, {K, V}),
  267:         ok
  268:         after 5000 ->
  269:             ct:fail("store_info_sends_message_to_the_session_owner=timeout")
  270:     end.
  271: 
  272: remove_info_sends_message_to_the_session_owner(C) ->
  273:     SID = {erlang:timestamp(), self()},
  274:     U = <<"alice2">>,
  275:     S = <<"localhost">>,
  276:     R = <<"res1">>,
  277:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  278:     %% Create session in one process
  279:     ?B(C):create_session(U, S, R, Session),
  280:     %% but call remove_info from another process
  281:     JID = jid:make_noprep(U, S, R),
  282:     spawn_link(fun() -> ejabberd_sm:remove_info(JID, cc) end),
  283:     %% The original process receives a message
  284:     receive {remove_session_info,
  285:              #jid{luser = User, lserver = Server, lresource = Resource},
  286:              Key, _FromPid} ->
  287:         ?eq(U, User),
  288:         ?eq(S, Server),
  289:         ?eq(R, Resource),
  290:         ?eq(cc, Key),
  291:         ok
  292:         after 5000 ->
  293:             ct:fail("remove_info_sends_message_to_the_session_owner=timeout")
  294:     end.
  295: 
  296: delete_session(C) ->
  297:     {Sid, {U, S, R} = USR} = generate_random_user(<<"localhost">>),
  298:     given_session_opened(Sid, USR),
  299:     verify_session_opened(C, Sid, USR),
  300: 
  301:     ?B(C):delete_session(Sid, U, S, R),
  302: 
  303:     [] = ?B(C):get_sessions(),
  304:     [] = ?B(C):get_sessions(S),
  305:     [] = ?B(C):get_sessions(U, S),
  306:     [] = ?B(C):get_sessions(U, S, R).
  307: 
  308: 
  309: 
  310: clean_up(C) ->
  311:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  312:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  313:     ?B(C):cleanup(node()),
  314:     %% give sm backend some time to clean all sessions
  315:     ensure_empty(C, 10, ?B(C):get_sessions()).
  316: 
  317: ensure_empty(_C, 0, Sessions) ->
  318:     [] = Sessions;
  319: ensure_empty(C, N, Sessions) ->
  320:     case Sessions of
  321:         [] ->
  322:             ok;
  323:         _ ->
  324:             timer:sleep(50),
  325:             ensure_empty(C, N-1, ?B(C):get_sessions())
  326:     end.
  327: 
  328: too_much_sessions(_C) ->
  329:     %% Max sessions set to ?MAX_USER_SESSIONS in init_per_testcase
  330:     UserSessions = [generate_random_user(<<"a">>, <<"localhost">>) || _ <- lists:seq(1, ?MAX_USER_SESSIONS)],
  331:     {AddSid, AddUSR} = generate_random_user(<<"a">>, <<"localhost">>),
  332: 
  333:     [given_session_opened(Sid, USR) || {Sid, USR} <- UserSessions],
  334: 
  335:     given_session_opened(AddSid, AddUSR),
  336: 
  337:     receive
  338:         replaced ->
  339:             ok
  340:     after 10 ->
  341:         ct:fail("replaced message not sent")
  342:     end.
  343: 
  344: 
  345: 
  346: unique_count(_C) ->
  347:     UsersWithManyResources = generate_many_random_res(5, 3, [<<"localhost">>, <<"otherhost">>]),
  348:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  349:     USDict = get_unique_us_dict(UsersWithManyResources),
  350:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  351:     UniqueCount = dict:size(USDict).
  352: 
  353: 
  354: unique_count_while_removing_entries(C) ->
  355:     unique_count(C),
  356:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  357:     %% Register more sessions and mock the crash
  358:     UsersWithManyResources = generate_many_random_res(10, 3, [<<"localhost">>, <<"otherhost">>]),
  359:     [given_session_opened(Sid, USR) || {Sid, USR} <- UsersWithManyResources],
  360:     set_test_case_meck_unique_count_crash(?B(C)),
  361:     USDict = get_unique_us_dict(UsersWithManyResources),
  362:     %% Check if unique count equals prev cached value
  363:     UniqueCount = ejabberd_sm:get_unique_sessions_number(),
  364:     meck:unload(?B(C)),
  365:     true = UniqueCount /= dict:size(USDict) + UniqueCount.
  366: 
  367: unload_meck() ->
  368:     meck:unload(acl),
  369:     meck:unload(gen_hook),
  370:     meck:unload(ejabberd_commands),
  371:     meck:unload(mongoose_domain_api).
  372: 
  373: set_test_case_meck(MaxUserSessions) ->
  374:     meck:new(acl, []),
  375:     meck:expect(acl, match_rule_for_host_type, fun(_, _, _, _) -> MaxUserSessions end),
  376:     meck:new(gen_hook, []),
  377:     meck:expect(gen_hook, run_fold, fun(_, _, Acc, _) -> {ok, Acc} end),
  378:     meck:new(mongoose_domain_api, []),
  379:     meck:expect(mongoose_domain_api, get_domain_host_type, fun(H) -> {ok, H} end).
  380: 
  381: set_test_case_meck_unique_count_crash(Backend) ->
  382:     F = get_fun_for_unique_count(Backend),
  383:     meck:new(Backend, []),
  384:     meck:expect(Backend, unique_count, F).
  385: 
  386: get_fun_for_unique_count(ejabberd_sm_mnesia) ->
  387:     fun() ->
  388:         mnesia:abort({badarg,[session,{{1442,941593,580189},list_to_pid("<0.23291.6>")}]})
  389:     end;
  390: get_fun_for_unique_count(ejabberd_sm_redis) ->
  391:     fun() ->
  392:         %% The code below is on purpose, it's to crash with badarg reason
  393:         length({error, timeout})
  394:     end.
  395: 
  396: make_sid() ->
  397:     {erlang:timestamp(), self()}.
  398: 
  399: given_session_opened(Sid, USR) ->
  400:     given_session_opened(Sid, USR, 1).
  401: 
  402: given_session_opened(Sid, {U, S, R}, Priority) ->
  403:     given_session_opened(Sid, {U, S, R}, Priority, []).
  404: 
  405: given_session_opened(Sid, {U, S, R}, Priority, Info) ->
  406:     JID = jid:make_noprep(U, S, R),
  407:     ejabberd_sm:open_session(S, Sid, JID, Priority, maps:from_list(Info)).
  408: 
  409: when_session_opened(Sid, {U, S, R}, Priority, Info) ->
  410:     given_session_opened(Sid, {U, S, R}, Priority, Info).
  411: 
  412: when_session_info_stored(U, S, R, {K, V}) ->
  413:     JID = jid:make_noprep(U, S, R),
  414:     ejabberd_sm:store_info(JID, K, V).
  415: 
  416: when_session_info_removed(U, S, R, Key) ->
  417:     JID = jid:make_noprep(U, S, R),
  418:     ejabberd_sm:remove_info(JID, Key).
  419: 
  420: verify_session_opened(C, Sid, USR) ->
  421:     do_verify_session_opened(?B(C), Sid, USR).
  422: 
  423: do_verify_session_opened(ejabberd_sm_mnesia, Sid, {U, S, R} = USR) ->
  424:     general_session_check(ejabberd_sm_mnesia, Sid, USR, U, S, R);
  425: do_verify_session_opened(ejabberd_sm_redis, Sid, {U, S, R} = USR) ->
  426:     UHash = iolist_to_binary(hash(U, S, R, Sid)),
  427:     Hashes = mongoose_redis:cmd(["SMEMBERS", n(node())]),
  428:     true = lists:member(UHash, Hashes),
  429:     SessionsUSEncoded = mongoose_redis:cmd(["SMEMBERS", hash(U, S)]),
  430:     SessionsUS = [binary_to_term(Entry) || Entry <- SessionsUSEncoded],
  431:     true = lists:keymember(Sid, 2, SessionsUS),
  432:     [SessionUSREncoded] = mongoose_redis:cmd(["SMEMBERS", hash(U, S, R)]),
  433:     SessionUSR = binary_to_term(SessionUSREncoded),
  434:     #session{sid = Sid} = SessionUSR,
  435:     general_session_check(ejabberd_sm_redis, Sid, USR, U, S, R).
  436: 
  437: verify_session_opened(C, U, S, Resources) ->
  438:     Sessions = ?B(C):get_sessions(U, S),
  439:     F = fun({Sid, USR}) ->
  440:         #session{} = Session = lists:keyfind(Sid, #session.sid, Sessions),
  441:         Session == lists:keyfind(USR, #session.usr, Sessions)
  442:     end,
  443:     true = lists:all(F, Resources).
  444: 
  445: general_session_check(M, Sid, USR, U, S, R) ->
  446:     [#session{sid = Sid, usr = USR, us = {U, S}}] = M:get_sessions(U, S, R).
  447: 
  448: clean_sessions(C) ->
  449:     case ?B(C) of
  450:         ejabberd_sm_mnesia ->
  451:             mnesia:clear_table(session);
  452:         ejabberd_sm_redis ->
  453:             mongoose_redis:cmd(["FLUSHALL"])
  454:     end.
  455: 
  456: generate_random_user(S) ->
  457:     U = base16:encode(crypto:strong_rand_bytes(5)),
  458:     generate_random_user(U, S).
  459: 
  460: generate_random_user(U, S) ->
  461:     R = base16:encode(crypto:strong_rand_bytes(5)),
  462:     generate_user(U, S, R).
  463: 
  464: generate_user(U, S, R) ->
  465:     Sid = make_sid(),
  466:     {Sid, {U, S, R}}.
  467: 
  468: generate_many_random_users(PerServerCount, Servers) ->
  469:     Users = [generate_random_users(PerServerCount, Server) || Server <- Servers],
  470:     lists:flatten(Users).
  471: 
  472: generate_random_users(Count, Server) ->
  473:     [generate_random_user(Server) || _ <- lists:seq(1, Count)].
  474: 
  475: generate_many_random_res(UsersPerServer, ResourcesPerUser, Servers) ->
  476:     Usernames = [base16:encode(crypto:strong_rand_bytes(5)) || _ <- lists:seq(1, UsersPerServer)],
  477:     [generate_random_user(U, S) || U <- Usernames, S <- Servers, _ <- lists:seq(1, ResourcesPerUser)].
  478: 
  479: get_unique_us_dict(USRs) ->
  480:     F = fun({_, {U, S, _}} = I, SetAcc) ->
  481:         dict:append({U, S}, I, SetAcc)
  482:     end,
  483:     lists:foldl(F, dict:new(), USRs).
  484: 
  485: %% Taken from ejabberd_sm_redis
  486: 
  487: -spec hash(binary()) -> iolist().
  488: hash(Val1) ->
  489:     ["s3:*:", Val1, ":*"].
  490: 
  491: 
  492: -spec hash(binary(), binary()) -> iolist().
  493: hash(Val1, Val2) ->
  494:     ["s2:", Val1, ":", Val2].
  495: 
  496: 
  497: -spec hash(binary(), binary(), binary()) -> iolist().
  498: hash(Val1, Val2, Val3) ->
  499:     ["s3:", Val1, ":", Val2, ":", Val3].
  500: 
  501: 
  502: -spec hash(binary(), binary(), binary(), binary()) -> iolist().
  503: hash(Val1, Val2, Val3, Val4) ->
  504:     ["s4:", Val1, ":", Val2, ":", Val3, ":", term_to_binary(Val4)].
  505: 
  506: 
  507: -spec n(atom()) -> iolist().
  508: n(Node) ->
  509:     ["n:", atom_to_list(Node)].
  510: 
  511: 
  512: is_redis_running() ->
  513:     case eredis:start_link() of
  514:         {ok, Client} ->
  515:             Result = eredis:q(Client, [<<"PING">>], 5000),
  516:             eredis:stop(Client),
  517:             case Result of
  518:                 {ok,<<"PONG">>} ->
  519:                     true;
  520:                 _ ->
  521:                     false
  522:             end;
  523:         _ ->
  524:             false
  525:     end.
  526: 
  527: try_to_reproduce_race_condition(Config) ->
  528:     SID = {erlang:timestamp(), self()},
  529:     U = <<"alice">>,
  530:     S = <<"localhost">>,
  531:     R = <<"res1">>,
  532:     Session = #session{sid = SID, usr = {U, S, R}, us = {U, S}, priority = 1, info = #{}},
  533:     ?B(Config):create_session(U, S, R, Session),
  534:     Parent = self(),
  535:     %% Add some instrumentation to simulate race conditions
  536:     %% The goal is to delete the session after other process reads it
  537:     %% but before it updates it. In other words, delete a record
  538:     %% between get_sessions and create_session in ejabberd_sm:store_info
  539:     %% Step1 prepare concurrent processes
  540:     DeleterPid = spawn_link(fun() ->
  541:                                     receive start -> ok end,
  542:                                     ?B(Config):delete_session(SID, U, S, R),
  543:                                     Parent ! p1_done
  544:                             end),
  545:     SetterPid = spawn_link(fun() ->
  546:                                    receive start -> ok end,
  547:                                    when_session_info_stored(U, S, R, {cc, undefined}),
  548:                                    Parent ! p2_done
  549:                            end),
  550:     %% Step2 setup mocking for some ejabbers_sm_mnesia functions
  551:     meck:new(?B(Config), []),
  552:     %% When the first get_sessions (run from ejabberd_sm:store_info)
  553:     %% is executed, the start msg is sent to Deleter process
  554:     %% Thanks to that, setter will get not empty list of sessions
  555:     PassThrough3 = fun(A, B, C) ->
  556:                            DeleterPid ! start,
  557:                            meck:passthrough([A, B, C]) end,
  558:     meck:expect(?B(Config), get_sessions, PassThrough3),
  559:     %% Wait some time before setting the sessions
  560:     %% so we are sure delete operation finishes
  561:     meck:expect(?B(Config), create_session,
  562:                 fun(U1, S1, R1, Session1) ->
  563:                         timer:sleep(100),
  564:                         meck:passthrough([U1, S1, R1, Session1])
  565:                 end),
  566:     PassThrough4 = fun(A, B, C, D) -> meck:passthrough([A, B, C, D]) end,
  567:     meck:expect(?B(Config), delete_session, PassThrough4),
  568:     %% Start the play from setter process
  569:     SetterPid ! start,
  570:     %% Wait for both process to finish
  571:     receive p1_done -> ok end,
  572:     receive p2_done -> ok end,
  573:     meck:unload(?B(Config)),
  574:     %% Session should not exist
  575:     case ?B(Config):get_sessions(U, S, R) of
  576:         [] ->
  577:             ok;
  578:         Other ->
  579:             error_logger:error_msg("issue=reproduced, sid=~p, other=~1000p",
  580:                                    [SID, Other]),
  581:             {error, reproduced}
  582:     end.
  583: 
  584: setup_sm(Config) ->
  585:     set_opts(Config),
  586:     set_meck(),
  587:     ejabberd_sm:start_link(),
  588:     case ?config(backend, Config) of
  589:         ejabberd_sm_redis ->
  590:             mongoose_redis:cmd(["FLUSHALL"]);
  591:         ejabberd_sm_mnesia ->
  592:             ok
  593:     end.
  594: 
  595: terminate_sm() ->
  596:     gen_server:stop(ejabberd_sm).
  597: 
  598: set_opts(Config) ->
  599:     [mongoose_config:set_opt(Key, Value) || {Key, Value} <- opts(Config)].
  600: 
  601: unset_opts(Config) ->
  602:     [mongoose_config:unset_opt(Key) || {Key, _Value} <- opts(Config)].
  603: 
  604: opts(Config) ->
  605:     [{hosts, [<<"localhost">>]},
  606:      {host_types, []},
  607:      {all_metrics_are_global, false},
  608:      {sm_backend, sm_backend(?config(backend, Config))}].
  609: 
  610: sm_backend(ejabberd_sm_redis) ->
  611:     {redis, [{pool_size, 3}, {worker_config, [{host, "localhost"}, {port, 6379}]}]};
  612: sm_backend(ejabberd_sm_mnesia) ->
  613:     {mnesia, []}.
  614: 
  615: set_meck() ->
  616:     meck:expect(gen_hook, add_handler, fun(_, _, _, _, _) -> ok end),
  617:     meck:new(ejabberd_commands, []),
  618:     meck:expect(ejabberd_commands, register_commands, fun(_) -> ok end),
  619:     meck:expect(ejabberd_commands, unregister_commands, fun(_) -> ok end),
  620:     ok.