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