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, 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.