diff options
Diffstat (limited to 'lib/megaco/test/megaco_tcp_test.erl')
-rw-r--r-- | lib/megaco/test/megaco_tcp_test.erl | 1253 |
1 files changed, 1253 insertions, 0 deletions
diff --git a/lib/megaco/test/megaco_tcp_test.erl b/lib/megaco/test/megaco_tcp_test.erl new file mode 100644 index 0000000000..31c88489fe --- /dev/null +++ b/lib/megaco/test/megaco_tcp_test.erl @@ -0,0 +1,1253 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2000-2009. All Rights Reserved. +%% +%% The contents of this file are subject to the Erlang Public License, +%% Version 1.1, (the "License"); you may not use this file except in +%% compliance with the License. You should have received a copy of the +%% Erlang Public License along with this software. If not, it can be +%% retrieved online at http://www.erlang.org/. +%% +%% Software distributed under the License is distributed on an "AS IS" +%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See +%% the License for the specific language governing rights and limitations +%% under the License. +%% +%% %CopyrightEnd% +%% + +%% +%%---------------------------------------------------------------------- +%% Purpose: +%%---------------------------------------------------------------------- +-module(megaco_tcp_test). + +%%---------------------------------------------------------------------- +%% Include files +%%---------------------------------------------------------------------- +-include_lib("megaco/src/tcp/megaco_tcp.hrl"). +-include("megaco_test_lib.hrl"). + +%% -compile(export_all). + + +%%---------------------------------------------------------------------- +%% External exports +%%---------------------------------------------------------------------- +-export([ + all/1, + + start/1, + start_normal/1, + start_invalid_opt/1, + start_and_stop/1, + + sending/1, + sendreceive/1, + block_unblock/1, + + errors/1, + socket_failure/1, + accept_process/1, + accept_supervisor/1, + connection_supervisor/1, + tcp_server/1, + + init_per_testcase/2, fin_per_testcase/2, + + t/0, t/1 + ]). + +%%---------------------------------------------------------------------- +%% Internal exports +%%---------------------------------------------------------------------- + +-export([ + receive_message/4, + process_received_message/4 + ]). + + +%%---------------------------------------------------------------------- +%% Macros +%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Records +%%---------------------------------------------------------------------- + +-record(command, {id, desc, cmd}). +-record(server, {parent, transport_ref, control_pid, handle}). +-record(client, {parent, transport_ref, control_pid, handle}). + + +%%====================================================================== +%% External functions +%%====================================================================== +%%---------------------------------------------------------------------- +%% Function: t/0 +%% Description: Run all test cases +%%---------------------------------------------------------------------- +t() -> megaco_test_lib:t(?MODULE). + + +%%---------------------------------------------------------------------- +%% Function: t/1 +%% Description: Run the specified test cases +%%---------------------------------------------------------------------- +t(Case) -> megaco_test_lib:t({?MODULE, Case}). + + +%%====================================================================== +%% Test server callbacks +%%====================================================================== +%%---------------------------------------------------------------------- +%% Function: init_per_testcase/2 +%% Description: +%%---------------------------------------------------------------------- +init_per_testcase(Case, Config) -> + megaco_test_lib:init_per_testcase(Case, Config). + + +%%---------------------------------------------------------------------- +%% Function: fin_per_testcase/2 +%% Description: +%%---------------------------------------------------------------------- +fin_per_testcase(Case, Config) -> + megaco_test_lib:fin_per_testcase(Case, Config). + + +%%====================================================================== +%% Test case definitions +%%====================================================================== +all(suite) -> + [ + start, + sending, + errors + ]. + +start(suite) -> + [ + start_normal, + start_invalid_opt, + start_and_stop + ]. + +sending(suite) -> + [ + sendreceive, + block_unblock + ]. + +errors(suite) -> + [ + socket_failure, + accept_process, + accept_supervisor, + connection_supervisor, + tcp_server + ]. + + +%% ------------------ start ------------------------ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_normal(suite) -> + []; +start_normal(Config) when is_list(Config) -> + put(sname, "start_normal"), + p("BEGIN TEST-CASE"), + Options = [{port, 20000}, {receive_handle, apa}], + {ok, Pid} = start_case(Options, ok), + megaco_tcp:stop_transport(Pid), + p("done"), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_invalid_opt(suite) -> + []; +start_invalid_opt(Config) when is_list(Config) -> + put(sname, "start_invalid_opt"), + p("BEGIN TEST-CASE"), + Options = [{port, 20000}, {receivehandle, apa}], + ok = start_case(Options, error), + p("done"), + ok. + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +start_and_stop(suite) -> + []; +start_and_stop(doc) -> + ["This test case sets up a connection and then cloises it. " + "No data is sent. "]; +start_and_stop(Config) when is_list(Config) -> + put(sname, "start_and_stop"), + p("BEGIN TEST-CASE"), + + process_flag(trap_exit, true), + + p("create nodes"), + ServerNode = make_node_name(server), + ClientNode = make_node_name(client), + Nodes = [ServerNode, ClientNode], + ok = megaco_test_lib:start_nodes(Nodes, ?FILE, ?LINE), + + %% Create command sequences + p("create command sequences"), + ServerPort = 2944, + ServerCmds = start_and_stop_server_commands(ServerPort), + {ok, ServerHost} = inet:gethostname(), + ClientCmds = start_and_stop_client_commands(ServerPort, ServerHost), + + %% Start the test procs used in the test-case, one for each node + p("start command handlers"), + Server = server_start_command_handler(ServerNode, ServerCmds), + p("server command handler started: ~p", [Server]), + Client = client_start_command_handler(ClientNode, ClientCmds), + p("client command handler started: ~p", [Client]), + + ok = + receive + {listening, Server} -> + p("received listening message from server [~p] => " + "send continue to client [~p]~n", [Server, Client]), + Client ! {continue, self()}, + ok + after 5000 -> + {error, server_timeout} + end, + + await_command_handler_completion([Server, Client], timer:seconds(20)), + p("done"), + ok. + + +start_and_stop_server_commands(Port) -> + Opts = [{port, Port}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#server{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + server_start_transport(State) + end}, + + #command{id = 3, + desc = "Listen", + cmd = fun(State) -> + server_listen(State, Opts) + end}, + + #command{id = 4, + desc = "Notify listening", + cmd = fun(State) -> + server_notify_listening(State) + end}, + + #command{id = 5, + desc = "Await nothing", + cmd = fun(State) -> + server_await_nothing(State, 6000) + end}, + + #command{id = 6, + desc = "Stop", + cmd = fun(State) -> + server_stop_transport(State) + end} + + ]. + + +start_and_stop_client_commands(ServerPort, ServerHost) -> + Opts = [{port, ServerPort}, {host, ServerHost}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#client{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + client_start_transport(State) + end}, + + #command{id = 3, + desc = "Await continue", + cmd = fun(State) -> + client_await_continue_signal(State, 5000) + end}, + + #command{id = 4, + desc = "Connect", + cmd = fun(State) -> + client_connect(State, Opts) + end}, + + #command{id = 5, + desc = "Await nothing", + cmd = fun(State) -> + client_await_nothing(State, 5000) + end}, + + #command{id = 6, + desc = "Disconnect", + cmd = fun(State) -> + client_disconnect(State) + end}, + + #command{id = 7, + desc = "Stop transport", + cmd = fun(State) -> + client_stop_transport(State) + end} + ]. + + +%% ------------------ sending ------------------------ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +sendreceive(suite) -> + []; +sendreceive(Config) when is_list(Config) -> + put(sname, "sendreceive"), + p("BEGIN TEST-CASE"), + + process_flag(trap_exit, true), + + p("create nodes"), + ServerNode = make_node_name(server), + ClientNode = make_node_name(client), + Nodes = [ServerNode, ClientNode], + ok = megaco_test_lib:start_nodes(Nodes, ?FILE, ?LINE), + + %% Create command sequences + p("create command sequences"), + ServerPort = 2944, + ServerCmds = sendreceive_server_commands(ServerPort), + {ok, ServerHost} = inet:gethostname(), + ClientCmds = sendreceive_client_commands(ServerPort, ServerHost), + + %% Start the test procs used in the test-case, one for each node + p("start command handlers"), + Server = server_start_command_handler(ServerNode, ServerCmds), + p("server command handler started: ~p", [Server]), + Client = client_start_command_handler(ClientNode, ClientCmds), + p("client command handler started: ~p", [Client]), + + ok = + receive + {listening, Server} -> + p("received listening message from server [~p] => " + "send continue to client [~p]~n", [Server, Client]), + Client ! {continue, self()}, + ok + after 5000 -> + {error, server_timeout} + end, + + await_command_handler_completion([Server, Client], timer:seconds(20)), + p("done"), + ok. + + +sendreceive_server_commands(Port) -> + Opts = [{port, Port}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#server{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + server_start_transport(State) + end}, + + #command{id = 3, + desc = "Listen", + cmd = fun(State) -> + server_listen(State, Opts) + end}, + + #command{id = 4, + desc = "Notify listening", + cmd = fun(State) -> + server_notify_listening(State) + end}, + + #command{id = 5, + desc = "Await initial message (ping)", + cmd = fun(State) -> + server_await_initial_message(State, "ping", 5000) + end}, + + #command{id = 6, + desc = "Send reply (pong) to initial message", + cmd = fun(State) -> + server_send_message(State, "pong") + end}, + + #command{id = 7, + desc = "Await nothing before sending a message (hejsan)", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 8, + desc = "Send message (hejsan)", + cmd = fun(State) -> + server_send_message(State, "hejsan") + end}, + + #command{id = 9, + desc = "Await reply (hoppsan) to message", + cmd = fun(State) -> + server_await_message(State, "hoppsan", 1000) + end}, + + #command{id = 10, + desc = "Await nothing before disconnecting", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 11, + desc = "Disconnect", + cmd = fun(State) -> + server_disconnect(State) + end}, + + #command{id = 12, + desc = "Await nothing before stopping transport", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 13, + desc = "Stop", + cmd = fun(State) -> + server_stop_transport(State) + end} + + ]. + +sendreceive_client_commands(ServerPort, ServerHost) -> + Opts = [{port, ServerPort}, {host, ServerHost}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#client{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + client_start_transport(State) + end}, + + #command{id = 3, + desc = "Await continue", + cmd = fun(State) -> + client_await_continue_signal(State, 5000) + end}, + + #command{id = 4, + desc = "Connect", + cmd = fun(State) -> + client_connect(State, Opts) + end}, + + #command{id = 5, + desc = "Send initial message (ping)", + cmd = fun(State) -> + client_send_message(State, "ping") + end}, + + #command{id = 6, + desc = "Await reply (pong) to initial message", + cmd = fun(State) -> + client_await_message(State, "pong", 1000) + end}, + + #command{id = 7, + desc = "Await message (hejsan)", + cmd = fun(State) -> + client_await_message(State, "hejsan", 5000) + end}, + + #command{id = 8, + desc = "Send reply (hoppsan) to message", + cmd = fun(State) -> + client_send_message(State, "hoppsan") + end}, + + #command{id = 9, + desc = "Await nothing before disconnecting", + cmd = fun(State) -> + client_await_nothing(State, 1000) + end}, + + #command{id = 10, + desc = "Disconnect", + cmd = fun(State) -> + client_disconnect(State) + end}, + + #command{id = 11, + desc = "Await nothing before stopping transport", + cmd = fun(State) -> + client_await_nothing(State, 1000) + end}, + + #command{id = 12, + desc = "Stop transport", + cmd = fun(State) -> + client_stop_transport(State) + end} + ]. + + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +block_unblock(suite) -> + []; +block_unblock(Config) when is_list(Config) -> + put(sname, "block_unblock"), + p("BEGIN TEST-CASE"), + + process_flag(trap_exit, true), + + p("create nodes"), + ServerNode = make_node_name(server), + ClientNode = make_node_name(client), + Nodes = [ServerNode, ClientNode], + ok = megaco_test_lib:start_nodes(Nodes, ?FILE, ?LINE), + + %% Create command sequences + p("create command sequences"), + ServerPort = 2944, + ServerCmds = block_unblock_server_commands(ServerPort), + {ok, ServerHost} = inet:gethostname(), + ClientCmds = block_unblock_client_commands(ServerPort, ServerHost), + + %% Start the test procs used in the test-case, one for each node + p("start command handlers"), + Server = server_start_command_handler(ServerNode, ServerCmds), + p("server command handler started: ~p", [Server]), + Client = client_start_command_handler(ClientNode, ClientCmds), + p("client command handler started: ~p", [Client]), + + ok = + receive + {listening, Server} -> + p("received listening message from server [~p] => " + "send continue to client [~p]~n", [Server, Client]), + Client ! {continue, self()}, + ok + after 5000 -> + {error, server_timeout} + end, + + ok = + receive + {blocked, Client} -> + p("received blocked message from client [~p] => " + "send continue to server [~p]~n", [Client, Server]), + Server ! {continue, self()}, + ok + after 5000 -> + {error, timeout} + end, + + await_command_handler_completion([Server, Client], timer:seconds(30)), + p("done"), + ok. + + +block_unblock_server_commands(Port) -> + Opts = [{port, Port}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#server{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + server_start_transport(State) + end}, + + #command{id = 3, + desc = "Listen", + cmd = fun(State) -> + server_listen(State, Opts) + end}, + + #command{id = 4, + desc = "Notify listening", + cmd = fun(State) -> + server_notify_listening(State) + end}, + + #command{id = 5, + desc = "Await initial message (ping)", + cmd = fun(State) -> + server_await_initial_message(State, "ping", 5000) + end}, + + #command{id = 6, + desc = "Send reply (pong) to initial message", + cmd = fun(State) -> + server_send_message(State, "pong") + end}, + + #command{id = 7, + desc = "Await continue", + cmd = fun(State) -> + server_await_continue_signal(State, 5000) + end}, + + #command{id = 9, + desc = "Await nothing before sending a message (hejsan)", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 10, + desc = "Send message (hejsan)", + cmd = fun(State) -> + server_send_message(State, "hejsan") + end}, + + #command{id = 11, + desc = "Await reply (hoppsan) to message", + cmd = fun(State) -> + server_await_message(State, "hoppsan", 10000) + end}, + + #command{id = 12, + desc = "Await nothing before disconnecting", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 13, + desc = "Disconnect", + cmd = fun(State) -> + server_disconnect(State) + end}, + + #command{id = 14, + desc = "Await nothing before stopping transport", + cmd = fun(State) -> + server_await_nothing(State, 1000) + end}, + + #command{id = 15, + desc = "Stop", + cmd = fun(State) -> + server_stop_transport(State) + end} + + ]. + +block_unblock_client_commands(ServerPort, ServerHost) -> + Opts = [{port, ServerPort}, {host, ServerHost}], + Self = self(), + [ + #command{id = 1, + desc = "Command sequence init", + cmd = fun(State) -> + {ok, State#client{parent = Self}} + end}, + + #command{id = 2, + desc = "Start transport", + cmd = fun(State) -> + client_start_transport(State) + end}, + + #command{id = 3, + desc = "Await continue", + cmd = fun(State) -> + client_await_continue_signal(State, 5000) + end}, + + #command{id = 4, + desc = "Connect", + cmd = fun(State) -> + client_connect(State, Opts) + end}, + + #command{id = 5, + desc = "Send initial message (ping)", + cmd = fun(State) -> + client_send_message(State, "ping") + end}, + + #command{id = 6, + desc = "Await reply (pong) to initial message", + cmd = fun(State) -> + client_await_message(State, "pong", 1000) + end}, + + #command{id = 7, + desc = "Await nothing before blocking", + cmd = fun(State) -> + client_await_nothing(State, 1000) + end}, + + #command{id = 8, + desc = "Block", + cmd = fun(State) -> + client_block(State) + end}, + + #command{id = 9, + desc = "Notify blocked", + cmd = fun(State) -> + client_notify_blocked(State) + end}, + + #command{id = 10, + desc = "Await nothing before unblocking", + cmd = fun(State) -> + client_await_nothing(State, 5000) + end}, + + #command{id = 11, + desc = "Unblock", + cmd = fun(State) -> + client_unblock(State) + end}, + + #command{id = 12, + desc = "Await message (hejsan)", + cmd = fun(State) -> + client_await_message(State, "hejsan", 100) + end}, + + #command{id = 13, + desc = "Send reply (hoppsan) to message", + cmd = fun(State) -> + client_send_message(State, "hoppsan") + end}, + + #command{id = 14, + desc = "Await nothing before disconnecting", + cmd = fun(State) -> + client_await_nothing(State, 1000) + end}, + + #command{id = 15, + desc = "Disconnect", + cmd = fun(State) -> + client_disconnect(State) + end}, + + #command{id = 16, + desc = "Await nothing before stopping transport", + cmd = fun(State) -> + client_await_nothing(State, 1000) + end}, + + #command{id = 17, + desc = "Stop transport", + cmd = fun(State) -> + client_stop_transport(State) + end} + ]. + + + +%% ------------------ errors ------------------------ + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +socket_failure(suite) -> + []; +socket_failure(Config) when is_list(Config) -> + put(sname, "socket_failure"), + p("BEGIN TEST-CASE"), + + %% process_flag(trap_exit, true), + + socket_faulure(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +accept_process(suite) -> + []; +accept_process(Config) when is_list(Config) -> + put(sname, "accept_process"), + p("BEGIN TEST-CASE"), + + %% process_flag(trap_exit, true), + + failing_accept_process(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +accept_supervisor(suite) -> + []; +accept_supervisor(Config) when is_list(Config) -> + put(sname, "accept_supervisor"), + p("BEGIN TEST-CASE"), + + %% process_flag(trap_exit, true), + + failing_accept_supervisor(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +connection_supervisor(suite) -> + []; +connection_supervisor(Config) when is_list(Config) -> + put(sname, "connection_supervisor"), + p("BEGIN TEST-CASE"), + + %% process_flag(trap_exit, true), + + failing_connection_supervisor(). + + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +tcp_server(suite) -> + []; +tcp_server(Config) when is_list(Config) -> + put(sname, "tcp_server"), + p("BEGIN TEST-CASE"), + + %% process_flag(trap_exit, true), + + failing_tcp_server(). + + +%%====================================================================== +%% Test functions +%%====================================================================== + +start_case(Options, Expect) -> + p("start transport"), + case (catch megaco_tcp:start_transport()) of + {ok, Pid} -> + p("create listen socket"), + case (catch megaco_tcp:listen(Pid, Options)) of + ok when Expect =:= ok -> + p("extected listen result [ok]"), + {ok, Pid}; + ok -> + p("unextected listen result [ok] - stop transport"), + megaco_tcp:stop_transport(Pid), + ?ERROR(unexpected_start_sucesss); + {error, _Reason} when Expect =:= error -> + p("extected listen result [error] - stop transport"), + megaco_tcp:stop_transport(Pid), + ok; + {error, Reason} -> + p("unextected listen result [error] - stop transport"), + megaco_tcp:stop_transport(Pid), + ?ERROR({unexpected_start_failure, Reason}); + Error -> + p("unextected listen result"), + ?ERROR({unexpected_result, Error}) + end; + {error, Reason} -> + p("unextected start_transport result"), + ?ERROR({failed_starting_transport, Reason}) + end. + +socket_faulure() -> + ?SKIP(not_yet_implemented). + +failing_accept_process() -> + ?SKIP(not_yet_implemented). + +failing_accept_supervisor() -> + ?SKIP(not_yet_implemented). + +failing_connection_supervisor() -> + ?SKIP(not_yet_implemented). + +failing_tcp_server() -> + ?SKIP(not_yet_implemented). + + +%%---------------------------------------------------------------------- +%% Message Callback functions +%%---------------------------------------------------------------------- + +receive_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) + when is_pid(ReceiveHandle) andalso is_binary(BinMsg) -> + Msg = binary_to_list(BinMsg), + ReceiveHandle ! {receive_message, {ControlPid, SendHandle, Msg}}, + ok. + +process_received_message(ReceiveHandle, ControlPid, SendHandle, BinMsg) + when is_pid(ReceiveHandle) andalso is_binary(BinMsg) -> + Msg = binary_to_list(BinMsg), + ReceiveHandle ! {process_received_message, {ControlPid, SendHandle, Msg}}, + ok. + + +%%====================================================================== +%% Internal functions +%%====================================================================== + +%% ------- Server command handler and utility functions ---------- + +server_start_command_handler(Node, Commands) -> + start_command_handler(Node, Commands, #server{}, "server"). + +server_start_transport(State) when is_record(State, server) -> + case (catch megaco_tcp:start_transport()) of + {ok, Ref} -> + {ok, State#server{transport_ref = Ref}}; + Error -> + Error + end. + +server_listen(#server{transport_ref = Ref} = State, Options) + when is_record(State, server) andalso is_list(Options) -> + Opts = [{receive_handle, self()}, {module, ?MODULE} | Options], + case (catch megaco_tcp:listen(Ref, Opts)) of + ok -> + {ok, State}; + Error -> + Error + end. + +server_notify_listening(#server{parent = Parent} = State) + when is_record(State, server) -> + Parent ! {listening, self()}, + {ok, State}. + +server_await_continue_signal(#server{parent = Parent} = State, Timeout) -> + receive + {continue, Parent} -> + {ok, State} + after Timeout -> + {error, timeout} + end. + +server_await_initial_message(State, InitialMessage, Timeout) + when is_record(State, server) -> + receive + {receive_message, {ControlPid, Handle, InitialMessage}} -> + NewState = State#server{control_pid = ControlPid, + handle = Handle}, + {ok, NewState}; + + Any -> + p("received unexpected event: ~p", [Any]), + {error, {unexpected_event, Any}} + + after Timeout -> + {error, timeout} + end. + +server_send_message(#server{handle = Handle} = State, Message) -> + megaco_tcp:send_message(Handle, Message), + {ok, State}. + +server_await_nothing(State, Timeout) + when is_record(State, server) -> + receive + Any -> + p("received unexpected event: ~p", [Any]), + {error, {unexpected_event, Any}} + + after Timeout -> + {ok, State} + end. + + +server_await_message(State, ExpectMessage, Timeout) + when is_record(State, server) -> + receive + {receive_message, {_, _, ExpectMessage}} -> + {ok, State}; + + Any -> + p("received unexpected event: ~p", [Any]), + {error, {unexpected_event, Any}} + + after Timeout -> + {error, timeout} + end. + +server_disconnect(#server{handle = Handle} = State) + when (Handle =/= undefined) -> + megaco_tcp:close(Handle), + {ok, State#server{handle = undefined}}. + +%% server_block(#server{handle = Handle} = State) +%% when (Handle =/= undefined) -> +%% megaco_tcp:block(Handle), +%% {ok, State}. + +%% server_unblock(#server{handle = Handle} = State) +%% when (Handle =/= undefined) -> +%% megaco_tcp:unblock(Handle), +%% {ok, State}. + +server_stop_transport(#server{transport_ref = Ref} = State) + when (Ref =/= undefined) -> + megaco_tcp:stop_transport(Ref), + {ok, State}. + + +%% ------- Client command handler and utility functions ---------- + +client_start_command_handler(Node, Commands) -> + start_command_handler(Node, Commands, #client{}, "client"). + +client_start_transport(State) when is_record(State, client) -> + case (catch megaco_tcp:start_transport()) of + {ok, Ref} -> + {ok, State#client{transport_ref = Ref}}; + Error -> + Error + end. + +client_connect(#client{transport_ref = Ref} = State, Options) + when is_record(State, client) andalso is_list(Options) -> + Opts = [{receive_handle, self()}, {module, ?MODULE} | Options], + case (catch megaco_tcp:connect(Ref, Opts)) of + {ok, Handle, ControlPid} -> + {ok, State#client{control_pid = ControlPid, + handle = Handle}}; + Error -> + Error + end. + +client_await_continue_signal(#client{parent = Parent} = State, Timeout) -> + receive + {continue, Parent} -> + {ok, State} + after Timeout -> + {error, timeout} + end. + +client_notify_blocked(#client{parent = Parent} = State) -> + Parent ! {blocked, self()}, + {ok, State}. + +client_await_nothing(State, Timeout) + when is_record(State, client) -> + receive + Any -> + p("received unexpected event: ~p", [Any]), + {error, {unexpected_event, Any}} + after Timeout -> + {ok, State} + end. + +client_send_message(#client{handle = Handle} = State, Message) -> + megaco_tcp:send_message(Handle, Message), + {ok, State}. + +client_await_message(State, ExpectMessage, Timeout) + when is_record(State, client) -> + receive + {receive_message, {_, _, ExpectMessage}} -> + {ok, State}; + + Any -> + p("received unexpected event: ~p", [Any]), + {error, {unexpected_event, Any}} + + after Timeout -> + {error, timeout} + end. + +client_block(#client{handle = Handle} = State) + when (Handle =/= undefined) -> + megaco_tcp:block(Handle), + {ok, State}. + +client_unblock(#client{handle = Handle} = State) + when (Handle =/= undefined) -> + megaco_tcp:unblock(Handle), + {ok, State}. + +client_disconnect(#client{handle = Handle} = State) + when (Handle =/= undefined) -> + megaco_tcp:close(Handle), + {ok, State#client{handle = undefined, control_pid = undefined}}. + +client_stop_transport(#client{transport_ref = Ref} = State) + when (Ref =/= undefined) -> + megaco_tcp:stop_transport(Ref), + {ok, State}. + + +%% -------- Command handler --------- + +start_command_handler(Node, Commands, State, ShortName) -> + Fun = fun() -> + put(sname, ShortName), + process_flag(trap_exit, true), + Result = (catch command_handler(Commands, State)), + p("command handler terminated with: " + "~n Result: ~p", [Result]), + exit(Result) + end, + erlang:spawn_link(Node, Fun). + +command_handler([], State) -> + p("command_handler -> entry when done with" + "~n State: ~p", [State]), + {ok, State}; +command_handler([#command{id = Id, + desc = Desc, + cmd = Cmd}|Commands], State) -> + p("command_handler -> entry with" + "~n Id: ~p" + "~n Desc: ~p", [Id, Desc]), + case (catch Cmd(State)) of + {ok, NewState} -> + p("command_handler -> cmd ~w ok", [Id]), + command_handler(Commands, NewState); + {error, Reason} -> + p("command_handler -> cmd ~w error: " + "~n Reason: ~p", [Id, Reason]), + {error, {cmd_error, Reason}}; + {'EXIT', Reason} -> + p("command_handler -> cmv ~w exit: " + "~n Reason: ~p", [Id, Reason]), + {error, {cmd_exit, Reason}}; + Error -> + p("command_handler -> cmd ~w failure: " + "~n Error: ~p", [Id, Error]), + {error, {cmd_failure, Error}} + end. + + +await_command_handler_completion(Pids, Timeout) -> + await_command_handler_completion(Pids, [], [], Timeout). + +await_command_handler_completion([], [], _Good, _Timeout) -> + p("await_command_handler_completion -> entry when done"), + ok; +await_command_handler_completion([], Bad, Good, _Timeout) -> + p("await_command_handler_completion -> entry when done with bad result: " + "~n Bad: ~p" + "~n Good: ~p", [Bad, Good]), + ok; +await_command_handler_completion(Pids, Bad, Good, Timeout) -> + p("await_command_handler_completion -> entry when waiting for" + "~n Pids: ~p" + "~n Bad: ~p" + "~n Good: ~p" + "~n Timeout: ~p", [Pids, Bad, Good, Timeout]), + Begin = ms(), + receive + {'EXIT', Pid, {ok, FinalState}} -> + p("await_command_handler_completion -> " + "received ok EXIT signal from ~p", [Pid]), + case lists:delete(Pid, Pids) of + Pids -> + await_command_handler_completion(Pids, Bad, Good, + Timeout - (ms() - Begin)); + Pids2 -> + p("await_command_handler_completion -> ~p done", [Pid]), + await_command_handler_completion(Pids2, + Bad, + [{Pid, FinalState}|Good], + Timeout - (ms() - Begin)) + end; + + {'EXIT', Pid, {error, Reason}} -> + p("await_command_handler_completion -> " + "received error EXIT signal from ~p", [Pid]), + case lists:delete(Pid, Pids) of + Pids -> + await_command_handler_completion(Pids, Bad, Good, + Timeout - (ms() - Begin)); + Pids2 -> + p("await_command_handler_completion -> ~p done", [Pid]), + await_command_handler_completion(Pids2, + [{Pid, Reason}|Bad], + Good, + Timeout - (ms() - Begin)) + end + + after Timeout -> + p("await_command_handler_completion -> timeout"), + exit({timeout, Pids}) + end. + + + +%% ------- Misc functions -------- + +make_node_name(Name) -> + case string:tokens(atom_to_list(node()), [$@]) of + [_,Host] -> + list_to_atom(lists:concat([atom_to_list(Name) ++ "@" ++ Host])); + _ -> + exit("Test node must be started with '-sname'") + end. + + +p(F) -> + p(F, []). + +p(F, A) -> + p(get(sname), F, A). + +p(S, F, A) when is_list(S) -> + io:format("*** [~s] ~p ~s ***" + "~n " ++ F ++ "~n", + [format_timestamp(now()), self(), S | A]); +p(_S, F, A) -> + io:format("*** [~s] ~p ~s *** " + "~n " ++ F ++ "~n", + [format_timestamp(now()), self(), "undefined" | A]). + + +ms() -> + {A,B,C} = erlang:now(), + A*1000000000+B*1000+(C div 1000). + + +format_timestamp({_N1, _N2, N3} = Now) -> + {Date, Time} = calendar:now_to_datetime(Now), + {YYYY,MM,DD} = Date, + {Hour,Min,Sec} = Time, + FormatDate = + io_lib:format("~.4w:~.2.0w:~.2.0w ~.2.0w:~.2.0w:~.2.0w 4~w", + [YYYY,MM,DD,Hour,Min,Sec,round(N3/1000)]), + lists:flatten(FormatDate). |