diff options
Diffstat (limited to 'lib/megaco/test/megaco_test_generator.erl')
-rw-r--r-- | lib/megaco/test/megaco_test_generator.erl | 549 |
1 files changed, 549 insertions, 0 deletions
diff --git a/lib/megaco/test/megaco_test_generator.erl b/lib/megaco/test/megaco_test_generator.erl new file mode 100644 index 0000000000..8bbc60e6cd --- /dev/null +++ b/lib/megaco/test/megaco_test_generator.erl @@ -0,0 +1,549 @@ +%% +%% %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: Sequence generator for the megaco test suite +%%---------------------------------------------------------------------- + +-module(megaco_test_generator). + +-behaviour(gen_server). + +-export([ + start_link/3, + start_link/4, + exec/2, exec/3, + stop/1 + ]). + +%% Misc utility function for modules implementing this behaviour +-export([ + sleep/1, + sz/1, + debug/1, debug/2, + error/2, + print/3, print/4 + ]). + +-export([behaviour_info/1]). + +%% Internal exports +-export([start/4]). +-export([handler_init/5]). + +%% Internal gen_server exports +-export([ + init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + terminate/2, + code_change/3 + ]). + + +-include_lib("megaco/include/megaco.hrl"). + + +%%---------------------------------------------------------------------- + +-define(TIMEOUT, timer:minutes(5)). + + +%%---------------------------------------------------------------------- + +-record(state, + { + parent, + callback_module, + callback_state, + handler = {undefined, undefined}, + timer, + name, + id + }). + + +%%%========================================================================= +%%% API +%%%========================================================================= + +behaviour_info(callbacks) -> + [ + {init, 1}, + {handle_parse, 2}, + {handle_exec, 2}, + {terminate, 2} + ]; +behaviour_info(_Other) -> + undefined. + + +%%---------------------------------------------------------------------- + +start_link(Mod, Args, Name) + when is_atom(Mod) andalso is_list(Name) -> + start(Mod, Args, Name, self()). + +start_link(Mod, Args, Name, Node) + when is_atom(Mod) andalso is_list(Name) andalso (Node =/= node()) -> + case rpc:call(Node, ?MODULE, start, [Mod, Args, Name, self()]) of + {ok, Pid} -> + link(Pid), + {ok, Pid}; + Error -> + Error + end; +start_link(Mod, Args, Name, Node) + when is_atom(Mod) andalso is_list(Name) andalso (Node =:= node()) -> + case start(Mod, Args, Name, self()) of + {ok, Pid} -> + link(Pid), + {ok, Pid}; + Error -> + Error + end. + +start(Mod, Args, Name, Pid) when is_pid(Pid) -> + gen_server:start({local, Mod}, ?MODULE, [Mod, Args, Name, Pid], []). + + +exec(Server, Instructions) -> + exec(Server, Instructions, infinity). + +exec(Server, Instructions, Timeout) + when ((Timeout == infinity) orelse + (is_integer(Timeout) andalso (Timeout > 0))) -> + call(Server, {exec, Instructions, Timeout}). + + +stop(Server) -> + call(Server, stop). + + +%%---------------------------------------------------------------------- + +%%-------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, State} | +%% {ok, State, Timeout} | +%% ignore | +%% {stop, Reason} +%%-------------------------------------------------------------------- + +init([Mod, Args, Name, Parent]) -> + put(name, Name ++ "-CTRL"), + process_flag(trap_exit, true), + put(debug, true), + d("init -> entry with" + "~n Mod: ~p" + "~n Args: ~p" + "~n Name: ~p" + "~n Parent: ~p", [Mod, Args, Name, Parent]), + case (catch Mod:init(Args)) of + {ok, CallbackState} -> + d("init -> ~p initiated:" + "~n CallbackState: ~p", [Mod, CallbackState]), + State = #state{callback_module = Mod, + callback_state = CallbackState, + parent = Parent, + name = Name}, + d("init -> initiated"), + {ok, State}; + {error, Reason} -> + {stop, Reason} + end. + + +%%-------------------------------------------------------------------- +%% Func: handle_call/3 +%% Returns: {reply, Reply, State} | +%% {reply, Reply, State, Timeout} | +%% {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, Reply, State} | (terminate/2 is called) +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_call({exec, Instructions, Timeout}, _From, + #state{callback_module = Mod, + callback_state = CallbackState, + name = Name} = State) -> + d("handle_call(exec) -> entry with" + "~n Timeout: ~p", [Timeout]), + case (catch handle_parse(Mod, CallbackState, Instructions)) of + {ok, NewCallbackState, NewInstructions} -> + d("handle_call(exec) -> parsed" + "~n NewCallbackState: ~p", [NewCallbackState]), + case handler_start(Name, Mod, NewCallbackState, NewInstructions) of + {ok, Pid} -> + d("handle_call(exec) -> handler started" + "~n Pid: ~p", [Pid]), + Timer = maybe_start_timer(Timeout), + Id = {node(), make_ref()}, + Reply = {ok, Id}, + {reply, Reply, + State#state{callback_state = NewCallbackState, + handler = {running, Pid}, + timer = Timer, + id = Id}}; + {error, Reason} -> + e("failed starting handler process" + "~n Reason: ~p", [Reason]), + Reply = {error, {failed_starting_handler, Reason}}, + {stop, Reason, Reply, State} + end; + {error, Reason} -> + e("failed parsing instructions" + "~n Reason: ~p", [Reason]), + Reply = {error, {invalid_instruction, Reason}}, + {stop, Reason, Reply, State} + end; + +handle_call(stop, _From, State) -> + Reply = ok, + {stop, normal, Reply, State}; + +handle_call(Request, From, State) -> + e("unexpected request" + "~n Request: ~p" + "~n From: ~p", [Request, From]), + Reason = {error, {unknown_request, Request, From}}, + Reply = {error, unknown_request}, + {stop, Reason, Reply, State}. + + +%%-------------------------------------------------------------------- +%% Func: handle_cast/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_cast(Msg, State) -> + e("unexpected message" + "~n Msg: ~p", [Msg]), + Reason = {error, {unknown_message, Msg}}, + {stop, Reason, State}. + + +%%-------------------------------------------------------------------- +%% Func: handle_info/2 +%% Returns: {noreply, State} | +%% {noreply, State, Timeout} | +%% {stop, Reason, State} (terminate/2 is called) +%%-------------------------------------------------------------------- +handle_info({handler_result, Pid, Result}, + #state{parent = Parent, + handler = {running, Pid}, + timer = Timer, + id = Id} = State) -> + d("handle_info(handler_result) -> entry with" + "~n Result: ~p", [Result]), + maybe_stop_timer(Timer), + handler_stop(Pid), + deliver_exec_result(Parent, Id, Result), + NewState = State#state{handler = {stopping, Pid}, + timer = undefined, + id = undefined}, + {noreply, NewState}; + +handle_info(handler_timeout, #state{handler = {running, Pid}} = State) -> + d("handle_info(handler_timeout) -> entry with"), + handler_stop(Pid), + {noreply, State#state{handler = {stopping, Pid}}}; + +handle_info({'EXIT', Pid, {stopped, Result}}, + #state{parent = Parent, + handler = {stopping, Pid}, + id = Id} = State) -> + d("handle_info(handler stopped EXIT) -> entry with" + "~n Result: ~p", [Result]), + deliver_exec_result(Parent, Id, {error, {handler_timeout, Result}}), + {noreply, State#state{handler = {stopped, undefined}, + timer = undefined, + id = undefined}}; + +handle_info({'EXIT', Pid, normal}, + #state{handler = {_, Pid}, + timer = Timer} = State) -> + d("handle_info(handler normal EXIT) -> entry"), + maybe_stop_timer(Timer), + {noreply, State#state{handler = {stopped, undefined}, timer = undefined}}; + +handle_info({'EXIT', Pid, Reason}, + #state{parent = Parent, + handler = {_, Pid}, + timer = Timer, + id = Id} = State) -> + d("handle_info(handler EXIT) -> entry with" + "~n Reason: ~p", [Reason]), + maybe_stop_timer(Timer), + deliver_exec_result(Parent, Id, {error, {handler_crashed, Reason}}), + {noreply, State#state{handler = {crashed, undefined}, + timer = undefined, + id = undefined}}; + +handle_info(Info, State) -> + e("unexpected info" + "~n Info: ~p" + "~n State: ~p", [Info, State]), + Reason = {error, {unknown_info, Info}}, + {stop, Reason, State}. + + +%%-------------------------------------------------------------------- +%% Func: terminate/2 +%% Purpose: Shutdown the server +%% Returns: any (ignored by gen_server) +%%-------------------------------------------------------------------- +terminate(normal, #state{handler = {_HandlerState, Pid}} = _State) -> + d("terminate(normal) -> entry"), + handler_stop(Pid), + ok; + +terminate(Reason, #state{handler = {_HandlerState, Pid}, + callback_module = Mod, + callback_state = CallbackState} = _State) -> + d("terminate -> entry with" + "~n Reason: ~p", [Reason]), + handler_kill(Pid), + (catch Mod:terminate(Reason, CallbackState)), + ok. + + +%%---------------------------------------------------------------------- +%% Func: code_change/3 +%% Purpose: Convert process state when code is changed +%% Returns: {ok, NewState} +%%---------------------------------------------------------------------- + +code_change(_Vsn, S, _Extra) -> + {ok, S}. + + +%%%------------------------------------------------------------------- +%%% Internal functions +%%%------------------------------------------------------------------- + +deliver_exec_result(Parent, Id, {ok, Result}) -> + Parent ! {exec_complete, Id, ok, Result}; +deliver_exec_result(Parent, Id, {error, Reason}) -> + Parent ! {exec_complete, Id, error, Reason}. + + +handle_parse(Mod, State, Instructions) -> + handle_parse(Mod, State, Instructions, []). + +handle_parse(_Mod, State, [], Acc) -> + {ok, State, lists:reverse(Acc)}; + +handle_parse(Mod, State, [Instruction|Instructions], Acc) -> + case (catch Mod:handle_parse(Instruction, State)) of + {ok, NewInstruction, NewState} -> + handle_parse(Mod, NewState, Instructions, [NewInstruction|Acc]); + {error, Reason} -> + {error, {invalid_instruction, Instruction, Reason}}; + {'EXIT', Reason} -> + {error, {exit, Instruction, Reason}} + end. + + +%%%------------------------------------------------------------------- + +handler_kill(Pid) when is_pid(Pid) -> + erlang:exit(Pid, kill); +handler_kill(_) -> + ok. + +handler_stop(Pid) when is_pid(Pid) -> + Pid ! {stop, self()}; +handler_stop(_) -> + ok. + +handler_start(Name, Mod, State, Instructions) -> + Args = [Name, self(), Mod, State, Instructions], + proc_lib:start_link(?MODULE, handler_init, Args). + + +handler_init(Name, Parent, Mod, State, Instructions) -> + put(name, Name ++ "-HANDLER"), + proc_lib:init_ack(Parent, {ok, self()}), + d("handler_init -> initiated"), + handler_main(Parent, Mod, State, Instructions). + +handler_main(Parent, Mod, State, []) -> + d("handler_main -> done when" + "~n State: ~p", [State]), + Result = (catch Mod:terminate(normal, State)), + Parent ! {handler_result, self(), {ok, Result}}, + receive + {stop, Parent} -> + exit(normal); + {'EXIT', Parent, Reason} -> + exit({parent_died, Reason}) + end; + +handler_main(Parent, Mod, State, [Instruction|Instructions]) -> + d("handler_main -> entry with" + "~n Instruction: ~p", [Instruction]), + receive + {stop, Parent} -> + d("handler_main -> premature stop requested"), + Result = (catch Mod:terminate(stopped, State)), + exit({stopped, Result}); + {'EXIT', Parent, Reason} -> + d("handler_main -> parent exited" + "~n Reason: ~p", [Reason]), + Result = (catch Mod:terminate({parent_died, Reason}, State)), + exit({parent_died, Reason, Result}) + after 0 -> + case (catch handler_callback_exec(Mod, State, Instruction)) of + {ok, NewState} -> + handler_main(Parent, Mod, NewState, Instructions); + {error, Reason} -> + d("handler_main -> exec failed" + "~n Reason: ~p", [Reason]), + case (catch Mod:terminate(normal, State)) of + {ok, Result} -> + Parent ! {handler_result, self(), {error, Result}}; + Error -> + Result = {bad_terminate, Error}, + Parent ! {handler_result, self(), {error, Result}} + end, + receive + {stop, Parent} -> + exit(normal); + {'EXIT', Parent, Reason} -> + exit({parent_died, Reason}) + end; + {'EXIT', Reason} -> + d("handler_main -> exec EXIT" + "~n Reason: ~p", [Reason]), + exit({callback_exec_exit, Reason}) + + end + end. + +handler_callback_exec(Mod, State, Instruction) -> + Mod:handle_exec(Instruction, State). + + +%%%------------------------------------------------------------------- + +maybe_start_timer(Timeout) when is_integer(Timeout) -> + erlang:send_after(Timeout, self(), handler_timeout); +maybe_start_timer(_) -> + undefined. + + +maybe_stop_timer(undefined) -> + ok; +maybe_stop_timer(Timer) -> + (catch erlang:cancel_timer(Timer)). + + +%%% ---------------------------------------------------------------- + +call(Server, Request) -> + call(Server, Request, infinity). + +call(Server, Request, Timeout) -> + case (catch gen_server:call(Server, Request, Timeout)) of + {'EXIT', _} -> + {error, not_started}; + Res -> + Res + end. + +%% cast(Server, Msg) -> +%% case (catch gen_server:cast(Server, Msg)) of +%% {'EXIT', _} -> +%% {error, not_started}; +%% Res -> +%% Res +%% end. + + +%%% ---------------------------------------------------------------- + +sleep(X) when is_integer(X) andalso (X =< 0) -> ok; +sleep(X) -> receive after X -> ok end. + +sz(Bin) when is_binary(Bin) -> + size(Bin); +sz(L) when is_list(L) -> + length(L); +sz(_) -> + -1. + + +%%% ---------------------------------------------------------------- + +d(F) -> debug(F). +d(F, A) -> debug(F, A). + +e(F, A) -> error(F, A). + +%% p(P, F, A) -> print(P, F, A). +%% p(P, N, F, A) -> print(P, N, F, A). + + +%% ------------------------- + +debug(F) -> + debug(F, []). + +debug(F, A) -> + debug(get(debug), F, A). + +debug(true, F, A) -> + print(" DBG", F, A); +debug(_, _F, _A) -> + ok. + + +error(F, A) -> + print(" ERROR", F, A). + + +print(P, F, A) -> + print(P, get(name), F, A). + +print([], undefined, F, A) -> + io:format("*** [~s] ~p *** " ++ + "~n " ++ F ++ "~n", + [format_timestamp(now()),self()|A]); +print(P, undefined, F, A) -> + io:format("*** [~s] ~p ~s *** " ++ + "~n " ++ F ++ "~n", + [format_timestamp(now()),self(),P|A]); +print(P, N, F, A) -> + io:format("*** [~s] ~p ~s~s *** " ++ + "~n " ++ F ++ "~n", + [format_timestamp(now()),self(),N,P|A]). + + +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). |