1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 2003-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: Verify the application specifics of the Megaco application 24%%---------------------------------------------------------------------- 25-module(megaco_mreq_SUITE). 26 27-export([ 28 suite/0, all/0, groups/0, 29 init_per_suite/1, end_per_suite/1, 30 init_per_group/2, end_per_group/2, 31 init_per_testcase/2, end_per_testcase/2, 32 33 req_and_rep/1, 34 req_and_pending/1, 35 req_and_cancel/1 36 37 ]). 38 39-include_lib("common_test/include/ct.hrl"). 40-include_lib("megaco/include/megaco.hrl"). 41-include_lib("megaco/include/megaco_message_v1.hrl"). 42-include("megaco_test_lib.hrl"). 43 44-define(TEST_VERBOSITY, debug). 45-define(MGC_VERBOSITY, debug). 46-define(MG_VERBOSITY, debug). 47 48-define(LOAD_COUNTER_START, 10). 49-define(A4444, ["11111111", "00000000", "00000000"]). 50-define(A4445, ["11111111", "00000000", "11111111"]). 51-define(A5555, ["11111111", "11111111", "00000000"]). 52-define(A5556, ["11111111", "11111111", "11111111"]). 53 54-define(MGC_START(Pid, Mid, ET, Conf, Verb), 55 megaco_test_mgc:start(Pid, Mid, ET, Conf, Verb)). 56-define(MGC_STOP(Pid), megaco_test_mgc:stop(Pid)). 57-define(MGC_GET_STATS(Pid, No), megaco_test_mgc:get_stats(Pid, No)). 58-define(MGC_RESET_STATS(Pid), megaco_test_mgc:reset_stats(Pid)). 59-define(MGC_REQ_DISC(Pid,To), megaco_test_mgc:request_discard(Pid,To)). 60-define(MGC_REQ_PEND(Pid,To), megaco_test_mgc:request_pending(Pid,To)). 61-define(MGC_REQ_HAND(Pid), megaco_test_mgc:request_handle(Pid)). 62 63-define(MG_START(Pid, Mid, Enc, Transp, Conf, Verb), 64 megaco_test_mg:start(Pid, Mid, Enc, Transp, Conf, Verb)). 65-define(MG_STOP(Pid), megaco_test_mg:stop(Pid)). 66-define(MG_GET_STATS(Pid), megaco_test_mg:get_stats(Pid)). 67-define(MG_RESET_STATS(Pid), megaco_test_mg:reset_stats(Pid)). 68-define(MG_SERV_CHANGE(Pid), megaco_test_mg:service_change(Pid)). 69-define(MG_NOTIF_RAR(Pid), megaco_test_mg:notify_request_and_reply(Pid)). 70-define(MG_NOTIF_REQ(Pid), megaco_test_mg:notify_request(Pid)). 71-define(MG_NOTIF_AR(Pid), megaco_test_mg:await_notify_reply(Pid)). 72-define(MG_CANCEL(Pid,R), megaco_test_mg:cancel_request(Pid,R)). 73-define(MG_APPLY_LOAD(Pid,CntStart), megaco_test_mg:apply_load(Pid,CntStart)). 74 75 76%%====================================================================== 77%% Common Test interface functions 78%%====================================================================== 79 80suite() -> 81 [{ct_hooks, [ts_install_cth]}]. 82 83all() -> 84 %% This is a temporary messure to ensure that we can 85 %% test the socket backend without effecting *all* 86 %% applications on *all* machines. 87 %% This flag is set only for *one* host. 88 case ?TEST_INET_BACKENDS() of 89 true -> 90 [ 91 {group, inet_backend_default}, 92 {group, inet_backend_inet}, 93 {group, inet_backend_socket} 94 ]; 95 _ -> 96 [ 97 {group, inet_backend_default} 98 ] 99 end. 100 101groups() -> 102 [ 103 {inet_backend_default, [], inet_backend_default_cases()}, 104 {inet_backend_inet, [], inet_backend_inet_cases()}, 105 {inet_backend_socket, [], inet_backend_socket_cases()}, 106 107 {all, [], all_cases()} 108 ]. 109 110inet_backend_default_cases() -> 111 [{all, [], all_cases()}]. 112 113inet_backend_inet_cases() -> 114 [{all, [], all_cases()}]. 115 116inet_backend_socket_cases() -> 117 [{all, [], all_cases()}]. 118 119all_cases() -> 120 [ 121 req_and_rep, 122 req_and_pending, 123 req_and_cancel 124 ]. 125 126 127 128%% 129%% ----- 130%% 131 132init_per_suite(suite) -> 133 []; 134init_per_suite(doc) -> 135 []; 136init_per_suite(Config0) when is_list(Config0) -> 137 138 ?ANNOUNCE_SUITE_INIT(), 139 140 p("init_per_suite -> entry with" 141 "~n Config: ~p" 142 "~n Nodes: ~p", [Config0, erlang:nodes()]), 143 144 case ?LIB:init_per_suite(Config0) of 145 {skip, _} = SKIP -> 146 SKIP; 147 148 Config1 when is_list(Config1) -> 149 150 %% We need a (local) monitor on this node also 151 megaco_test_sys_monitor:start(), 152 153 p("init_per_suite -> end when" 154 "~n Config: ~p" 155 "~n Nodes: ~p", [Config1, erlang:nodes()]), 156 157 Config1 158 end. 159 160end_per_suite(suite) -> []; 161end_per_suite(doc) -> []; 162end_per_suite(Config0) when is_list(Config0) -> 163 164 p("end_per_suite -> entry with" 165 "~n Config: ~p" 166 "~n Nodes: ~p", [Config0, erlang:nodes()]), 167 168 megaco_test_sys_monitor:stop(), 169 Config1 = ?LIB:end_per_suite(Config0), 170 171 p("end_per_suite -> end when" 172 "~n Nodes: ~p", [erlang:nodes()]), 173 174 Config1. 175 176 177%% 178%% ----- 179%% 180 181init_per_group(inet_backend_default = Group, Config) -> 182 ?ANNOUNCE_GROUP_INIT(Group), 183 [{socket_create_opts, []} | Config]; 184init_per_group(inet_backend_inet = Group, Config) -> 185 ?ANNOUNCE_GROUP_INIT(Group), 186 case ?EXPLICIT_INET_BACKEND() of 187 true -> 188 %% The environment trumps us, 189 %% so only the default group should be run! 190 {skip, "explicit inet backend"}; 191 false -> 192 [{socket_create_opts, [{inet_backend, inet}]} | Config] 193 end; 194init_per_group(inet_backend_socket = Group, Config) -> 195 ?ANNOUNCE_GROUP_INIT(Group), 196 case ?EXPLICIT_INET_BACKEND() of 197 true -> 198 %% The environment trumps us, 199 %% so only the default group should be run! 200 {skip, "explicit inet backend"}; 201 false -> 202 [{socket_create_opts, [{inet_backend, socket}]} | Config] 203 end; 204init_per_group(Group, Config) -> 205 ?ANNOUNCE_GROUP_INIT(Group), 206 Config. 207 208end_per_group(_Group, Config) -> 209 Config. 210 211 212 213init_per_testcase(Case, Config) -> 214 process_flag(trap_exit, true), 215 216 p("init_per_suite -> entry with" 217 "~n Config: ~p" 218 "~n Nodes: ~p", [Config, erlang:nodes()]), 219 220 megaco_test_global_sys_monitor:reset_events(), 221 megaco_test_lib:init_per_testcase(Case, Config). 222 223end_per_testcase(Case, Config) -> 224 process_flag(trap_exit, false), 225 226 p("end_per_suite -> entry with" 227 "~n Config: ~p" 228 "~n Nodes: ~p", [Config, erlang:nodes()]), 229 230 p("system events during test: " 231 "~n ~p", [megaco_test_global_sys_monitor:events()]), 232 233 megaco_test_lib:end_per_testcase(Case, Config). 234 235 236 237%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 238 239req_and_rep(suite) -> 240 []; 241req_and_rep(doc) -> 242 []; 243req_and_rep(Config) when is_list(Config) -> 244 Pre = fun() -> 245 MgcNode = make_node_name(mgc), 246 Mg1Node = make_node_name(mg1), 247 Mg2Node = make_node_name(mg2), 248 Mg3Node = make_node_name(mg3), 249 Mg4Node = make_node_name(mg4), 250 d("try start nodes: " 251 "~n MgcNode: ~p" 252 "~n Mg1Node: ~p" 253 "~n Mg2Node: ~p" 254 "~n Mg3Node: ~p" 255 "~n Mg4Node: ~p", 256 [MgcNode, Mg1Node, Mg2Node, Mg3Node, Mg4Node]), 257 Nodes = [MgcNode, Mg1Node, Mg2Node, Mg3Node, Mg4Node], 258 ok = ?START_NODES(Nodes), 259 Nodes 260 end, 261 Case = fun(X) -> 262 SCO = ?config(socket_create_opts, Config), 263 do_req_and_rep(SCO, X) 264 end, 265 Post = fun(Nodes) -> 266 d("stop nodes (in the reverse order):" 267 "~n ~p", [Nodes]), 268 ?STOP_NODES(lists:reverse(Nodes)) 269 end, 270 try_tc(req_and_rep, Pre, Case, Post). 271 272do_req_and_rep(SCO, [MgcNode, Mg1Node, Mg2Node, Mg3Node, Mg4Node]) -> 273 %% Start the MGC and MGs 274 i("start the MGC"), 275 ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], 276 {ok, Mgc} = 277 ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, SCO, ?MGC_VERBOSITY), 278 279 i("start and connect the MGs"), 280 MgConf0 = [{Mg1Node, "mg1", text, tcp, ?MG_VERBOSITY}, 281 {Mg2Node, "mg2", text, udp, ?MG_VERBOSITY}, 282 {Mg3Node, "mg3", binary, tcp, ?MG_VERBOSITY}, 283 {Mg4Node, "mg4", binary, udp, ?MG_VERBOSITY}], 284 MgConf = rar_connect_mg(SCO, MgConf0, []), 285 286 %% Collect the (initial) MGs statistics 287 Stats1 = rar_get_mg_stats(MgConf, []), 288 d("stats for the MGs: " 289 "~n ~p", [Stats1]), 290 291 %% Collect and check the MGC statistics 292 i("collect and check the MGC stats"), 293 {ok, MgcStats1} = ?MGC_GET_STATS(Mgc, 1), 294 d("stats (1) for Mgc: " 295 "~n ~p" 296 "~n", [MgcStats1]), 297 298 299 sleep(1000), 300 301 302 %% And apply some load 303 i("apply traffic load"), 304 ok = rar_apply_load(MgConf), 305 306 %% Await completion of load part and the collect traffic 307 i("await load completion"), 308 ok = rar_await_load_complete(MgConf), 309 310 311 sleep(1000), 312 313 314 i("collect the MGs statistics"), 315 Stats2 = rar_get_mg_stats(MgConf, []), 316 d("stats for MGs: " 317 "~n ~p", [Stats2]), 318 319 i("collect the MGC statistics"), 320 {ok, MgcStats2} = ?MGC_GET_STATS(Mgc, 1), 321 d("stats (1) for Mgc: " 322 "~n ~p" 323 "~n", [MgcStats2]), 324 325 326 sleep(1000), 327 328 329 %% Reset counters 330 i("reset the MGs statistics"), 331 rar_reset_mg_stats(MgConf), 332 Stats3 = rar_get_mg_stats(MgConf, []), 333 d("stats for the MGs: " 334 "~n ~p", [Stats3]), 335 336 i("reset the MGC statistics"), 337 rar_reset_mgc_stats(Mgc), 338 {ok, MgcStats3} = ?MGC_GET_STATS(Mgc, 1), 339 d("stats (1) for Mgc: " 340 "~n ~p" 341 "~n", [MgcStats3]), 342 343 344 sleep(1000), 345 346 347 %% Tell MGs to stop 348 i("stop the MGs"), 349 rar_stop_mg(MgConf), 350 351 352 sleep(1000), 353 354 355 %% Collect the statistics 356 i("collect the MGC statistics"), 357 {ok, MgcStats4} = ?MGC_GET_STATS(Mgc, 1), 358 d("stats (1) for Mgc: " 359 "~n ~p", [MgcStats4]), 360 {ok, MgcStats5} = ?MGC_GET_STATS(Mgc, 2), 361 d("stats (2) for Mgc: " 362 "~n ~p" 363 "~n", [MgcStats5]), 364 365 %% Tell Mgc to stop 366 i("stop the MGC"), 367 ?MGC_STOP(Mgc), 368 369 i("done", []), 370 ok. 371 372 373rar_connect_mg(_, [], Acc) -> 374 lists:reverse(Acc); 375rar_connect_mg(SCO, [{Node, Name, Coding, Trans, Verb}|Mg], Acc) -> 376 Pid = rar_connect_mg(SCO, Node, Name, Coding, Trans, Verb), 377 rar_connect_mg(SCO, Mg, [{Name, Pid}|Acc]). 378 379rar_connect_mg(SCO, Node, Name, Coding, Trans, Verb) -> 380 Mid = {deviceName, Name}, 381 {ok, Pid} = ?MG_START(Node, Mid, Coding, Trans, SCO, Verb), 382 383 %% Ask the MGs to do a service change 384 Res = ?MG_SERV_CHANGE(Pid), 385 d("rar_connect_mg -> (~s) service change result: ~p", [Name, Res]), 386 387 Pid. 388 389 390rar_stop_mg(MGs) -> 391 [?MG_STOP(Pid) || {_Name, Pid} <- MGs]. 392 393 394rar_get_mg_stats([], Acc) -> 395 lists:reverse(Acc); 396rar_get_mg_stats([{Name, Pid}|Mgs], Acc) -> 397 {ok, Stats} = ?MG_GET_STATS(Pid), 398 d("rar_get_mg_stats -> stats for ~s: " 399 "~n ~p" 400 "~n", [Name, Stats]), 401 rar_get_mg_stats(Mgs, [{Name, Stats}|Acc]). 402 403 404rar_apply_load([]) -> 405 ok; 406rar_apply_load([{_, MG}|MGs]) -> 407 ?MG_APPLY_LOAD(MG,?LOAD_COUNTER_START), 408 rar_apply_load(MGs). 409 410 411rar_reset_mg_stats([]) -> 412 ok; 413rar_reset_mg_stats([{Name, Pid}|MGs]) -> 414 d("rar_reset_mg_stats -> resetting ~s", [Name]), 415 ?MG_RESET_STATS(Pid), 416 rar_reset_mg_stats(MGs). 417 418rar_reset_mgc_stats(Mgc) -> 419 d("rar_reset_mgc_stats -> resetting ~p", [Mgc]), 420 ?MGC_RESET_STATS(Mgc). 421 422 423rar_await_load_complete([]) -> 424 ok; 425rar_await_load_complete(MGs0) -> 426 receive 427 {load_complete, Pid} -> 428 d("received load_complete from ~p", [Pid]), 429 MGs1 = lists:keydelete(Pid, 2, MGs0), 430 rar_await_load_complete(lists:delete(Pid, MGs1)); 431 {'EXIT', Pid, Reason} -> 432 e("exit signal from ~p: ~p", [Pid, Reason]), 433 case lists:keymember(Pid, 2, MGs0) of 434 true -> 435 exit({mg_exit, Pid, Reason}); 436 false -> 437 MGs1 = lists:keydelete(Pid, 2, MGs0), 438 rar_await_load_complete(lists:delete(Pid, MGs1)) 439 end 440 end. 441 442 443%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 444 445req_and_pending(suite) -> 446 []; 447req_and_pending(doc) -> 448 []; 449req_and_pending(Config) when is_list(Config) -> 450 Pre = fun() -> 451 MgcNode = make_node_name(mgc), 452 MgNode = make_node_name(mg), 453 d("try starting nodes: " 454 "~n MgcNode: ~p" 455 "~n MgNode: ~p", [MgcNode, MgNode]), 456 Nodes = [MgcNode, MgNode], 457 ok = ?START_NODES(Nodes), 458 Nodes 459 end, 460 Case = fun(X) -> 461 SCO = ?config(socket_create_opts, Config), 462 do_req_and_pending(SCO, X) 463 end, 464 Post = fun(Nodes) -> 465 d("stop nodes (in the reverse order):" 466 "~n ~p", [Nodes]), 467 ?STOP_NODES(lists:reverse(Nodes)) 468 end, 469 try_tc(req_and_pending, Pre, Case, Post). 470 471do_req_and_pending(SCO, [MgcNode, MgNode]) -> 472 473 %% Start the MGC and MGs 474 i("try start the MGC"), 475 ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], 476 {ok, Mgc} = 477 ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, SCO, ?MGC_VERBOSITY), 478 479 i("try start the MG"), 480 {ok, Mg} = 481 ?MG_START(MgNode, {deviceName, "mg"}, text, tcp, SCO, ?MG_VERBOSITY), 482 483 i("connect MG (to MFC)"), 484 Res1 = ?MG_SERV_CHANGE(Mg), 485 d("service change result: ~p", [Res1]), 486 487 sleep(1000), 488 489 i("[MGC] change request action to pending"), 490 {ok, _} = ?MGC_REQ_PEND(Mgc, 3500), 491 492 i("[MG] send notify request"), 493 {ok, Res2} = ?MG_NOTIF_RAR(Mg), 494 d("notify reply: ~p", [Res2]), 495 496 sleep(1000), 497 498 %% Tell MG to stop 499 i("stop the MG"), 500 ?MG_STOP(Mg), 501 502 %% Tell Mgc to stop 503 i("stop the MGC"), 504 ?MGC_STOP(Mgc), 505 506 i("done", []), 507 ok. 508 509 510 511%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 512 513req_and_cancel(suite) -> 514 []; 515req_and_cancel(doc) -> 516 []; 517req_and_cancel(Config) when is_list(Config) -> 518 Pre = fun() -> 519 MgcNode = make_node_name(mgc), 520 MgNode = make_node_name(mg), 521 d("try start nodes: " 522 "~n MgcNode: ~p" 523 "~n MgNode: ~p", 524 [MgcNode, MgNode]), 525 Nodes = [MgcNode, MgNode], 526 ok = ?START_NODES(Nodes), 527 Nodes 528 end, 529 Case = fun(X) -> 530 SCO = ?config(socket_create_opts, Config), 531 do_req_and_cancel(SCO, X) 532 end, 533 Post = fun(Nodes) -> 534 d("stop nodes (in the reverse order):" 535 "~n ~p", [Nodes]), 536 ?STOP_NODES(lists:reverse(Nodes)) 537 end, 538 try_tc(req_and_cancel, Pre, Case, Post). 539 540do_req_and_cancel(SCO, [MgcNode, MgNode]) -> 541 %% Start the MGC and MGs 542 i("start the MGC"), 543 ET = [{text,tcp}, {text,udp}, {binary,tcp}, {binary,udp}], 544 {ok, Mgc} = 545 ?MGC_START(MgcNode, {deviceName, "ctrl"}, ET, SCO, ?MGC_VERBOSITY), 546 547 i("start the MG"), 548 {ok, Mg} = 549 ?MG_START(MgNode, {deviceName, "mg"}, text, tcp, SCO, ?MG_VERBOSITY), 550 551 i("connect the MG"), 552 Res1 = ?MG_SERV_CHANGE(Mg), 553 d("service change result: ~p", [Res1]), 554 555 556 sleep(1000), 557 558 i("change request action to pending"), 559 {ok, _} = ?MGC_REQ_DISC(Mgc,5000), 560 561 i("send notify request"), 562 ?MG_NOTIF_REQ(Mg), 563 564 d("wait some to get it going",[]), 565 sleep(1000), 566 567 i("now cancel the notify request"), 568 ok = ?MG_CANCEL(Mg, req_and_cancel), 569 570 i("now await the notify request result"), 571 Res2 = ?MG_NOTIF_AR(Mg), 572 rac_analyze_result(Res2), 573 574 575 %% Tell MGs to stop 576 i("stop the MG"), 577 ?MG_STOP(Mg), 578 579 %% Tell Mgc to stop 580 i("stop the MGC"), 581 ?MGC_STOP(Mgc), 582 583 i("done", []), 584 ok. 585 586 587rac_analyze_result({ok, {_PV,Res}}) -> 588 i("rac_analyze_result -> notify request result:" 589 "~n ~p", [Res]), 590 rac_analyze_result2(Res); 591rac_analyze_result(Unexpected) -> 592 e("rac_analyze_result -> unexpected result: " 593 "~n ~p", [Unexpected]), 594 exit({unexpected_result, Unexpected}). 595 596rac_analyze_result2({error,{user_cancel, req_and_cancel}}) -> 597 ok; 598rac_analyze_result2([]) -> 599 ok; 600rac_analyze_result2([{error,{user_cancel, req_and_cancel}}|Res]) -> 601 rac_analyze_result2(Res); 602rac_analyze_result2([Unknown|_Res]) -> 603 e("rac_analyze_result2 -> unexpected result: " 604 "~n ~p", [Unknown]), 605 exit({unknown_result, Unknown}). 606 607 608%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 609 610make_node_name(Name) -> 611 case string:tokens(atom_to_list(node()), [$@]) of 612 [_,Host] -> 613 list_to_atom(lists:concat([atom_to_list(Name) ++ "@" ++ Host])); 614 _ -> 615 exit("Test node must be started with '-sname'") 616 end. 617 618 619%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 620 621sleep(X) -> 622 receive after X -> ok end. 623 624 625%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 626 627try_tc(TCName, Pre, Case, Post) -> 628 try_tc(TCName, "TEST", ?TEST_VERBOSITY, Pre, Case, Post). 629 630try_tc(TCName, Name, Verbosity, Pre, Case, Post) -> 631 ?TRY_TC(TCName, Name, Verbosity, Pre, Case, Post). 632 633 634 635%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 636 637p(F, A) -> 638 io:format("*** [~s] ~p ***" 639 "~n " ++ F ++ "~n", 640 [?FTS(), self() | A]). 641 642%% e(F) -> 643%% i(F, []). 644 645e(F, A) -> 646 print(info, get(verbosity), "ERROR", get(tc), F, A). 647 648 649i(F) -> 650 i(F, []). 651 652i(F, A) -> 653 print(info, get(verbosity), "INFO", get(tc), F, A). 654 655 656%% d(F) -> 657%% d(F, []). 658 659d(F, A) -> 660 print(debug, get(verbosity), "DBG", get(tc), F, A). 661 662 663printable(_, debug) -> true; 664printable(info, info) -> true; 665printable(_,_) -> false. 666 667print(Severity, Verbosity, P, TC, F, A) -> 668 print(printable(Severity,Verbosity), P, TC, F, A). 669 670print(true, P, TC, F, A) -> 671 io:format("*** [~s] [~s] ~p ~s:~w ***" 672 "~n " ++ F ++ "~n", 673 [?FTS(), P, self(), get(sname), TC | A]); 674print(_, _, _, _, _) -> 675 ok. 676 677