diff options
-rw-r--r-- | src/mysqlerl_connection.erl | 56 | ||||
-rw-r--r-- | test/mysqlerl_SUITE.erl | 19 | ||||
-rw-r--r-- | test/mysqlerl_connect_SUITE.erl | 242 | ||||
-rw-r--r-- | test/mysqlerl_test_lib.erl | 25 |
4 files changed, 305 insertions, 37 deletions
diff --git a/src/mysqlerl_connection.erl b/src/mysqlerl_connection.erl index bfff97d..18b2903 100644 --- a/src/mysqlerl_connection.erl +++ b/src/mysqlerl_connection.erl @@ -19,7 +19,7 @@ start_link(Owner, Host, Port, Database, User, Password, Options) -> User, Password, Options], []). stop(Pid) -> - case (catch gen_server:call(Pid, stop)) of + case catch gen_server:call(Pid, stop) of {'EXIT', _} -> ok; Other -> Other end. @@ -27,20 +27,26 @@ stop(Pid) -> init([Owner, Host, Port, Database, User, Password, Options]) -> process_flag(trap_exit, true), erlang:monitor(process, Owner), - Ref = open_port({spawn, helper()}, [{packet, 4}, binary]), - ConnectArgs = #sql_connect{host = Host, - port = Port, - database = Database, - user = User, - password = Password, - options = Options}, - case send_port_cmd(Ref, ConnectArgs, ?CONNECT_TIMEOUT) of - {data, ok} -> - {ok, #state{port = Ref, owner = Owner}}; - {data, {error, Error}} -> - {stop, Error}; - {'EXIT', Ref, Reason} -> - {stop, {port_closed, Reason}} + case os:find_executable("mysqlerl", helper_dir()) of + false -> + {stop, port_program_executable_not_found}; + Helper -> + Ref = open_port({spawn, Helper}, + [{packet, 4}, binary, exit_status]), + ConnectArgs = #sql_connect{host = Host, + port = Port, + database = Database, + user = User, + password = Password, + options = Options}, + case catch send_port_cmd(Ref, ConnectArgs, ?CONNECT_TIMEOUT) of + {data, ok} -> + {ok, #state{port = Ref, owner = Owner}}; + {data, {error, Error}} -> + {stop, Error}; + {'EXIT', Ref, Reason} -> + {stop, {port_closed, Reason}} + end end. terminate(Reason, _State) -> @@ -58,7 +64,7 @@ handle_call(Request, From, #state{owner = Owner} = State) handle_call(stop, _From, State) -> {stop, normal, State}; handle_call({Req, Timeout}, From, State) -> - case send_port_cmd(State#state.port, Req, Timeout) of + case catch send_port_cmd(State#state.port, Req, Timeout) of {data, Res} -> {reply, Res, State}; {'EXIT', _Ref, Reason} -> @@ -76,17 +82,23 @@ handle_call({Req, Timeout}, From, State) -> handle_cast(_Request, State) -> {noreply, State}. -handle_info({'DOWN', _Monitor, process, _Reason, _PID, Reason}, - #state{owner = PID} = State) -> - io:format("DEBUG: owner ~p shut down: ~p.~n", [PID, Reason]), - {stop, normal, State}. +handle_info({'EXIT', Ref, Reason}, #state{port = Ref} = State) -> + {stop, Reason, State}; +handle_info({'DOWN', _Ref, _Type, _PID, normal}, State) -> + {stop, normal, State}; +handle_info({'DOWN', _Ref, _Type, _PID, timeout}, State) -> + {stop, normal, State}; +handle_info({'DOWN', _Ref, _Type, _PID, shutdown}, State) -> + {stop, normal, State}; +handle_info({'DOWN', _Ref, _Type, PID, Reason}, State) -> + {stop, {stopped, {'EXIT', PID, Reason}}, State}. -helper() -> +helper_dir() -> case code:priv_dir(mysqlerl) of PrivDir when is_list(PrivDir) -> ok; {error, bad_name} -> PrivDir = filename:join(["..", "priv"]) end, - filename:nativename(filename:join([PrivDir, "bin", "mysqlerl"])). + filename:nativename(filename:join([PrivDir, "bin"])). send_port_cmd(Ref, Request, Timeout) -> io:format("DEBUG: Sending request: ~p~n", [Request]), diff --git a/test/mysqlerl_SUITE.erl b/test/mysqlerl_SUITE.erl index 0a1c3f3..d8ffc2f 100644 --- a/test/mysqlerl_SUITE.erl +++ b/test/mysqlerl_SUITE.erl @@ -32,14 +32,8 @@ suite() -> %% @end %%-------------------------------------------------------------------- init_per_suite(Config) -> - DBInfo = ct:get_config(db_info), - DataDir = ?config(data_dir, Config), - User = ?config(username, DBInfo), - Pass = ?config(password, DBInfo), - Name = ?config(name, DBInfo), - - mysqlerl_test_lib:create_db(User, Pass, Name), - mysqlerl_test_lib:create_table(User, Pass, Name, DataDir), + mysqlerl_test_lib:create_db(Config), + mysqlerl_test_lib:create_table(Config), ok = application:start(mysqlerl), Config. @@ -48,14 +42,9 @@ init_per_suite(Config) -> %% Config0 = Config1 = [tuple()] %% @end %%-------------------------------------------------------------------- -end_per_suite(_Config) -> - DBInfo = ct:get_config(db_info), - User = ?config(username, DBInfo), - Pass = ?config(password, DBInfo), - Name = ?config(name, DBInfo), - +end_per_suite(Config) -> ok = application:stop(mysqlerl), - mysqlerl_test_lib:drop_db(User, Pass, Name). + mysqlerl_test_lib:drop_db(Config). %%-------------------------------------------------------------------- %% @spec init_per_group(GroupName, Config0) -> diff --git a/test/mysqlerl_connect_SUITE.erl b/test/mysqlerl_connect_SUITE.erl new file mode 100644 index 0000000..5f18648 --- /dev/null +++ b/test/mysqlerl_connect_SUITE.erl @@ -0,0 +1,242 @@ +%%%------------------------------------------------------------------- +%%% @author Brian Cully <bjc@kublai.com> +%%% @copyright (C) 2012, Brian Cully +%%% @doc +%%% +%%% @end +%%% Created : 9 Feb 2012 by Brian Cully <bjc@kublai.com> +%%%------------------------------------------------------------------- +-module(mysqlerl_connect_SUITE). + +-compile(export_all). + +-include_lib("common_test/include/ct.hrl"). + +%%-------------------------------------------------------------------- +%% @spec suite() -> Info +%% Info = [tuple()] +%% @end +%%-------------------------------------------------------------------- +suite() -> + [{timetrap,{seconds,30}}]. + +%%-------------------------------------------------------------------- +%% @spec init_per_suite(Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_suite(Config) -> + mysqlerl_test_lib:create_db(Config), + ok = application:start(mysqlerl), + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_suite(Config0) -> void() | {save_config,Config1} +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_suite(Config) -> + ok = application:stop(mysqlerl), + mysqlerl_test_lib:drop_db(Config). + +%%-------------------------------------------------------------------- +%% @spec init_per_group(GroupName, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_group(_GroupName, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_group(GroupName, Config0) -> +%% void() | {save_config,Config1} +%% GroupName = atom() +%% Config0 = Config1 = [tuple()] +%% @end +%%-------------------------------------------------------------------- +end_per_group(_GroupName, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec init_per_testcase(TestCase, Config0) -> +%% Config1 | {skip,Reason} | {skip_and_save,Reason,Config1} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +init_per_testcase(_TestCase, Config) -> + Config. + +%%-------------------------------------------------------------------- +%% @spec end_per_testcase(TestCase, Config0) -> +%% void() | {save_config,Config1} | {fail,Reason} +%% TestCase = atom() +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +end_per_testcase(_TestCase, _Config) -> + ok. + +%%-------------------------------------------------------------------- +%% @spec groups() -> [Group] +%% Group = {GroupName,Properties,GroupsAndTestCases} +%% GroupName = atom() +%% Properties = [parallel | sequence | Shuffle | {RepeatType,N}] +%% GroupsAndTestCases = [Group | {group,GroupName} | TestCase] +%% TestCase = atom() +%% Shuffle = shuffle | {shuffle,{integer(),integer(),integer()}} +%% RepeatType = repeat | repeat_until_all_ok | repeat_until_all_fail | +%% repeat_until_any_ok | repeat_until_any_fail +%% N = integer() | forever +%% @end +%%-------------------------------------------------------------------- +groups() -> + []. + +%%-------------------------------------------------------------------- +%% @spec all() -> GroupsAndTestCases | {skip,Reason} +%% GroupsAndTestCases = [{group,GroupName} | TestCase] +%% GroupName = atom() +%% TestCase = atom() +%% Reason = term() +%% @end +%%-------------------------------------------------------------------- +all() -> + [valid_connect, valid_disconnect, no_port_driver, + port_dies, owner_dies, controller_dies]. + +%%-------------------------------------------------------------------- +%% @spec TestCase(Config0) -> +%% ok | exit() | {skip,Reason} | {comment,Comment} | +%% {save_config,Config1} | {skip_and_save,Reason,Config1} +%% Config0 = Config1 = [tuple()] +%% Reason = term() +%% Comment = term() +%% @end +%%-------------------------------------------------------------------- +valid_connect(doc) -> + ["Test that a normal connection works."]; +valid_connect(Config) -> + DBInfo = ct:get_config(db_info), + {ok, DBRef} = mysqlerl:connect(?config(host, DBInfo), + ?config(port, DBInfo), + ?config(name, DBInfo), + ?config(username, DBInfo), + ?config(password, DBInfo), + ?config(options, DBInfo)), + [{db_ref, DBRef} | Config]. + +valid_disconnect(doc) -> + ["Test that disconnection works with open connection."]; +valid_disconnect(Config) -> + ok = mysqlerl:disconnect(?config(db_ref, Config)). + +no_port_driver(doc) -> + ["Test that connection fails properly when the port driver is missing."]; +no_port_driver(_Config) -> + Dir = filename:nativename(filename:join(code:priv_dir(mysqlerl), "bin")), + FN1 = filename:nativename(os:find_executable("mysqlerl", Dir)), + FN2 = filename:nativename(filename:join(Dir, "mysqlerl.bak")), + ok = file:rename(FN1, FN2), + + DBInfo = ct:get_config(db_info), + Res = case catch mysqlerl:connect(?config(host, DBInfo), + ?config(port, DBInfo), + ?config(name, DBInfo), + ?config(username, DBInfo), + ?config(password, DBInfo), + ?config(options, DBInfo)) of + {error, port_program_executable_not_found} -> ok; + Other -> Other + end, + + ok = file:rename(FN2, FN1), + ok = Res. + +port_dies(doc) -> + ["Test that port driver crashes properly."]; +port_dies(_Config) -> + DBInfo = ct:get_config(db_info), + {ok, Ref} = mysqlerl:connect(?config(host, DBInfo), + ?config(port, DBInfo), + ?config(name, DBInfo), + ?config(username, DBInfo), + ?config(password, DBInfo), + ?config(options, DBInfo)), + {status, _} = process_info(Ref, status), + MonRef = erlang:monitor(process, Ref), + Port = find_port(Ref), + exit(Port, kill), + + receive + {'DOWN', MonRef, _Type, _Object, _Info} -> + ok + after 5000 -> + test_server:fail(owner_process_not_stopped) + end. + +owner_dies(doc) -> + ["Test that port closes when owner dies."]; +owner_dies(_Config) -> + PID = spawn(?MODULE, owner_fun, [self()]), + MonRef = receive + {ref, Ref} -> + MRef = erlang:monitor(process, Ref), + PID ! continue, + MRef + end, + + receive + {'DOWN', MonRef, _Type, _Object, _Info} -> + ok + after 5000 -> + test_server:fail(owner_process_not_stopped) + end. + +owner_fun(PID) -> + DBInfo = ct:get_config(db_info), + {ok, Ref} = mysqlerl:connect(?config(host, DBInfo), + ?config(port, DBInfo), + ?config(name, DBInfo), + ?config(username, DBInfo), + ?config(password, DBInfo), + ?config(options, DBInfo)), + PID ! {ref, Ref}, + receive + continue -> ok + end, + exit(self(), normal). + +controller_dies(doc) -> + ["Test that connection controller death kills the port."]; +controller_dies(_Config) -> + DBInfo = ct:get_config(db_info), + {ok, Ref} = mysqlerl:connect(?config(host, DBInfo), + ?config(port, DBInfo), + ?config(name, DBInfo), + ?config(username, DBInfo), + ?config(password, DBInfo), + ?config(options, DBInfo)), + Port = find_port(Ref), + {connected, Ref} = erlang:port_info(Port, connected), + exit(Ref, kill), + test_server:sleep(500), + undefined = erlang:port_info(Port, connected). + +find_port(Ref) -> + find_port(Ref, erlang:ports()). + +find_port(Ref, [Port | T]) -> + case proplists:lookup(connected, erlang:port_info(Port)) of + {connected, Ref} -> Port; + _ -> find_port(Ref, T) + end; +find_port(_Ref, []) -> + undefined. diff --git a/test/mysqlerl_test_lib.erl b/test/mysqlerl_test_lib.erl index a94e8e9..7a98c09 100644 --- a/test/mysqlerl_test_lib.erl +++ b/test/mysqlerl_test_lib.erl @@ -9,6 +9,8 @@ -compile(export_all). +-include_lib("common_test/include/ct.hrl"). + mysql_cmd(undefined, undefined) -> "mysql"; mysql_cmd(User, undefined) -> @@ -18,17 +20,40 @@ mysql_cmd(undefined, Pass) -> mysql_cmd(User, Pass) -> io_lib:format("mysql -u'~s' -p'~s'", [User, Pass]). +create_db(_Config) -> + DBInfo = ct:get_config(db_info), + User = ?config(username, DBInfo), + Pass = ?config(password, DBInfo), + Name = ?config(name, DBInfo), + create_db(User, Pass, Name). + + create_db(User, Pass, Name) -> drop_db(User, Pass, Name), SQL = io_lib:format("CREATE DATABASE ~s", [Name]), CMD = mysql_cmd(User, Pass), os:cmd(io_lib:format("echo '~s' | ~s", [SQL, CMD])). +drop_db(_Config) -> + DBInfo = ct:get_config(db_info), + User = ?config(username, DBInfo), + Pass = ?config(password, DBInfo), + Name = ?config(name, DBInfo), + drop_db(User, Pass, Name). + drop_db(User, Pass, Name) -> SQL = io_lib:format("DROP DATABASE IF EXISTS ~s", [Name]), CMD = mysql_cmd(User, Pass), os:cmd(io_lib:format("echo '~s' | ~s", [SQL, CMD])). +create_table(Config) -> + DBInfo = ct:get_config(db_info), + User = ?config(username, DBInfo), + Pass = ?config(password, DBInfo), + Name = ?config(name, DBInfo), + DataDir = ?config(data_dir, Config), + create_table(User, Pass, Name, DataDir). + create_table(User, Pass, Name, DataDir) -> CMD = mysql_cmd(User, Pass), os:cmd(io_lib:format("~s ~s < ~s/table-data.sql", [CMD, Name, DataDir])). |