1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2007-2020. All Rights Reserved.
5%%
6%% Licensed under the Apache License, Version 2.0 (the "License");
7%% you may not use this file except in compliance with the License.
8%% You may obtain a copy of the License at
9%%
10%%     http://www.apache.org/licenses/LICENSE-2.0
11%%
12%% Unless required by applicable law or agreed to in writing, software
13%% distributed under the License is distributed on an "AS IS" BASIS,
14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15%% See the License for the specific language governing permissions and
16%% limitations under the License.
17%%
18%% %CopyrightEnd%
19%%
20
21%%
22%%----------------------------------------------------------------------
23%% Purpose: Sequence generator for the megaco test suite
24%%----------------------------------------------------------------------
25
26-module(megaco_test_generator).
27
28-behaviour(gen_server).
29
30-compile({no_auto_import,[error/2]}).
31
32%% ----
33
34-export([
35	 start_link/3,
36	 start_link/4,
37	 exec/2, exec/3,
38	 stop/1
39	]).
40
41%% Misc utility function for modules implementing this behaviour
42-export([
43	 sleep/1,
44	 sz/1,
45	 debug/1, debug/2,
46	 error/2,
47	 print/3, print/4
48	]).
49
50
51%% Internal exports
52-export([start/4]).
53-export([handler_init/5]).
54
55%% Internal gen_server exports
56-export([
57	 init/1,
58	 handle_call/3,
59	 handle_cast/2,
60	 handle_info/2,
61	 terminate/2,
62	 code_change/3
63	]).
64
65
66-include_lib("megaco/include/megaco.hrl").
67-include("megaco_test_lib.hrl").
68
69
70%%----------------------------------------------------------------------
71
72-define(TIMEOUT, timer:minutes(5)).
73
74
75%%----------------------------------------------------------------------
76
77-record(state,
78	{
79	  parent,
80	  callback_module,
81	  callback_state,
82	  handler = {undefined, undefined},
83	  timer,
84	  name,
85	  id
86	 }).
87
88
89%%%=========================================================================
90%%%  API
91%%%=========================================================================
92
93-callback init(Args) -> {ok, State} | {error, Reason} when
94      Args :: term(),
95      State :: term(),
96      Reason :: term().
97
98-callback handle_parse(Instruction, State) ->
99    {ok, NewInstruction, NewState} |
100    {error, Reason} when
101      Instruction    :: term(),
102      State          :: term(),
103      NewInstruction :: term(),
104      NewState       :: term(),
105      Reason         :: term().
106
107-callback handle_exec(Instruction, State) ->
108    {ok, NewState} |
109    {error, Reason} when
110      Instruction :: term(),
111      State       :: term(),
112      NewState    :: term(),
113      Reason      :: term().
114
115-callback terminate(Reason, State) ->
116    megaco:void() when
117      Reason :: term(),
118      State :: term().
119
120
121%%----------------------------------------------------------------------
122
123start_link(Mod, Args, Name)
124  when is_atom(Mod) andalso is_list(Name) ->
125    start(Mod, Args, Name, self()).
126
127start_link(Mod, Args, Name, Node)
128  when is_atom(Mod) andalso is_list(Name) andalso (Node =/= node()) ->
129    case rpc:call(Node, ?MODULE, start, [Mod, Args, Name, self()]) of
130	{ok, Pid} ->
131	    link(Pid),
132	    {ok, Pid};
133	Error ->
134	    Error
135    end;
136start_link(Mod, Args, Name, Node)
137  when is_atom(Mod) andalso is_list(Name) andalso (Node =:= node()) ->
138    case start(Mod, Args, Name, self()) of
139	{ok, Pid} ->
140	    link(Pid),
141	    {ok, Pid};
142	Error ->
143	    Error
144    end.
145
146start(Mod, Args, Name, Pid) when is_pid(Pid) ->
147    gen_server:start({local, Mod}, ?MODULE, [Mod, Args, Name, Pid], []).
148
149
150exec(Server, Instructions) ->
151    exec(Server, Instructions, infinity).
152
153exec(Server, Instructions, Timeout)
154  when ((Timeout == infinity) orelse
155	(is_integer(Timeout) andalso (Timeout > 0))) ->
156    call(Server, {exec, Instructions, Timeout}).
157
158
159stop(Server) ->
160    call(Server, stop).
161
162
163%%----------------------------------------------------------------------
164
165%%--------------------------------------------------------------------
166%% Func: init/1
167%% Returns: {ok, State}          |
168%%          {ok, State, Timeout} |
169%%          ignore               |
170%%          {stop, Reason}
171%%--------------------------------------------------------------------
172
173init([Mod, Args, Name, Parent]) ->
174    put(name, Name ++ "-CTRL"),
175    process_flag(trap_exit, true),
176    put(debug, true),
177    d("init -> entry with"
178      "~n   Mod:     ~p"
179      "~n   Args:    ~p"
180      "~n   Name:    ~p"
181      "~n   Parent:  ~p", [Mod, Args, Name, Parent]),
182    case (catch Mod:init(Args)) of
183	{ok, CallbackState} ->
184	    d("init -> ~p initiated:"
185	      "~n   CallbackState: ~p", [Mod, CallbackState]),
186	    State = #state{callback_module = Mod,
187			   callback_state  = CallbackState,
188			   parent          = Parent,
189			   name            = Name},
190	    d("init -> initiated"),
191	    {ok, State};
192	{error, Reason} ->
193	    {stop, Reason}
194    end.
195
196
197%%--------------------------------------------------------------------
198%% Func: handle_call/3
199%% Returns: {reply, Reply, State}          |
200%%          {reply, Reply, State, Timeout} |
201%%          {noreply, State}               |
202%%          {noreply, State, Timeout}      |
203%%          {stop, Reason, Reply, State}   | (terminate/2 is called)
204%%          {stop, Reason, State}            (terminate/2 is called)
205%%--------------------------------------------------------------------
206handle_call({exec, Instructions, Timeout}, _From,
207	    #state{callback_module = Mod,
208		   callback_state  = CallbackState,
209		   name            = Name} = State) ->
210    d("handle_call(exec) -> entry with"
211      "~n   Timeout: ~p", [Timeout]),
212    case (catch handle_parse(Mod, CallbackState, Instructions)) of
213	{ok, NewCallbackState, NewInstructions} ->
214	    d("handle_call(exec) -> parsed"
215	      "~n   NewCallbackState: ~p", [NewCallbackState]),
216	    case handler_start(Name, Mod, NewCallbackState, NewInstructions) of
217		{ok, Pid} ->
218		    d("handle_call(exec) -> handler started"
219		      "~n   Pid: ~p", [Pid]),
220		    Timer = maybe_start_timer(Timeout),
221		    Id    = {node(), make_ref()},
222		    Reply = {ok, Id},
223		    {reply, Reply,
224		     State#state{callback_state = NewCallbackState,
225				 handler        = {running, Pid},
226				 timer          = Timer,
227				 id             = Id}};
228		{error, Reason} ->
229		    e("failed starting handler process"
230		      "~n   Reason: ~p", [Reason]),
231		    Reply = {error, {failed_starting_handler, Reason}},
232		    {stop, Reason, Reply, State}
233	    end;
234	{error, Reason} ->
235	    e("failed parsing instructions"
236	      "~n   Reason: ~p", [Reason]),
237	    Reply = {error, {invalid_instruction, Reason}},
238	    {stop, Reason, Reply, State}
239    end;
240
241handle_call(stop, _From, State) ->
242    Reply = ok,
243    {stop, normal, Reply, State};
244
245handle_call(Request, From, State) ->
246    e("unexpected request"
247      "~n   Request: ~p"
248      "~n   From:    ~p", [Request, From]),
249    Reason = {error, {unknown_request, Request, From}},
250    Reply  = {error, unknown_request},
251    {stop, Reason, Reply, State}.
252
253
254%%--------------------------------------------------------------------
255%% Func: handle_cast/2
256%% Returns: {noreply, State}          |
257%%          {noreply, State, Timeout} |
258%%          {stop, Reason, State}            (terminate/2 is called)
259%%--------------------------------------------------------------------
260handle_cast(Msg, State) ->
261    e("unexpected message"
262      "~n   Msg: ~p", [Msg]),
263    Reason = {error, {unknown_message, Msg}},
264    {stop, Reason, State}.
265
266
267%%--------------------------------------------------------------------
268%% Func: handle_info/2
269%% Returns: {noreply, State}          |
270%%          {noreply, State, Timeout} |
271%%          {stop, Reason, State}            (terminate/2 is called)
272%%--------------------------------------------------------------------
273handle_info({handler_result, Pid, Result},
274	    #state{parent  = Parent,
275		   handler = {running, Pid},
276		   timer   = Timer,
277		   id      = Id} = State) ->
278    d("handle_info(handler_result) -> entry with"
279      "~n   Result: ~p", [Result]),
280    maybe_stop_timer(Timer),
281    handler_stop(Pid),
282    deliver_exec_result(Parent, Id, Result),
283    NewState = State#state{handler = {stopping, Pid},
284			   timer   = undefined,
285			   id      = undefined},
286    {noreply, NewState};
287
288handle_info(handler_timeout, #state{handler = {running, Pid}} = State) ->
289    d("handle_info(handler_timeout) -> entry with"),
290    handler_stop(Pid),
291    {noreply, State#state{handler = {stopping, Pid}}};
292
293handle_info({'EXIT', Pid, {stopped, Result}},
294	    #state{parent  = Parent,
295		   handler = {stopping, Pid},
296		   id      = Id} = State) ->
297    d("handle_info(handler stopped EXIT) -> entry with"
298      "~n   Result: ~p", [Result]),
299    deliver_exec_result(Parent, Id, {error, {handler_timeout, Result}}),
300    {noreply, State#state{handler = {stopped, undefined},
301			  timer   = undefined,
302			  id      = undefined}};
303
304handle_info({'EXIT', Pid, normal},
305	    #state{handler = {_, Pid},
306		   timer   = Timer} = State) ->
307    d("handle_info(handler normal EXIT) -> entry"),
308    maybe_stop_timer(Timer),
309    {noreply, State#state{handler = {stopped, undefined}, timer = undefined}};
310
311handle_info({'EXIT', Pid, Reason},
312	    #state{parent  = Parent,
313		   handler = {_, Pid},
314		   timer   = Timer,
315		   id      = Id} = State) ->
316    d("handle_info(handler EXIT) -> entry with"
317      "~n   Reason: ~p", [Reason]),
318    maybe_stop_timer(Timer),
319    deliver_exec_result(Parent, Id, {error, {handler_crashed, Reason}}),
320    {noreply, State#state{handler = {crashed, undefined},
321			  timer   = undefined,
322			  id      = undefined}};
323
324handle_info(Info, State) ->
325    e("unexpected info"
326      "~n   Info:  ~p"
327      "~n   State: ~p", [Info, State]),
328    Reason = {error, {unknown_info, Info}},
329    {stop, Reason, State}.
330
331
332%%--------------------------------------------------------------------
333%% Func: terminate/2
334%% Purpose: Shutdown the server
335%% Returns: any (ignored by gen_server)
336%%--------------------------------------------------------------------
337terminate(normal, #state{handler = {_HandlerState, Pid}} = _State) ->
338    d("terminate(normal) -> entry"),
339    handler_stop(Pid),
340    ok;
341
342terminate(Reason, #state{handler         = {_HandlerState, Pid},
343			 callback_module = Mod,
344			 callback_state  = CallbackState} = _State) ->
345    d("terminate -> entry with"
346      "~n   Reason: ~p", [Reason]),
347    handler_kill(Pid),
348    (catch Mod:terminate(Reason, CallbackState)),
349    ok.
350
351
352%%----------------------------------------------------------------------
353%% Func: code_change/3
354%% Purpose: Convert process state when code is changed
355%% Returns: {ok, NewState}
356%%----------------------------------------------------------------------
357
358code_change(_Vsn, S, _Extra) ->
359    {ok, S}.
360
361
362%%%-------------------------------------------------------------------
363%%% Internal functions
364%%%-------------------------------------------------------------------
365
366deliver_exec_result(Parent, Id, {ok, Result}) ->
367    Parent ! {exec_complete, Id, ok, Result};
368deliver_exec_result(Parent, Id, {error, Reason}) ->
369    Parent ! {exec_complete, Id, error, Reason}.
370
371
372handle_parse(Mod, State, Instructions) ->
373    handle_parse(Mod, State, Instructions, []).
374
375handle_parse(_Mod, State, [], Acc) ->
376    {ok, State, lists:reverse(Acc)};
377
378handle_parse(Mod, State, [Instruction|Instructions], Acc) ->
379    case (catch Mod:handle_parse(Instruction, State)) of
380	{ok, NewInstruction, NewState} ->
381	    handle_parse(Mod, NewState, Instructions, [NewInstruction|Acc]);
382	{error, Reason} ->
383	    {error, {invalid_instruction, Instruction, Reason}};
384	{'EXIT', Reason} ->
385	    {error, {exit, Instruction, Reason}}
386    end.
387
388
389%%%-------------------------------------------------------------------
390
391handler_kill(Pid) when is_pid(Pid) ->
392    erlang:exit(Pid, kill);
393handler_kill(_) ->
394    ok.
395
396handler_stop(Pid) when is_pid(Pid) ->
397    Pid ! {stop, self()};
398handler_stop(_) ->
399    ok.
400
401handler_start(Name, Mod, State, Instructions) ->
402    Args = [Name, self(), Mod, State, Instructions],
403    proc_lib:start_link(?MODULE, handler_init, Args).
404
405
406handler_init(Name, Parent, Mod, State, Instructions) ->
407    put(name, Name ++ "-HANDLER"),
408    proc_lib:init_ack(Parent, {ok, self()}),
409    d("handler_init -> initiated"),
410    handler_main(Parent, Mod, State, Instructions).
411
412handler_main(Parent, Mod, State, []) ->
413    d("handler_main -> done when"
414      "~n   State: ~p", [State]),
415    Result = (catch Mod:terminate(normal, State)),
416    Parent ! {handler_result, self(), {ok, Result}},
417    receive
418	{stop, Parent} ->
419	    exit(normal);
420	{'EXIT', Parent, Reason} ->
421	    exit({parent_died, Reason})
422    end;
423
424handler_main(Parent, Mod, State, [Instruction|Instructions]) ->
425    d("handler_main -> entry with"
426      "~n      Instruction: ~p", [Instruction]),
427    receive
428	{stop, Parent} ->
429	    d("handler_main -> premature stop requested"),
430	    Result = (catch Mod:terminate(stopped, State)),
431	    exit({stopped, Result});
432	{'EXIT', Parent, Reason} ->
433	    d("handler_main -> parent exited"
434	      "~n   Reason: ~p", [Reason]),
435	    Result = (catch Mod:terminate({parent_died, Reason}, State)),
436	    exit({parent_died, Reason, Result})
437    after 0 ->
438            d("handler_main -> exec: "
439              "~n      Instruction: ~p", [Instruction]),
440	    case (catch handler_callback_exec(Mod, State, Instruction)) of
441		{ok, NewState} ->
442		    handler_main(Parent, Mod, NewState, Instructions);
443		{error, Reason} ->
444		    d("handler_main -> exec failed"
445		      "~n   Reason: ~p", [Reason]),
446		    case (catch Mod:terminate(normal, State)) of
447			{ok, Result} ->
448			    Parent ! {handler_result, self(), {error, Result}};
449			Error ->
450			    Result = {bad_terminate, Error},
451			    Parent ! {handler_result, self(), {error, Result}}
452		    end,
453		    receive
454			{stop, Parent} ->
455			    exit(normal);
456			{'EXIT', Parent, Reason} ->
457			    exit({parent_died, Reason})
458		    end;
459		{'EXIT', Reason} ->
460		    d("handler_main -> exec EXIT"
461		      "~n   Reason: ~p", [Reason]),
462		    exit({callback_exec_exit, Reason})
463
464	    end
465    end.
466
467handler_callback_exec(Mod, State, Instruction) ->
468    Mod:handle_exec(Instruction, State).
469
470
471%%%-------------------------------------------------------------------
472
473maybe_start_timer(Timeout) when is_integer(Timeout) ->
474    erlang:send_after(Timeout, self(), handler_timeout);
475maybe_start_timer(_) ->
476    undefined.
477
478
479maybe_stop_timer(undefined) ->
480    ok;
481maybe_stop_timer(Timer) ->
482    (catch erlang:cancel_timer(Timer)).
483
484
485%%% ----------------------------------------------------------------
486
487call(Server, Request) ->
488    call(Server, Request, infinity).
489
490call(Server, Request, Timeout) ->
491    case (catch gen_server:call(Server, Request, Timeout)) of
492        {'EXIT', _} ->
493            {error, not_started};
494        Res ->
495            Res
496    end.
497
498%% cast(Server, Msg) ->
499%%     case (catch gen_server:cast(Server, Msg)) of
500%%         {'EXIT', _} ->
501%%             {error, not_started};
502%%         Res ->
503%%             Res
504%%     end.
505
506
507%%% ----------------------------------------------------------------
508
509sleep(X) when is_integer(X) andalso (X =< 0) -> ok;
510sleep(X) -> receive after X -> ok end.
511
512sz(Bin) when is_binary(Bin) ->
513    size(Bin);
514sz(L) when is_list(L) ->
515    length(L);
516sz(_) ->
517    -1.
518
519
520%%% ----------------------------------------------------------------
521
522d(F)    -> debug(F).
523d(F, A) -> debug(F, A).
524
525e(F, A) -> error(F, A).
526
527%% p(P,    F, A) -> print(P,    F, A).
528%% p(P, N, F, A) -> print(P, N, F, A).
529
530
531%% -------------------------
532
533debug(F) ->
534    debug(F, []).
535
536debug(F, A) ->
537    debug(get(debug), F, A).
538
539debug(true, F, A) ->
540    print(false, " DBG", F, A);
541debug(_, _, _) ->
542    ok.
543
544
545print(Pre, F, A) ->
546    print(true, Pre, F, A).
547
548
549error(F, A) ->
550    print(true, " ERROR", F, A).
551
552
553print(true = _Verbose, Pre, F, A) ->
554    FStr = ?F("*** [~s] ~p ~s~s *** " ++
555                  "~n   " ++ F ++ "~n~n",
556              [?FTS(), self(), string_name(), Pre | A]),
557    io:format(user, FStr, []),
558    io:format(standard_io, FStr, []);
559print(false = _Verbose, Pre, F, A) ->
560    FStr = ?F("*** [~s] ~p ~s~s *** " ++
561                  "~n   " ++ F ++ "~n~n",
562              [?FTS(), self(), string_name(), Pre | A]),
563    io:format(FStr, []).
564
565string_name() ->
566    case get(name) of
567        N when is_list(N) ->
568            N;
569        undefined ->
570            "";
571        N when is_atom(N) ->
572            atom_to_list(N)
573    end.
574
575