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