aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/mysqlerl_connection.erl56
-rw-r--r--test/mysqlerl_SUITE.erl19
-rw-r--r--test/mysqlerl_connect_SUITE.erl242
-rw-r--r--test/mysqlerl_test_lib.erl25
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])).