aboutsummaryrefslogtreecommitdiffstats
path: root/lib/megaco/test/megaco_test_megaco_generator.erl
diff options
context:
space:
mode:
authorErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
committerErlang/OTP <[email protected]>2009-11-20 14:54:40 +0000
commit84adefa331c4159d432d22840663c38f155cd4c1 (patch)
treebff9a9c66adda4df2106dfd0e5c053ab182a12bd /lib/megaco/test/megaco_test_megaco_generator.erl
downloadotp-84adefa331c4159d432d22840663c38f155cd4c1.tar.gz
otp-84adefa331c4159d432d22840663c38f155cd4c1.tar.bz2
otp-84adefa331c4159d432d22840663c38f155cd4c1.zip
The R13B03 release.OTP_R13B03
Diffstat (limited to 'lib/megaco/test/megaco_test_megaco_generator.erl')
-rw-r--r--lib/megaco/test/megaco_test_megaco_generator.erl1120
1 files changed, 1120 insertions, 0 deletions
diff --git a/lib/megaco/test/megaco_test_megaco_generator.erl b/lib/megaco/test/megaco_test_megaco_generator.erl
new file mode 100644
index 0000000000..5ff7162223
--- /dev/null
+++ b/lib/megaco/test/megaco_test_megaco_generator.erl
@@ -0,0 +1,1120 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2007-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: megaco sequence generator for the megaco test suite
+%%----------------------------------------------------------------------
+
+-module(megaco_test_megaco_generator).
+
+-behaviour(megaco_test_generator).
+
+%% API
+-export([
+ start_link/1, start_link/2,
+ stop/1,
+ exec/2, exec/3
+ ]).
+
+%% genarator behaviour callback exports
+-export([
+ init/1,
+ handle_parse/2,
+ handle_exec/2,
+ terminate/2
+ ]).
+
+%% Megaco callback api
+-export([
+ handle_connect/3, handle_connect/4,
+ handle_disconnect/4,
+ handle_syntax_error/4, handle_syntax_error/5,
+ handle_message_error/4, handle_message_error/5,
+ handle_trans_request/4, handle_trans_request/5,
+ handle_trans_long_request/4, handle_trans_long_request/5,
+ handle_trans_reply/5, handle_trans_reply/6,
+ handle_trans_ack/5, handle_trans_ack/6,
+ handle_trans_request_abort/5, handle_trans_request_abort/6,
+ handle_unexpected_trans/4, handle_unexpected_trans/5
+ ]).
+
+
+%%----------------------------------------------------------------------
+
+-include_lib("megaco/include/megaco.hrl").
+
+
+%%----------------------------------------------------------------------
+
+-define(DELIVER_MOD, megaco_test_deliver).
+
+
+%%----------------------------------------------------------------------
+
+-record(state,
+ {
+ mid,
+ recv_handle,
+ port,
+ send_handle,
+ conn_handle,
+
+ transport_sup,
+ ctrl_pid,
+
+ result = [] % Accumulated results from verification
+ }).
+
+
+%%----------------------------------------------------------------------
+%% API
+%%----------------------------------------------------------------------
+
+start_link(Name) ->
+ megaco_test_generator:start_link(?MODULE, [], Name).
+
+start_link(Name, Node) ->
+ megaco_test_generator:start_link(?MODULE, [], Name, Node).
+
+stop(Server) ->
+ megaco_test_generator:stop(Server).
+
+exec(Server, Instructions) when is_list(Instructions) ->
+ megaco_test_generator:exec(Server, Instructions).
+
+exec(Server, Instructions, Timeout) when is_list(Instructions) ->
+ megaco_test_generator:exec(Server, Instructions, Timeout).
+
+
+%%----------------------------------------------------------------------
+%% generator callback functions
+%%----------------------------------------------------------------------
+
+init([]) ->
+ random_init(),
+ {ok, #state{}}.
+
+
+%% ----- instruction parser -----
+
+handle_parse({debug, Debug} = Instruction, State)
+ when (Debug == true) orelse (Debug == false) ->
+ {ok, Instruction, State};
+
+handle_parse({expect_nothing, To} = Instruction, State)
+ when is_integer(To) andalso (To > 0) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_trace, Level} = Instruction, State)
+ when (Level == disable) orelse
+ (Level == max) orelse
+ (Level == min) orelse
+ is_integer(Level) ->
+ {ok, Instruction, State};
+
+handle_parse({sleep, To} = Instruction, State)
+ when is_integer(To) andalso (To > 0) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_start = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_stop = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_start_user, _Mid, _RecvInfo, Conf} = Instruction, State)
+ when is_list(Conf) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_stop_user = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_info = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_system_info, State) ->
+ Verify = fun(_) -> ok end,
+ Instruction = {megaco_system_info, internal_system_info_tag, Verify},
+ {ok, Instruction, State};
+
+handle_parse({megaco_system_info, Tag}, State)
+ when is_atom(Tag) ->
+ Verify = fun(_) -> ok end,
+ Instruction = {megaco_system_info, Tag, Verify},
+ {ok, Instruction, State};
+
+handle_parse({megaco_system_info, Tag, Verify} = Instruction, State)
+ when is_atom(Tag) andalso is_function(Verify) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_user_info, Tag} = Instruction, State)
+ when is_atom(Tag) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_update_user_info, Tag, _Val} = Instruction, State)
+ when is_atom(Tag) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_conn_info, Tag} = Instruction, State)
+ when is_atom(Tag) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_update_conn_info, Tag, _Val} = Instruction, State)
+ when is_atom(Tag) ->
+ {ok, Instruction, State};
+
+handle_parse(start_transport = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(listen = _Instruction, State) ->
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {listen, [], MeybeRetry},
+ {ok, Instruction, State};
+
+handle_parse({listen, Opts} = _Instruction, State)
+ when is_list(Opts) ->
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {listen, Opts, MeybeRetry},
+ {ok, Instruction, State};
+
+handle_parse({listen, Opts, MeybeRetry} = Instruction, State)
+ when is_list(Opts) andalso is_function(MeybeRetry) ->
+ {ok, Instruction, State};
+
+handle_parse(connect = _Instruction, State) ->
+ case inet:gethostname() of
+ {ok, LocalHost} ->
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {connect, LocalHost, [], MeybeRetry},
+ {ok, Instruction, State};
+ Error ->
+ Error
+ end;
+
+handle_parse({connect, Opts} = _Instruction, State)
+ when is_list(Opts) ->
+ verify_connect_opts(Opts),
+ case inet:gethostname() of
+ {ok, LocalHost} ->
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {connect, LocalHost, Opts, MeybeRetry},
+ {ok, Instruction, State};
+ Error ->
+ Error
+ end;
+
+handle_parse({connect, Host} = _Instruction, State)
+ when is_atom(Host) ->
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {connect, Host, [], MeybeRetry},
+ {ok, Instruction, State};
+
+handle_parse({connect, Host, Opts} = _Instruction, State)
+ when (is_atom(Host) orelse is_list(Host)) andalso is_list(Opts) ->
+ verify_connect_opts(Opts),
+ MeybeRetry = make_connect_retry_fun2(),
+ Instruction = {connect, Host, Opts, MeybeRetry},
+ {ok, Instruction, State};
+
+handle_parse({connect, Host, Opts, MeybeRetry} = Instruction, State)
+ when (is_atom(Host) orelse is_list(Host)) andalso
+ is_list(Opts) andalso
+ is_function(MeybeRetry) ->
+ verify_connect_opts(Opts),
+ {ok, Instruction, State};
+
+handle_parse(disconnect = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_connect = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_connect, _} = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse(megaco_disconnect = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_disconnect, _Reason} = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_call, ARs, Opts} = Instruction, State)
+ when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_call, _Mid, ARs, Opts} = Instruction, State)
+ when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_cast, ARs, Opts} = Instruction, State)
+ when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_cast, _Mid, ARs, Opts} = Instruction, State)
+ when (is_list(ARs) orelse is_binary(ARs)) andalso is_list(Opts) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_cancel, _Reason} = Instruction, State) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_callback, Tag, TimeoutOrVerify} = Instruction, State)
+ when (is_atom(Tag) andalso
+ ((is_integer(TimeoutOrVerify) andalso
+ (TimeoutOrVerify > 0)) orelse
+ is_function(TimeoutOrVerify))) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_callback, Tag, Verify, Timeout} = Instruction, State)
+ when (is_atom(Tag) andalso
+ is_function(Verify) andalso
+ (is_integer(Timeout) andalso (Timeout > 0))) ->
+ {ok, Instruction, State};
+
+handle_parse({megaco_callback, Tag, {VMod, VFunc, VArgs}} = _Instruction,
+ State)
+ when (is_atom(Tag) andalso
+ (is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs))) ->
+ Verify = fun(X) ->
+ io:format("[megaco_callback ~w] calling ~w:~w with"
+ "~n X: ~p"
+ "~n VArgs: ~w"
+ "~n", [Tag, VMod, VFunc, X, VArgs]),
+ (catch apply(VMod, VFunc, [X|VArgs]))
+ end,
+ Instruction = {megaco_callback, Tag, Verify},
+ {ok, Instruction, State};
+
+handle_parse({megaco_callback, Verifiers0} = _Instruction, State)
+ when is_list(Verifiers0) ->
+ Verifiers = [make_verifier(Verifier) || Verifier <- Verifiers0],
+ Instruction = {megaco_callback, Verifiers},
+ {ok, Instruction, State};
+
+handle_parse({trigger, Trigger} = Instruction, State)
+ when is_function(Trigger) ->
+ {ok, Instruction, State};
+
+handle_parse(Instruction, _State) ->
+ error({invalid_instruction, Instruction}).
+
+
+make_verifier({Tag, No, VerifyFunc} = Verify)
+ when is_atom(Tag) andalso is_integer(No) andalso is_function(VerifyFunc) ->
+ Verify;
+make_verifier({Tag, No, {VMod, VFunc, VArgs}})
+ when is_atom(Tag) andalso is_integer(No) andalso
+ (is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs)) ->
+ VerifyFunc = fun(X) ->
+ io:format("[megaco_callback ~w] calling ~w:~w with"
+ "~n X: ~p"
+ "~n VArgs: ~w"
+ "~n", [Tag, VMod, VFunc, X, VArgs]),
+ (catch apply(VMod, VFunc, [X|VArgs]))
+ end,
+ Verify = {Tag, No, VerifyFunc},
+ Verify;
+make_verifier(BadVerifier) ->
+ error({bad_verifier, BadVerifier}).
+
+
+verify_connect_opts([]) ->
+ ok;
+verify_connect_opts([{Key, _}|Opts]) when is_atom(Key) ->
+ verify_connect_opts(Opts);
+verify_connect_opts([H|_]) ->
+ error({bad_opts_list, H}).
+
+%% make_connect_retry_fun1() ->
+%% fun(Error, _) ->
+%% {false, Error}
+%% end.
+
+make_connect_retry_fun2() ->
+ fun(Error, noError) ->
+ Timeout = 250,
+ sleep(random(Timeout) + 100),
+ {true, {3, Timeout*2, Error}};
+ (_Error, {0, _Timeout, OriginalError}) ->
+ {false, OriginalError};
+ (_Error, {N, Timeout, OriginalError}) ->
+ sleep(random(Timeout) + 100),
+ {true, {N-1, Timeout*2, OriginalError}}
+ end.
+
+
+%% ----- instruction exececutor -----
+
+handle_exec({debug, Debug}, State) ->
+ p("debug: ~p", [Debug]),
+ put(debug, Debug),
+ {ok, State};
+
+handle_exec({expect_nothing, To}, State) ->
+ p("expect_nothing: ~p", [To]),
+ receive
+ Any ->
+ error({expect_nothing, Any})
+ after To ->
+ {ok, State}
+ end;
+
+handle_exec({megaco_trace, disable}, State) ->
+ p("megaco trace: disable"),
+ megaco:disable_trace(),
+ {ok, State};
+handle_exec({megaco_trace, Level}, State) ->
+ p("megaco trace: enable [~w]", [Level]),
+ megaco:enable_trace(Level, io),
+ {ok, State};
+
+handle_exec(megaco_start, State) ->
+ p("megaco_start"),
+ ok = megaco:start(),
+ {ok, State};
+
+handle_exec(megaco_stop, State) ->
+ p("megaco_stop"),
+ ok = megaco:stop(),
+ {ok, State};
+
+handle_exec({megaco_start_user, Mid, RecvInfo, Conf}, State) ->
+ p("megaco_start_user: ~p", [Mid]),
+
+ d("megaco_start_user -> start user"),
+ ok = megaco:start_user(Mid, Conf),
+
+ d("megaco_start_user -> update user info: user_mod"),
+ ok = megaco:update_user_info(Mid, user_mod, ?MODULE),
+
+ d("megaco_start_user -> update user info: user_args"),
+ ok = megaco:update_user_info(Mid, user_args, [self()]),
+
+ Port = get_config(port, RecvInfo),
+ EM = get_config(encoding_module, RecvInfo),
+ EC = get_config(encoding_config, RecvInfo),
+ TM = get_config(transport_module, RecvInfo),
+ RH0 = megaco:user_info(Mid, receive_handle),
+
+ RH1 = RH0#megaco_receive_handle{send_mod = TM,
+ encoding_mod = EM,
+ encoding_config = EC},
+
+ State1 = State#state{mid = Mid, recv_handle = RH1, port = Port},
+ {ok, State1};
+
+handle_exec(megaco_stop_user, #state{mid = Mid} = State)
+ when Mid /= undefined ->
+ megaco_cleanup(State),
+ ok = megaco:stop_user(Mid),
+ {ok, State#state{mid = undefined}};
+
+handle_exec(start_transport, #state{recv_handle = RH} = State) ->
+ p("start_transport"),
+ #megaco_receive_handle{send_mod = TM} = RH,
+ case (catch TM:start_transport()) of
+ {ok, Sup} ->
+ d("start_transport -> Sup: ~p", [Sup]),
+ {ok, State#state{transport_sup = Sup}};
+ {error, Reason} ->
+ e("failed starting transport (~w): "
+ "~n ~p", [TM, Reason]),
+ error({failed_starting_transport, TM, Reason});
+ Crap ->
+ e("failed starting transport (~w): "
+ "~n ~p", [TM, Crap]),
+ error({failed_starting_transport, TM, Crap})
+ end;
+
+handle_exec({listen, Opts0, MaybeRetry},
+ #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
+ p("listen(tcp)", []),
+ Opts = [{module, ?DELIVER_MOD},
+ {port, Port},
+ {receive_handle, RH},
+ {tcp_options, [{nodelay, true}]} | Opts0],
+ case (catch handle_exec_listen_tcp(Pid, Opts, MaybeRetry)) of
+ ok ->
+ {ok, State};
+ Else ->
+ error({tcp_listen_failed, Opts0, Else})
+ end;
+handle_exec({listen, Opts0, _MaybeRetry},
+ #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
+ p("listen(udp) - open"),
+ Opts = [{module, ?DELIVER_MOD}, {port, Port}, {receive_handle, RH}|Opts0],
+ case (catch megaco_udp:open(Pid, Opts)) of
+ {ok, _SH, _CtrlPid} ->
+ {ok, State};
+ Else ->
+ error({udp_open, Opts0, Else})
+ end;
+handle_exec({listen, Opts0, _MaybeRetry},
+ #state{recv_handle = RH, port = Port, transport_sup = Pid} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_test_generic_transport ->
+ p("listen(generic)"),
+ Opts = [{module, ?DELIVER_MOD}, {port, Port}, {receive_handle, RH}|Opts0],
+ case (catch megaco_test_generic_transport:listen(Pid, Opts)) of
+ {ok, _SH, _CtrlPid} ->
+ {ok, State};
+ Else ->
+ error({udp_open, Opts0, Else})
+ end;
+
+handle_exec({connect, Host, Opts0, MaybeRetry},
+ #state{transport_sup = Sup,
+ recv_handle = RH,
+ port = Port} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_tcp ->
+ p("connect[megaco_tcp] to ~p:~p", [Host, Port]),
+ PrelMid = preliminary_mid,
+ Opts = [{host, Host},
+ {port, Port},
+ {receive_handle, RH},
+ {tcp_options, [{nodelay, true}]} | Opts0],
+ case (catch handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry)) of
+ {ok, SH, ControlPid} ->
+ d("tcp connected: ~p, ~p", [SH, ControlPid]),
+ megaco_connector_start(RH, PrelMid, SH, ControlPid),
+ {ok, State#state{send_handle = SH,
+ ctrl_pid = ControlPid}};
+ Error ->
+ error({tcp_connect_failed, Host, Opts0, Error})
+ end;
+
+handle_exec({connect, Host, Opts0, _MaybeRetry},
+ #state{transport_sup = Sup,
+ recv_handle = RH,
+ port = Port} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_udp ->
+ p("connect[megaco_udp] to ~p", [Host]),
+ PrelMid = preliminary_mid,
+ Opts = [{port, 0}, {receive_handle, RH}|Opts0],
+ d("udp open", []),
+ case (catch megaco_udp:open(Sup, Opts)) of
+ {ok, Handle, ControlPid} ->
+ d("udp opened: ~p, ~p", [Handle, ControlPid]),
+ SH = megaco_udp:create_send_handle(Handle, Host, Port),
+ megaco_connector_start(RH, PrelMid, SH, ControlPid),
+ {ok, State#state{send_handle = SH,
+ ctrl_pid = ControlPid}};
+ Error ->
+ error({udp_connect_failed, Host, Opts0, Error})
+ end;
+
+handle_exec({connect, Host, Opts0, _MaybeRetry},
+ #state{transport_sup = Sup,
+ recv_handle = RH,
+ port = Port} = State)
+ when RH#megaco_receive_handle.send_mod =:= megaco_test_generic_transport ->
+ p("connect[megaco_test_generic_transport] to ~p", [Host]),
+ PrelMid = preliminary_mid,
+ Opts = [{host, Host}, {port, Port}, {receive_handle, RH}|Opts0],
+ case (catch megaco_test_generic_transport:connect(Sup, Opts)) of
+ {ok, SH, ControlPid} ->
+ d("generic connected: ~p, ~p", [SH, ControlPid]),
+ megaco_connector_start(RH, PrelMid, SH, ControlPid),
+ {ok, State#state{send_handle = SH,
+ ctrl_pid = ControlPid}};
+ Error ->
+ error({generic_connect_failed, Host, Opts0, Error})
+ end;
+
+handle_exec(megaco_connect, State) ->
+ p("megaco_connect"),
+ receive
+ {megaco_connect_result, {ok, CH}} ->
+ p("megaco connect succeeded: ~p", [CH]),
+ {ok, State#state{conn_handle = CH}};
+ {megaco_connect_result, Error} ->
+ p("megaco connect failed: ~p", [Error]),
+ #state{result = Res} = State,
+ {ok, State#state{result = [Error|Res]}}
+ end;
+
+handle_exec({megaco_connect, Mid},
+ #state{recv_handle = RH,
+ send_handle = SH,
+ ctrl_pid = ControlPid} = State) ->
+ p("megaco_connect: ~p", [Mid]),
+ megaco_connector_start(RH, Mid, SH, ControlPid),
+ {ok, State};
+
+handle_exec({megaco_user_info, Tag}, #state{mid = Mid, result = Res} = State)
+ when Mid /= undefined ->
+ p("megaco_user_info: ~w", [Tag]),
+ Val = (catch megaco:user_info(Mid, Tag)),
+ d("megaco_user_info: ~p", [Val]),
+ {ok, State#state{result = [Val|Res]}};
+
+handle_exec({megaco_update_user_info, Tag, Val}, #state{mid = Mid} = State)
+ when Mid /= undefined ->
+ p("megaco_update_user_info: ~w -> ~p", [Tag, Val]),
+ ok = megaco:update_user_info(Mid, Tag, Val),
+ {ok, State};
+
+handle_exec({megaco_conn_info, Tag},
+ #state{conn_handle = CH, result = Res} = State)
+ when CH /= undefined ->
+ p("megaco_conn_info: ~w", [Tag]),
+ Val = (catch megaco:conn_info(CH, Tag)),
+ d("megaco_conn_info: ~p", [Val]),
+ {ok, State#state{result = [Val|Res]}};
+
+handle_exec({megaco_update_conn_info, Tag, Val},
+ #state{conn_handle = CH} = State)
+ when CH /= undefined ->
+ p("megaco_update_conn_info: ~w -> ~p", [Tag, Val]),
+ case megaco:update_conn_info(CH, Tag, Val) of
+ ok ->
+ {ok, State};
+ Error ->
+ error({failed_updating_conn_info, Tag, Val, Error})
+ end;
+
+handle_exec(megaco_info, #state{result = Res} = State) ->
+ p("megaco_info", []),
+ Val = (catch megaco:info()),
+ d("megaco_info: ~p", [Val]),
+ {ok, State#state{result = [Val|Res]}};
+
+handle_exec({megaco_system_info, Tag, Verify}, #state{result = Res} = State) ->
+ p("megaco_system_info: ~w", [Tag]),
+ Val = (catch megaco:system_info(Tag)),
+ d("megaco_system_info: ~p", [Val]),
+ case Verify(Val) of
+ ok ->
+ {ok, State#state{result = [Val|Res]}};
+ Error ->
+ {error, State#state{result = [Error|Res]}}
+ end;
+
+%% This is either a MG or a MGC which is only connected to one MG
+handle_exec({megaco_call, ARs, Opts}, #state{conn_handle = CH} = State)
+ when CH /= undefined ->
+ p("megaco_call"),
+ {_PV, UserReply} = megaco:call(CH, ARs, Opts),
+ d("megaco_call -> UserReply: ~n~p", [UserReply]),
+ {ok, State};
+
+handle_exec({megaco_call, RemoteMid, ARs, Opts}, #state{mid = Mid} = State) ->
+ p("megaco_call: ~p", [RemoteMid]),
+ %% First we have to find the CH for this Mid
+ Conns = megaco:user_info(Mid, connections),
+ {value, {_, CH}} =
+ lists:keysearch(RemoteMid, #megaco_conn_handle.remote_mid, Conns),
+ {_PV, UserReply} = megaco:call(CH, ARs, Opts),
+ d("megaco_call -> UserReply: ~n~p", [UserReply]),
+ {ok, State};
+
+%% This is either a MG or a MGC which is only connected to one MG
+handle_exec({megaco_cast, ARs, Opts}, #state{conn_handle = CH} = State)
+ when CH =/= undefined ->
+ p("megaco_cast"),
+ case megaco:cast(CH, ARs, Opts) of
+ ok ->
+ {ok, State};
+ Error ->
+ d("failed sending (cast) message: ~n~p", [Error]),
+ #state{result = Acc} = State,
+ {error, State#state{result = [Error|Acc]}}
+ end;
+
+handle_exec({megaco_cast, RemoteMid, ARs, Opts}, #state{mid = Mid} = State) ->
+ p("megaco_cast: ~p", [RemoteMid]),
+ %% First we have to find the CH for this Mid
+ Conns = megaco:user_info(Mid, connections),
+ {value, {_, CH}} =
+ lists:keysearch(RemoteMid, #megaco_conn_handle.remote_mid, Conns),
+ case megaco:cast(CH, ARs, Opts) of
+ ok ->
+ {ok, State};
+ Error ->
+ d("failed sending (cast) message: ~n~p", [Error]),
+ #state{result = Acc} = State,
+ {error, State#state{result = [Error|Acc]}}
+ end;
+
+%% Nothing shall happen for atleast Timeout time
+handle_exec({megaco_callback, nocall, Timeout}, State) ->
+ p("megaco_callback [~w,~w]", [nocall, Timeout]),
+ receive
+ {handle_megaco_callback, Type, Msg, Pid} ->
+ d("received unexpected megaco callback: ~n~p", [Msg]),
+ #state{result = Res} = State,
+ Err = {unexpected_callback, Type, Msg, Pid},
+ {error, State#state{result = [Err|Res]}}
+ after Timeout ->
+ {ok, State}
+ end;
+
+handle_exec({megaco_callback, Tag, Verify}, State) when is_function(Verify) ->
+ p("megaco_callback [~w]", [Tag]),
+ receive
+ {handle_megaco_callback, Type, Msg, Pid} ->
+ d("received megaco callback: ~n~p", [Msg]),
+ case Verify(Msg) of
+ {VRes, Res, Reply} ->
+ d("megaco_callback [~w] ~w",[Tag, VRes]),
+ handle_megaco_callback_reply(Pid, Type, Reply),
+ validate(VRes, Tag, Res, State);
+ {VRes, Delay, Res, Reply} ->
+ d("megaco_callback [~w] ~w, ~w",[Tag,Delay,VRes]),
+ handle_megaco_callback_reply(Pid, Type, Delay, Reply),
+ validate(VRes, Tag, Res, State)
+ end
+ end;
+
+handle_exec({megaco_callback, Tag, {VMod, VFunc, VArgs}}, State)
+ when is_atom(VMod) andalso is_atom(VFunc) andalso is_list(VArgs) ->
+ p("megaco_callback [~w]", [Tag]),
+ receive
+ {handle_megaco_callback, Type, Msg, Pid} ->
+ d("received megaco callback: ~n~p"
+ "~n VMod: ~w"
+ "~n VFunc: ~w"
+ "~n VArgs: ~p", [Msg, VMod, VFunc, VArgs]),
+ case apply(VMod, VFunc, [Msg|VArgs]) of
+ {VRes, Res, Reply} ->
+ d("megaco_callback [~w] ~w",[Tag, VRes]),
+ handle_megaco_callback_reply(Pid, Type, Reply),
+ validate(VRes, Tag, Res, State);
+ {VRes, Delay, Res, Reply} ->
+ d("megaco_callback [~w] ~w, ~w",[Tag,Delay,VRes]),
+ handle_megaco_callback_reply(Pid, Type, Delay, Reply),
+ validate(VRes, Tag, Res, State)
+ end
+ end;
+
+handle_exec({megaco_callback, Tag, Verify, Timeout}, State)
+ when (is_function(Verify) andalso
+ (is_integer(Timeout) andalso (Timeout > 0))) ->
+ p("megaco_callback [~w]", [Tag]),
+ receive
+ {handle_megaco_callback, Type, Msg, Pid} ->
+ d("received megaco callback: ~n~p", [Msg]),
+ case Verify(Msg) of
+ {VRes, Res, Reply} ->
+ d("megaco_callback [~w] ~w",[Tag,VRes]),
+ handle_megaco_callback_reply(Pid, Type, Reply),
+ validate(VRes, Tag, Res, State);
+ {VRes, Delay, Res, Reply} ->
+ d("megaco_callback [~w] ~w, ~w",[Tag,Delay,VRes]),
+ handle_megaco_callback_reply(Pid, Type, Delay, Reply),
+ validate(VRes, Tag, Res, State)
+ end
+ after Timeout ->
+ #state{result = Res} = State,
+ Err = {callback_timeout, Tag, Timeout},
+ {error, State#state{result = [Err|Res]}}
+ end;
+
+handle_exec({megaco_callback, Verifiers}, State) ->
+ p("megaco_callback"),
+ megaco_callback_verify(Verifiers, State);
+
+handle_exec({megaco_cancel, Reason}, #state{conn_handle = CH} = State) ->
+ p("megaco_cancel [~w]", [Reason]),
+ case megaco:cancel(CH, Reason) of
+ ok ->
+ {ok, State};
+ Error ->
+ d("failed cancel: ~n~p", [Error]),
+ #state{result = Acc} = State,
+ {error, State#state{result = [Error|Acc]}}
+ end;
+
+handle_exec({trigger, Trigger}, State) when is_function(Trigger) ->
+ p("trigger"),
+ (catch Trigger()),
+ {ok, State};
+
+handle_exec({sleep, To}, State) ->
+ p("sleep ~p", [To]),
+ megaco_test_generator:sleep(To),
+ {ok, State};
+
+handle_exec(BadInstruction, _State) ->
+ error({invalid_instruction, BadInstruction}).
+
+
+%% --- cleanup ---
+
+megaco_cleanup(#state{mid = Mid}) ->
+ Close = fun(CH) -> do_megaco_cleanup(CH) end,
+ Conns =
+ case (catch megaco:user_info(Mid, connections)) of
+ Connections when is_list(Connections) ->
+ Connections;
+ _ ->
+ []
+ end,
+ lists:foreach(Close, Conns).
+
+do_megaco_cleanup(CH) ->
+ case (catch do_megaco_cleanup2(CH)) of
+ ok ->
+ ok;
+ {'EXIT', {no_such_connection, _}} ->
+ ok;
+ {'EXIT', Reason} ->
+ exit(Reason)
+ end.
+
+do_megaco_cleanup2(CH) ->
+ d("do_megaco_cleanup2 -> entry with"
+ "~n CH: ~p", [CH]),
+ Reason = {stopped_by_user,self()},
+ Pid = megaco:conn_info(CH, control_pid),
+ SendMod = megaco:conn_info(CH, send_mod),
+ SendHandle = megaco:conn_info(CH, send_handle),
+ d("do_megaco_cleanup2 -> disconnect"),
+ megaco:disconnect(CH, Reason),
+ d("do_megaco_cleanup2 -> disconnected, now cancel"),
+ megaco:cancel(CH, Reason),
+ d("do_megaco_cleanup2 -> canceled, now close"),
+ case SendMod of
+ megaco_tcp -> (catch megaco_tcp:close(SendHandle));
+ megaco_udp -> (catch megaco_udp:close(SendHandle));
+ SendMod -> exit(Pid, Reason)
+ end,
+ ok.
+
+
+%% --- connector ---
+
+megaco_connector_start(RH, PrelMid, SH, ControlPid) ->
+ Self = self(),
+ Fun = fun() -> megaco_connect(RH, PrelMid, SH, ControlPid, Self) end,
+ erlang:spawn_opt(Fun, [link]).
+
+megaco_connect(RH, PrelMid, SH, ControlPid, Parent) ->
+ Result = megaco:connect(RH, PrelMid, SH, ControlPid),
+ Parent ! {megaco_connect_result, Result},
+ exit(normal).
+
+
+%% --- megaco callback verify ---
+
+%% This is used when a number of callback's is expected, but where
+%% the specific order is unknown.
+megaco_callback_verify([], State) ->
+ d("megaco_callback_verify -> done"),
+ {ok, State};
+megaco_callback_verify(Verifiers0, State0) ->
+ d("megaco_callback_verify -> entry when"
+ "~n length(Verifiers0): ~w", [length(Verifiers0)]),
+ receive
+ {handle_megaco_callback, Type, Msg, Pid} ->
+ d("megaco_callback_verify -> received megaco callback: ~w"
+ "~n Msg: ~p", [Type, Msg]),
+ case megaco_callback_verify(Verifiers0, Type, Msg, Pid, State0) of
+ {ok, Verifiers, State} ->
+ megaco_callback_verify(Verifiers, State);
+ Error ->
+ Error
+ end
+ end.
+
+megaco_callback_verify(Verifiers0, Type, Msg, Pid, State0) ->
+ d("megaco_callback_verify -> entry"),
+ Tag = element(1, Msg),
+ d("megaco_callback_verify -> Tag: ~w", [Tag]),
+ case lists:keysearch(Tag, 1, Verifiers0) of
+ {value, {Tag, N, Verify}} when (N > 0) andalso is_function(Verify) ->
+ d("megaco_callback_verify -> N: ~w",[N]),
+ case Verify(Msg) of
+ {VRes, Res, Reply} ->
+ d("megaco_callback_verify -> VRes: ~w",[VRes]),
+ handle_megaco_callback_reply(Pid, Type, Reply),
+ case validate(VRes, Tag, Res, State0) of
+ {error, _} = EState ->
+ e("megaco_callback_verify -> (1) error", []),
+ throw(EState);
+ {ok, State} when N > 1 ->
+ d("megaco_callback_verify -> (1) validated"),
+ Rec = {Tag, N-1, Verify},
+ Verifiers =
+ lists:keyreplace(Tag, 1, Verifiers0, Rec),
+ {ok, Verifiers, State};
+ {ok, State} ->
+ d("megaco_callback_verify -> (2) validated"),
+ Verifiers = lists:keydelete(Tag, 1, Verifiers0),
+ {ok, Verifiers, State}
+ end;
+ {VRes, Delay, Res, Reply} ->
+ d("megaco_callback_verify -> Delay: ~w, VRes: ~w",
+ [Delay,VRes]),
+ handle_megaco_callback_reply(Pid, Type, Delay, Reply),
+ case validate(VRes, Tag, Res, State0) of
+ {error, _} = EState ->
+ e("megaco_callback_verify -> (2) error", []),
+ throw(EState);
+ {ok, State} when N > 1 ->
+ d("megaco_callback_verify -> (3) validated"),
+ Rec = {Tag, N-1, Verify},
+ Verifiers =
+ lists:keyreplace(Tag, 1, Verifiers0, Rec),
+ {ok, Verifiers, State};
+ {ok, State} ->
+ d("megaco_callback_verify -> (4) validated"),
+ Verifiers = lists:keydelete(Tag, 1, Verifiers0),
+ {ok, Verifiers, State}
+ end
+ end;
+ false ->
+ e("megaco_callback_verify -> no such tag ~w~n~p",
+ [Tag, Verifiers0]),
+ #state{result = Res} = State0,
+ State = State0#state{result = [{Type, error, Msg}|Res]},
+ error(State)
+ end.
+
+
+%% --- validate verify result ---
+
+validate(ok, handle_connect = Tag, CH, #state{result = Acc} = S) ->
+ {ok, S#state{conn_handle = CH, result = [{Tag, ok, CH}|Acc]}};
+validate(ok, Tag, Res, #state{result = Acc} = S) ->
+ {ok, S#state{result = [{Tag, ok, Res}|Acc]}};
+validate(error, Tag, Res, #state{result = Acc} = S) ->
+ {error, S#state{result = [{Tag, error, Res}|Acc]}}.
+
+
+%% ----- termination -----
+
+terminate(normal, #state{result = Result} = _State) ->
+ d("terminate -> entry when normal with"
+ "~n Result: ~p", [Result]),
+ %% megaco_cleanup(State),
+ {ok, Result};
+
+terminate(Reason, #state{result = Result} = State) ->
+ d("terminate -> entry with"
+ "~n Reason: ~p"
+ "~n Result: ~p", [Reason, Result]),
+ megaco_cleanup(State),
+ {error, {Reason, Result}}.
+
+
+%%----------------------------------------------------------------------
+
+handle_exec_listen_tcp(Sup, Opts, MaybeRetry) ->
+ handle_exec_listen_tcp(Sup, Opts, MaybeRetry, noError).
+
+handle_exec_listen_tcp(Sup, Opts, MaybeRetry, Error0) ->
+ case (catch megaco_tcp:listen(Sup, Opts)) of
+ ok ->
+ ok;
+ Error1 ->
+ case (catch MaybeRetry(Error1, Error0)) of
+ {true, Error2} ->
+ handle_exec_listen_tcp(Sup, Opts, MaybeRetry, Error2);
+ {false, Error3} ->
+ {error, Error3}
+ end
+ end.
+
+
+handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry)
+ when is_function(MaybeRetry) ->
+ handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry, noError).
+
+handle_exec_connect_tcp(Host, Opts, Sup, MaybeRetry, Error0) ->
+ case (catch megaco_tcp:connect(Sup, Opts)) of
+ {ok, SH, ControlPid} ->
+ d("tcp connected: ~p, ~p", [SH, ControlPid]),
+ {ok, SH, ControlPid};
+ Error1 ->
+ case (catch MaybeRetry(Error1, Error0)) of
+ {true, Error2} ->
+ handle_exec_connect_tcp(Host, Opts, Sup,
+ MaybeRetry, Error2);
+ {false, Error3} ->
+ {error, Error3}
+ end
+ end.
+
+
+
+%%----------------------------------------------------------------------
+%% megaco_user callback functions
+%%----------------------------------------------------------------------
+
+handle_connect(CH, PV, P) ->
+ Req = {handle_connect, CH, PV},
+ handle_megaco_callback_call(P, Req).
+
+handle_connect(CH, PV, Extra, P) ->
+ Req = {handle_connect, CH, PV, Extra},
+ handle_megaco_callback_call(P, Req).
+
+handle_disconnect(CH, PV, R, P) ->
+ Msg = {handle_disconnect, CH, PV, R},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_syntax_error(RH, PV, ED, P) ->
+ Req = {handle_syntax_error, RH, PV, ED},
+ handle_megaco_callback_call(P, Req).
+
+handle_syntax_error(RH, PV, ED, Extra, P) ->
+ Req = {handle_syntax_error, RH, PV, ED, Extra},
+ handle_megaco_callback_call(P, Req).
+
+handle_message_error(CH, PV, ED, P) ->
+ Msg = {handle_message_error, CH, PV, ED},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_message_error(CH, PV, ED, Extra, P) ->
+ Msg = {handle_message_error, CH, PV, ED, Extra},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_request(CH, PV, AR, P) ->
+ Req = {handle_trans_request, CH, PV, AR},
+ handle_megaco_callback_call(P, Req).
+
+handle_trans_request(CH, PV, AR, Extra, P) ->
+ Req = {handle_trans_request, CH, PV, AR, Extra},
+ handle_megaco_callback_call(P, Req).
+
+handle_trans_long_request(CH, PV, RD, P) ->
+ Req = {handle_trans_long_request, CH, PV, RD},
+ handle_megaco_callback_call(P, Req).
+
+handle_trans_long_request(CH, PV, RD, Extra, P) ->
+ Req = {handle_trans_long_request, CH, PV, RD, Extra},
+ handle_megaco_callback_call(P, Req).
+
+handle_trans_reply(CH, PV, AR, RD, P) ->
+ Msg = {handle_trans_reply, CH, PV, AR, RD},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_reply(CH, PV, AR, RD, Extra, P) ->
+ Msg = {handle_trans_reply, CH, PV, AR, RD, Extra},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_ack(CH, PV, AS, AD, P) ->
+ Msg = {handle_trans_ack, CH, PV, AS, AD},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_ack(CH, PV, AS, AD, Extra, P) ->
+ Msg = {handle_trans_ack, CH, PV, AS, AD, Extra},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_unexpected_trans(CH, PV, T, P) ->
+ Msg = {handle_unexpected_trans, CH, PV, T},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_unexpected_trans(CH, PV, T, Extra, P) ->
+ Msg = {handle_unexpected_trans, CH, PV, T, Extra},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_request_abort(RH, PV, TransNo, Pid, P) ->
+ Msg = {handle_trans_request_abort, RH, PV, TransNo, Pid},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_trans_request_abort(RH, PV, TransNo, Pid, Extra, P) ->
+ Msg = {handle_trans_request_abort, RH, PV, TransNo, Pid, Extra},
+ Reply = ok,
+ handle_megaco_callback_cast(P, Msg, Reply).
+
+handle_megaco_callback_cast(P, Msg, Reply) ->
+ p("handle_megaco_callback_cast -> entry with Msg: ~n~p", [Msg]),
+ P ! {handle_megaco_callback, cast, Msg, self()},
+ Reply.
+
+handle_megaco_callback_call(P, Msg) ->
+ p("handle_megaco_callback_call -> entry with"
+ "~n P: ~p"
+ "~n Msg: ~p", [P, Msg]),
+ P ! {handle_megaco_callback, call, Msg, self()},
+ receive
+ {handle_megaco_callback_reply, Reply} ->
+ p("handle_megaco_callback_call -> received reply: ~n~p", [Reply]),
+ Reply;
+ {handle_megaco_callback_reply, Delay, Reply} when is_integer(Delay) ->
+ p("handle_megaco_callback_call -> "
+ "received reply [~w]: "
+ "~n ~p", [Delay, Reply]),
+ sleep(Delay),
+ p("handle_megaco_callback_call -> deliver reply after delay [~w]",
+ [Delay]),
+ Reply;
+ {'EXIT', SomePid, SomeReason} ->
+ p("handle_megaco_callback_call -> "
+ "received unexpected EXIT signal: "
+ "~n SomePid: ~p"
+ "~n SomeReason: ~p", [SomePid, SomeReason]),
+ exit({unexpected_EXIT_signal, SomePid, SomeReason})
+ end.
+
+
+handle_megaco_callback_reply(P, call, Reply) ->
+ P ! {handle_megaco_callback_reply, Reply};
+handle_megaco_callback_reply(_, _, _) ->
+ ok.
+
+handle_megaco_callback_reply(P, call, Delay, Reply) ->
+ P ! {handle_megaco_callback_reply, Delay, Reply};
+handle_megaco_callback_reply(_, _, _, _) ->
+ ok.
+
+
+%%----------------------------------------------------------------------
+%% internal utility functions
+%%----------------------------------------------------------------------
+
+random_init() ->
+ {A,B,C} = now(),
+ random:seed(A,B,C).
+
+random(N) ->
+ random:uniform(N).
+
+
+get_config(Key, Opts) ->
+ {value, {Key, Val}} = lists:keysearch(Key, 1, Opts),
+ Val.
+
+sleep(X) -> megaco_test_generator:sleep(X).
+
+d(F) -> megaco_test_generator:debug(F).
+d(F, A) -> megaco_test_generator:debug(F, A).
+
+e(F, A) -> megaco_test_generator:error(F, A).
+
+p(F ) -> p("", F, []).
+p(F, A) -> p("", F, A).
+p(P, F, A) -> megaco_test_generator:print(P, F, A).
+
+error(Reason) ->
+ throw({error, Reason}).
+