1: -module(mongoose_rdbms_SUITE). 2: -include_lib("eunit/include/eunit.hrl"). 3: -include_lib("common_test/include/ct.hrl"). 4: -include("mongoose.hrl"). 5: 6: -compile([export_all, nowarn_export_all]). 7: 8: -define(_eq(E, I), ?_assertEqual(E, I)). 9: -define(eq(E, I), ?assertEqual(E, I)). 10: -define(ne(E, I), ?assert(E =/= I)). 11: 12: -define(KEEPALIVE_INTERVAL, 1). 13: -define(KEEPALIVE_QUERY, <<"SELECT 1;">>). 14: -define(MAX_INTERVAL, 30). 15: 16: all() -> 17: [{group, odbc}, 18: {group, mysql}, 19: {group, pgsql}]. 20: 21: init_per_suite(Config) -> 22: application:ensure_all_started(exometer_core), 23: Config. 24: 25: end_per_suite(_Config) -> 26: meck:unload(), 27: application:stop(exometer_core). 28: 29: groups() -> 30: [{odbc, [], tests()}, 31: {mysql, [], tests()}, 32: {pgsql, [], tests()}]. 33: 34: tests() -> 35: [keepalive_interval, 36: does_backoff_increase_to_a_point, 37: keepalive_exit]. 38: 39: init_per_group(odbc, Config) -> 40: case code:ensure_loaded(eodbc) of 41: {module, eodbc} -> 42: mongoose_backend:init(global, mongoose_rdbms, [], [{backend, odbc}]), 43: [{db_type, odbc} | Config]; 44: _ -> 45: {skip, no_odbc_application} 46: end; 47: init_per_group(Group, Config) -> 48: mongoose_backend:init(global, mongoose_rdbms, [], [{backend, Group}]), 49: [{db_type, Group} | Config]. 50: 51: end_per_group(_, Config) -> 52: % clean up after mongoose_backend:init 53: persistent_term:erase({backend_module, global, mongoose_rdbms}), 54: Config. 55: 56: init_per_testcase(does_backoff_increase_to_a_point, Config) -> 57: DbType = ?config(db_type, Config), 58: set_opts(), 59: meck_db(DbType), 60: meck_connection_error(DbType), 61: meck_rand(), 62: [{db_opts, [{server, server(DbType)}, {keepalive_interval, 2}, {start_interval, 10}]} | Config]; 63: init_per_testcase(_, Config) -> 64: DbType = ?config(db_type, Config), 65: set_opts(), 66: meck_db(DbType), 67: [{db_opts, [{server, server(DbType)}, {keepalive_interval, ?KEEPALIVE_INTERVAL}, 68: {start_interval, ?MAX_INTERVAL}]} | Config]. 69: 70: end_per_testcase(does_backoff_increase_to_a_point, Config) -> 71: meck_unload_rand(), 72: Db = ?config(db_type, Config), 73: meck_config_and_db_unload(Db), 74: unset_opts(), 75: Config; 76: end_per_testcase(_, Config) -> 77: meck_config_and_db_unload(?config(db_type, Config)), 78: unset_opts(), 79: Config. 80: 81: %% Test cases 82: keepalive_interval(Config) -> 83: {ok, Pid} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 84: timer:sleep(5500), 85: ?eq(5, query_calls(Config)), 86: true = erlang:exit(Pid, kill), 87: ok. 88: 89: keepalive_exit(Config) -> 90: {ok, Pid} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 91: Monitor = erlang:monitor(process, Pid), 92: timer:sleep(3500), 93: ?eq(3, query_calls(Config)), 94: meck_error(?config(db_type, Config)), 95: receive 96: {'DOWN', Monitor, process, Pid, {keepalive_failed, _}} -> 97: ok 98: after 1500 -> 99: ct:fail(no_down_message) 100: end. 101: 102: %% 5 retries. Max retry 10. Initial retry 2. 103: %% We should get a sequence: 2 -> 4 -> 10 -> 10 -> 10. 104: does_backoff_increase_to_a_point(Config) -> 105: {error, _} = gen_server:start(mongoose_rdbms, ?config(db_opts, Config), []), 106: % We expect to have 2 at the beginning, then values up to 10 and 10 three times in total 107: receive_backoffs(2, 10, 3). 108: 109: receive_backoffs(InitialValue, MaxValue, MaxCount) -> 110: receive_backoffs(InitialValue, MaxValue, MaxCount, 0). 111: 112: receive_backoffs(ExpectedVal, MaxValue, MaxCountExpected, MaxCount) -> 113: receive 114: {backoff, MaxValue} when MaxCount =:= MaxCountExpected - 1 -> 115: ok; 116: {backoff, MaxValue} -> 117: receive_backoffs(MaxValue, MaxValue, MaxCountExpected, MaxCount + 1); 118: {backoff, ExpectedVal} -> 119: receive_backoffs(min(ExpectedVal * ExpectedVal, MaxValue), MaxValue, MaxCountExpected, MaxCount) 120: after 200 -> % Lower this 121: ct:fail(no_backoff) 122: end. 123: 124: %% Mocks 125: 126: meck_rand() -> 127: meck:new(rand, [unstick, no_link]), 128: Self = self(), 129: Fun = fun(Val) -> ct:log("sending backoff: ~p to pid: ~p~n", [Val, Self]), Self ! {backoff, Val}, 0 end, 130: meck:expect(rand, uniform, Fun). 131: 132: meck_unload_rand() -> 133: meck:unload(rand). 134: 135: set_opts() -> 136: [mongoose_config:set_opt(Key, Value) || {Key, Value} <- opts()]. 137: 138: unset_opts() -> 139: [mongoose_config:unset_opt(Key) || {Key, _Value} <- opts()]. 140: 141: opts() -> 142: [{all_metrics_are_global, false}, 143: {max_fsm_queue, 1024}]. 144: 145: meck_db(odbc) -> 146: meck:new(eodbc, [no_link]), 147: meck:expect(mongoose_rdbms_odbc, disconnect, fun(_) -> ok end), 148: meck:expect(eodbc, connect, fun(_, _) -> {ok, self()} end), 149: meck:expect(eodbc, sql_query, fun(_Ref, _Query, _Timeout) -> {selected, ["row"]} end); 150: meck_db(mysql) -> 151: meck:new(mysql, [no_link]), 152: meck:expect(mongoose_rdbms_mysql, disconnect, fun(_) -> ok end), 153: meck:expect(mysql, start_link, fun(_) -> {ok, self()} end), 154: meck:expect(mysql, query, fun(_Ref, _Query) -> {ok, [], []} end); 155: meck_db(pgsql) -> 156: meck:new(epgsql, [no_link]), 157: meck:expect(mongoose_rdbms_pgsql, disconnect, fun(_) -> ok end), 158: meck:expect(epgsql, connect, fun(_) -> {ok, self()} end), 159: meck:expect(epgsql, squery, 160: fun(_Ref, <<"SET", _/binary>>) -> {ok, 0}; 161: (_Ref, [<<"SET", _/binary>> | _]) -> {ok, 0}; 162: (_Ref, [<<"SELECT", _/binary>>]) -> {ok, [], [{1}]} end). 163: 164: meck_connection_error(pgsql) -> 165: meck:expect(epgsql, connect, fun(_) -> connection_error end); 166: meck_connection_error(odbc) -> 167: meck:expect(eodbc, connect, fun(_, _) -> connection_error end); 168: meck_connection_error(mysql) -> 169: meck:expect(mongoose_rdbms_mysql, connect, fun(_, _) -> {error, connection_error} end). 170: 171: 172: meck_error(odbc) -> 173: meck:expect(eodbc, sql_query, 174: fun(_Ref, _Query, _Timeout) -> 175: {error, "connection broken"} 176: end); 177: meck_error(mysql) -> 178: meck:expect(mysql, query, fun(_Ref, _Query) -> {error, {123, "", <<"connection broken">>}} end); 179: meck_error(pgsql) -> 180: meck:expect(epgsql, connect, fun(_) -> {ok, self()} end), 181: meck:expect(epgsql, squery, 182: fun(_Ref, _Query) -> {error, {error, 2, 3, 4, <<"connection broken">>, 5}} end). 183: 184: meck_config_and_db_unload(DbType) -> 185: do_meck_unload(DbType). 186: 187: do_meck_unload(odbc) -> 188: meck:unload(eodbc); 189: do_meck_unload(mysql) -> 190: meck:unload(mongoose_rdbms_mysql), 191: meck:unload(mysql); 192: do_meck_unload(pgsql) -> 193: meck:unload(epgsql). 194: 195: query_calls(Config) -> 196: DbType = ?config(db_type, Config), 197: {M, F} = mf(DbType), 198: meck:num_calls(M, F, a(DbType)). 199: 200: mf(odbc) -> 201: {eodbc, sql_query}; 202: mf(mysql) -> 203: {mysql, query}; 204: mf(pgsql) -> 205: {epgsql, squery}. 206: 207: a(odbc) -> 208: ['_', [?KEEPALIVE_QUERY], '_']; 209: a(mysql) -> 210: ['_', [?KEEPALIVE_QUERY]]; 211: a(pgsql) -> 212: ['_', [?KEEPALIVE_QUERY]]. 213: 214: server(odbc) -> 215: "fake-connection-string"; 216: server(mysql) -> 217: {mysql, "fake-host", "fake-db", "fake-user", "fake-pass"}; 218: server(pgsql) -> 219: {pgsql, "fake-host", "fake-db", "fake-user", "fake-pass"}.