1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 1999-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: Lightweight test server
24%%----------------------------------------------------------------------
25%%
26
27-module(megaco_test_lib).
28
29%% -compile(export_all).
30
31-export([
32         log/4,
33         error/3,
34
35         sleep/1,
36         hours/1, minutes/1, seconds/1,
37         formated_timestamp/0, format_timestamp/1,
38
39         skip/3,
40         non_pc_tc_maybe_skip/4,
41         os_based_skip/1,
42
43         flush/0,
44         still_alive/1,
45
46         display_alloc_info/0,
47         display_system_info/1, display_system_info/2, display_system_info/3,
48
49         try_tc/6,
50
51         prepare_test_case/5,
52
53         proxy_start/1, proxy_start/2,
54
55         mk_nodes/1,
56         start_nodes/3, start_nodes/4,
57         start_node/3,  start_node/4,
58
59         stop_nodes/3,
60         stop_node/3
61
62        ]).
63-export([init_per_suite/1,    end_per_suite/1,
64         init_per_testcase/2, end_per_testcase/2]).
65
66-export([proxy_init/2]).
67
68-include("megaco_test_lib.hrl").
69
70-record('REASON', {mod, line, desc}).
71
72
73%% ----------------------------------------------------------------
74%% Time related function
75%%
76
77sleep(infinity) ->
78    receive
79    after infinity ->
80            ok
81    end;
82sleep(MSecs) ->
83    receive
84    after trunc(MSecs) ->
85            ok
86    end,
87    ok.
88
89
90hours(N)   -> trunc(N * 1000 * 60 * 60).
91minutes(N) -> trunc(N * 1000 * 60).
92seconds(N) -> trunc(N * 1000).
93
94
95formated_timestamp() ->
96    format_timestamp(os:timestamp()).
97
98format_timestamp(TS) ->
99    megaco:format_timestamp(TS).
100
101
102%% ----------------------------------------------------------------
103%% Conditional skip of testcases
104%%
105
106non_pc_tc_maybe_skip(Config, Condition, File, Line)
107  when is_list(Config) andalso is_function(Condition) ->
108    %% Check if we shall skip the skip
109    case os:getenv("TS_OS_BASED_SKIP") of
110	"false" ->
111	    ok;
112	_ ->
113	    case lists:keysearch(ts, 1, Config) of
114		{value, {ts, megaco}} ->
115		    %% Always run the testcase if we are using our own
116		    %% test-server...
117		    ok;
118		_ ->
119		    case (catch Condition()) of
120			true ->
121			    skip(non_pc_testcase, File, Line);
122			_ ->
123			    ok
124		    end
125	    end
126    end.
127
128
129%% The type and spec'ing is just to increase readability
130-type os_family()  :: win32 | unix.
131-type os_name()    :: atom().
132-type os_version() :: string() | {non_neg_integer(),
133                                  non_neg_integer(),
134                                  non_neg_integer()}.
135-type os_skip_check() :: fun(() -> boolean()) |
136                            fun((os_version()) -> boolean()).
137-type skippable() :: any | [os_family() |
138                            {os_family(), os_name() |
139                                          [os_name() | {os_name(),
140                                                        os_skip_check()}]}].
141
142-spec os_based_skip(skippable()) -> boolean().
143
144os_based_skip(any) ->
145    true;
146os_based_skip(Skippable) when is_list(Skippable) ->
147    os_base_skip(Skippable, os:type());
148os_based_skip(_Crap) ->
149    false.
150
151os_base_skip(Skippable, {OsFam, OsName}) ->
152    os_base_skip(Skippable, OsFam, OsName);
153os_base_skip(Skippable, OsFam) ->
154    os_base_skip(Skippable, OsFam, undefined).
155
156os_base_skip(Skippable, OsFam, OsName) ->
157    %% Check if the entire family is to be skipped
158    %% Example: [win32, unix]
159    case lists:member(OsFam, Skippable) of
160        true ->
161            true;
162        false ->
163            %% Example: [{unix, freebsd}] | [{unix, [freebsd, darwin]}]
164            case lists:keysearch(OsFam, 1, Skippable) of
165                {value, {OsFam, OsName}} ->
166                    true;
167                {value, {OsFam, OsNames}} when is_list(OsNames) ->
168                    %% OsNames is a list of:
169                    %%    [atom()|{atom(), function/0 | function/1}]
170                    case lists:member(OsName, OsNames) of
171                        true ->
172                            true;
173                        false ->
174                            os_based_skip_check(OsName, OsNames)
175                    end;
176                _ ->
177                    false
178            end
179    end.
180
181
182
183%% Performs a check via a provided fun with arity 0 or 1.
184%% The argument is the result of os:version().
185os_based_skip_check(OsName, OsNames) ->
186    case lists:keysearch(OsName, 1, OsNames) of
187        {value, {OsName, Check}} when is_function(Check, 0) ->
188            Check();
189        {value, {OsName, Check}} when is_function(Check, 1) ->
190            Check(os:version());
191        _ ->
192            false
193    end.
194
195
196
197%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
198%% Evaluates a test case or test suite
199%% Returns a list of failing test cases:
200%%
201%%     {Mod, Fun, ExpectedRes, ActualRes}
202%%----------------------------------------------------------------------
203
204display_alloc_info() ->
205    io:format("Allocator memory information:~n", []),
206    AllocInfo = alloc_info(),
207    display_alloc_info(AllocInfo).
208
209display_alloc_info([]) ->
210    ok;
211display_alloc_info([{Alloc, Mem}|AllocInfo]) ->
212    io:format("  ~15w: ~10w~n", [Alloc, Mem]),
213    display_alloc_info(AllocInfo).
214
215alloc_info() ->
216    case erlang:system_info(allocator) of
217	{_Allocator, _Version, Features, _Settings} ->
218	    alloc_info(Features);
219	_ ->
220	    []
221    end.
222
223alloc_info(Allocators) ->
224    Allocs = [temp_alloc, sl_alloc, std_alloc, ll_alloc, eheap_alloc,
225	      ets_alloc, binary_alloc, driver_alloc],
226    alloc_info(Allocators, Allocs, []).
227
228alloc_info([], _, Acc) ->
229    lists:reverse(Acc);
230alloc_info([Allocator | Allocators], Allocs, Acc) ->
231    case lists:member(Allocator, Allocs) of
232	true ->
233	    Instances0 = erlang:system_info({allocator, Allocator}),
234	    Instances =
235		if
236		    is_list(Instances0) ->
237			[Instance || Instance <- Instances0,
238				     element(1, Instance) =:= instance];
239		    true ->
240			[]
241		end,
242	    AllocatorMem = alloc_mem_info(Instances),
243	    alloc_info(Allocators, Allocs, [{Allocator, AllocatorMem} | Acc]);
244
245	false ->
246	    alloc_info(Allocators, Allocs, Acc)
247    end.
248
249alloc_mem_info(Instances) ->
250    alloc_mem_info(Instances, []).
251
252alloc_mem_info([], Acc) ->
253    lists:sum([Mem || {instance, _, Mem} <- Acc]);
254alloc_mem_info([{instance, N, Info}|Instances], Acc) ->
255    InstanceMemInfo = alloc_instance_mem_info(Info),
256    alloc_mem_info(Instances, [{instance, N, InstanceMemInfo} | Acc]).
257
258alloc_instance_mem_info(InstanceInfo) ->
259    MBCS = alloc_instance_mem_info(mbcs, InstanceInfo),
260    SBCS = alloc_instance_mem_info(sbcs, InstanceInfo),
261    MBCS + SBCS.
262
263alloc_instance_mem_info(Key, InstanceInfo) ->
264    case lists:keysearch(Key, 1, InstanceInfo) of
265	{value, {Key, Info}} ->
266	    case lists:keysearch(blocks_size, 1, Info) of
267		{value, {blocks_size, Mem, _, _}} ->
268		    Mem;
269		_ ->
270		    0
271	    end;
272	_ ->
273	    0
274    end.
275
276
277display_system_info(WhenStr) ->
278    display_system_info(WhenStr, undefined, undefined).
279
280display_system_info(WhenStr, undefined, undefined) ->
281    display_system_info(WhenStr, "");
282display_system_info(WhenStr, Mod, Func) ->
283    ModFuncStr = lists:flatten(io_lib:format(" ~w:~w", [Mod, Func])),
284    display_system_info(WhenStr, ModFuncStr).
285
286display_system_info(WhenStr, ModFuncStr) ->
287    Fun = fun(F) -> case (catch F()) of
288			{'EXIT', _} ->
289			    undefined;
290			Res ->
291			    Res
292		    end
293	  end,
294    ProcCount    = Fun(fun() -> erlang:system_info(process_count) end),
295    ProcLimit    = Fun(fun() -> erlang:system_info(process_limit) end),
296    ProcMemAlloc = Fun(fun() -> erlang:memory(processes) end),
297    ProcMemUsed  = Fun(fun() -> erlang:memory(processes_used) end),
298    ProcMemBin   = Fun(fun() -> erlang:memory(binary) end),
299    ProcMemTot   = Fun(fun() -> erlang:memory(total) end),
300    %% error_logger:info_msg(
301    io:format("~n"
302	      "~n*********************************************"
303	      "~n"
304	      "System info ~s~s => "
305	      "~n   Process count:        ~w"
306              "~n   Process limit:        ~w"
307              "~n   Process memory alloc: ~w"
308              "~n   Process memory used:  ~w"
309              "~n   Memory for binaries:  ~w"
310              "~n   Memory total:         ~w"
311	      "~n"
312	      "~n*********************************************"
313	      "~n"
314	      "~n", [WhenStr, ModFuncStr,
315		     ProcCount, ProcLimit, ProcMemAlloc, ProcMemUsed,
316		     ProcMemBin, ProcMemTot]),
317    ok.
318
319
320
321%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
322%% Verify that the actual result of a test case matches the exected one
323%% Returns the actual result
324%% Stores the result in the process dictionary if mismatch
325
326error(Actual, Mod, Line) ->
327    global:send(megaco_global_logger, {failed, Mod, Line}),
328    log("<ERROR> Bad result: ~p~n", [Actual], Mod, Line),
329    Label = lists:concat([Mod, "(", Line, ") unexpected result"]),
330    megaco:report_event(60, Mod, Mod, Label,
331			[{line, Mod, Line}, {error, Actual}]),
332    case global:whereis_name(megaco_test_case_sup) of
333	undefined ->
334	    ignore;
335	Pid ->
336	    Fail = #'REASON'{mod = Mod, line = Line, desc = Actual},
337	    Pid ! {fail, self(), Fail}
338    end,
339    Actual.
340
341log(Format, Args, Mod, Line) ->
342    case global:whereis_name(megaco_global_logger) of
343	undefined ->
344	    io:format(user, "~p~p(~p): " ++ Format,
345		      [self(), Mod, Line] ++ Args);
346	Pid ->
347	    io:format(Pid, "~p~p(~p): " ++ Format,
348		      [self(), Mod, Line] ++ Args)
349    end.
350
351skip(Reason) ->
352    exit({skip, Reason}).
353
354skip(Actual, File, Line) ->
355    log("Skipping test case: ~p~n", [Actual], File, Line),
356    String = f("~p(~p): ~p~n", [File, Line, Actual]),
357    skip(String).
358
359fatal_skip(Actual, File, Line) ->
360    error(Actual, File, Line),
361    skip({fatal, Actual, File, Line}).
362
363
364
365%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
366%% Flush the message queue and return its messages
367
368flush() ->
369    receive
370	Msg ->
371	    [Msg | flush()]
372    after 1000 ->
373	    []
374    end.
375
376
377%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
378%% Check if process is alive and kicking
379
380still_alive(Pid) ->
381    erlang:is_process_alive(Pid).
382
383
384%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
385%% The proxy process
386
387proxy_start(ProxyId) ->
388    spawn_link(?MODULE, proxy_init, [ProxyId, self()]).
389
390proxy_start(Node, ProxyId) ->
391    spawn_link(Node, ?MODULE, proxy_init, [ProxyId, self()]).
392
393proxy_init(ProxyId, Controller) ->
394    process_flag(trap_exit, true),
395    IdStr = proxyid2string(ProxyId),
396    put(id, IdStr),
397    ?LOG("[~s] proxy started by ~p~n", [IdStr, Controller]),
398    proxy_loop(ProxyId, Controller).
399
400proxy_loop(OwnId, Controller) ->
401    receive
402	{'EXIT', Controller, Reason} ->
403	    pprint("proxy_loop -> received exit from controller"
404                   "~n   Reason: ~p", [Reason]),
405	    exit(Reason);
406	{stop, Controller, Reason} ->
407	    p("proxy_loop -> received stop from controller"
408	      "~n   Reason: ~p"
409	      "~n", [Reason]),
410	    exit(Reason);
411
412	{apply, Fun} ->
413            pprint("proxy_loop -> received apply request"),
414	    Res = Fun(),
415            pprint("proxy_loop -> apply result: "
416                   "~n   ~p", [Res]),
417	    Controller ! {res, OwnId, Res},
418	    proxy_loop(OwnId, Controller);
419	OtherMsg ->
420	    pprint("proxy_loop -> received unknown message: "
421                   "~n  ~p", [OtherMsg]),
422	    Controller ! {msg, OwnId, OtherMsg},
423	    proxy_loop(OwnId, Controller)
424    end.
425
426proxyid2string(Id) when is_list(Id) ->
427    Id;
428proxyid2string(Id) when is_atom(Id) ->
429    atom_to_list(Id);
430proxyid2string(Id) ->
431    f("~p", [Id]).
432
433pprint(F) ->
434    pprint(F, []).
435
436pprint(F, A) ->
437    io:format("[~s] ~p ~s " ++ F ++ "~n",
438              [get(id), self(), formated_timestamp() | A]).
439
440
441%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
442%% Test server callbacks
443
444init_per_suite(Config) ->
445
446    %% We have some crap machines that causes random test case failures
447    %% for no obvious reason. So, attempt to identify those without actually
448    %% checking for the host name...
449    %% We have two "machines" we are checking for. Both are old installations
450    %% running on really slow VMs (the host machines are old and tired).
451    LinuxVersionVerify =
452        fun(V) when (V > {3,6,11}) ->
453                false; % OK - No skip
454           (V) when (V =:= {3,6,11}) ->
455                case string:trim(os:cmd("cat /etc/issue")) of
456                    "Fedora release 16 " ++ _ -> % Stone age Fedora => Skip
457                        true;
458                    _ ->
459                        false
460                end;
461           (V) when (V > {2,6,24}) ->
462                false; % OK - No skip
463           (_) ->
464                %% We are specifically checking for
465                %% a *really* old gento...
466                case string:find(string:strip(os:cmd("uname -a")), "gentoo") of
467                    nomatch ->
468                        false;
469                    _ -> % Stone age gentoo => Skip
470                        true
471                end
472        end,
473    DarwinVersionVerify =
474        fun(V) when (V > {9, 8, 0}) ->
475                %% This version is OK: No Skip
476                false;
477           (_V) ->
478                %% This version is *not* ok: Skip
479                true
480        end,
481    %% We are "looking" for a specific machine (a VM)
482    %% which are *old and crappy" and slow, because it
483    %% causes a bunch of test cases to fail randomly.
484    %% But we don not want to test for the host name...
485    WinVersionVerify =
486        fun(V) when (V =:= {6,2,9200}) ->
487                try erlang:system_info(schedulers) of
488                    2 ->
489                        true;
490                    _ ->
491                        false
492                catch
493                    _:_:_ ->
494                        true
495                end;
496           (_) ->
497                false
498        end,
499    COND = [
500            {unix, [{linux,  LinuxVersionVerify},
501		    {darwin, DarwinVersionVerify}]},
502            {win32, [{nt, WinVersionVerify}]}
503           ],
504    case os_based_skip(COND) of
505        true ->
506            {skip, "Unstable host and/or os (or combo thererof)"};
507        false ->
508            Factor = analyze_and_print_host_info(),
509            maybe_start_global_sys_monitor(Config),
510            [{megaco_factor, Factor} | Config]
511    end.
512
513%% We start the global system monitor unless explicitly disabled
514maybe_start_global_sys_monitor(Config) ->
515    case lists:keysearch(sysmon, 1, Config) of
516        {value, {sysmon, false}} ->
517            ok;
518        _ ->
519            megaco_test_global_sys_monitor:start()
520    end.
521
522end_per_suite(Config) when is_list(Config) ->
523
524    case lists:keysearch(sysmon, 1, Config) of
525        {value, {sysmon, false}} ->
526            ok;
527        _ ->
528            megaco_test_global_sys_monitor:stop()
529    end,
530
531    Config.
532
533
534init_per_testcase(_Case, Config) ->
535    Pid = group_leader(),
536    Name = megaco_global_logger,
537    case global:whereis_name(Name) of
538	undefined ->
539	    global:register_name(megaco_global_logger, Pid);
540	Pid ->
541	    io:format("~w:init_per_testcase -> "
542		      "already registered to ~p~n", [?MODULE, Pid]),
543	    ok;
544	OtherPid when is_pid(OtherPid) ->
545	    io:format("~w:init_per_testcase -> "
546		      "already registered to other ~p (~p)~n",
547		      [?MODULE, OtherPid, Pid]),
548	    exit({already_registered, {megaco_global_logger, OtherPid, Pid}})
549    end,
550    set_kill_timer(Config).
551
552end_per_testcase(_Case, Config) ->
553    Name = megaco_global_logger,
554    case global:whereis_name(Name) of
555	undefined ->
556	    io:format("~w:end_per_testcase -> already un-registered~n",
557		      [?MODULE]),
558	    ok;
559	Pid when is_pid(Pid) ->
560	    global:unregister_name(megaco_global_logger),
561	    ok
562    end,
563    reset_kill_timer(Config).
564
565
566%% This function prints various host info, which might be usefull
567%% when analyzing the test suite (results).
568%% It also returns a "factor" that can be used when deciding
569%% the load for some test cases. Such as run time or number of
570%% iteraions. This only works for some OSes.
571%%
572%% We make some calculations on Linux, OpenBSD and FreeBSD.
573%% On SunOS we always set the factor to 2 (just to be on the safe side)
574%% On all other os:es (mostly windows) we check the number of schedulers,
575%% but at least the factor will be 2.
576analyze_and_print_host_info() ->
577    {OsFam, OsName} = os:type(),
578    Version         =
579        case os:version() of
580            {Maj, Min, Rel} ->
581                f("~w.~w.~w", [Maj, Min, Rel]);
582            VStr ->
583                VStr
584        end,
585    case {OsFam, OsName} of
586        {unix, linux} ->
587            analyze_and_print_linux_host_info(Version);
588        {unix, openbsd} ->
589            analyze_and_print_openbsd_host_info(Version);
590        {unix, freebsd} ->
591            analyze_and_print_freebsd_host_info(Version);
592        {unix, sunos} ->
593            analyze_and_print_solaris_host_info(Version);
594        {win32, nt} ->
595            analyze_and_print_win_host_info(Version);
596        _ ->
597            io:format("OS Family: ~p"
598                      "~n   OS Type:        ~p"
599                      "~n   Version:        ~p"
600                      "~n   Num Schedulers: ~s"
601                      "~n", [OsFam, OsName, Version, str_num_schedulers()]),
602            try erlang:system_info(schedulers) of
603                1 ->
604                    10;
605                2 ->
606                    5;
607                N when (N =< 6) ->
608                    2;
609                _ ->
610                    1
611            catch
612                _:_:_ ->
613                    10
614            end
615    end.
616
617str_num_schedulers() ->
618    try erlang:system_info(schedulers) of
619        N -> f("~w", [N])
620    catch
621        _:_:_ -> "-"
622    end.
623
624
625analyze_and_print_linux_host_info(Version) ->
626    case file:read_file_info("/etc/issue") of
627        {ok, _} ->
628            io:format("Linux: ~s"
629                      "~n   ~s"
630                      "~n",
631                      [Version, string:trim(os:cmd("cat /etc/issue"))]);
632        _ ->
633            io:format("Linux: ~s"
634                      "~n", [Version])
635    end,
636    Factor =
637        case (catch linux_which_cpuinfo()) of
638            {ok, {CPU, BogoMIPS}} ->
639                io:format("CPU: "
640                          "~n   Model:          ~s"
641                          "~n   BogoMIPS:       ~s"
642                          "~n   Num Schedulers: ~s"
643                          "~n", [CPU, BogoMIPS, str_num_schedulers()]),
644                %% We first assume its a float, and if not try integer
645                try list_to_float(string:trim(BogoMIPS)) of
646                    F when F > 4000 ->
647                        1;
648                    F when F > 1000 ->
649                        2;
650                    F when F > 500 ->
651                        3;
652                    _ ->
653                        5
654                catch
655                    _:_:_ ->
656                        try list_to_integer(string:trim(BogoMIPS)) of
657                            I when I > 4000 ->
658                                1;
659                            I when I > 1000 ->
660                                2;
661                            I when I > 500 ->
662                                3;
663                            _ ->
664                                5
665                        catch
666                            _:_:_ ->
667                                5 % Be a "bit" conservative...
668                        end
669                end;
670            {ok, CPU} ->
671                io:format("CPU: "
672                          "~n   Model:          ~s"
673                          "~n   Num Schedulers: ~s"
674                          "~n", [CPU, str_num_schedulers()]),
675                2; % Be a "bit" conservative...
676            _ ->
677                5 % Be a "bit" (more) conservative...
678        end,
679    %% Check if we need to adjust the factor because of the memory
680    try linux_which_meminfo() of
681        AddFactor ->
682            Factor + AddFactor
683    catch
684        _:_:_ ->
685            Factor
686    end.
687
688linux_which_cpuinfo() ->
689    %% Check for x86 (Intel or AMD)
690    CPU =
691        try [string:trim(S) || S <- string:tokens(os:cmd("grep \"model name\" /proc/cpuinfo"), [$:,$\n])] of
692            ["model name", ModelName | _] ->
693                ModelName;
694            _ ->
695                %% ARM (at least some distros...)
696                try [string:trim(S) || S <- string:tokens(os:cmd("grep \"Processor\" /proc/cpuinfo"), [$:,$\n])] of
697                    ["Processor", Proc | _] ->
698                        Proc;
699                    _ ->
700                        %% Ok, we give up
701                        throw(noinfo)
702                catch
703                    _:_:_ ->
704                        throw(noinfo)
705                end
706        catch
707            _:_:_ ->
708                throw(noinfo)
709        end,
710    try [string:trim(S) || S <- string:tokens(os:cmd("grep -i \"bogomips\" /proc/cpuinfo"), [$:,$\n])] of
711        [_, BMips | _] ->
712            {ok, {CPU, BMips}};
713        _ ->
714            {ok, CPU}
715    catch
716        _:_:_ ->
717            {ok, CPU}
718    end.
719
720%% We *add* the value this return to the Factor.
721linux_which_meminfo() ->
722    try [string:trim(S) || S <- string:tokens(os:cmd("grep MemTotal /proc/meminfo"), [$:])] of
723        [_, MemTotal] ->
724            io:format("Memory:"
725                      "~n   ~s"
726                      "~n", [MemTotal]),
727            case string:tokens(MemTotal, [$ ]) of
728                [MemSzStr, MemUnit] ->
729                    MemSz2 = list_to_integer(MemSzStr),
730                    MemSz3 =
731                        case string:to_lower(MemUnit) of
732                            "kb" ->
733                                MemSz2;
734                            "mb" ->
735                                MemSz2*1024;
736                            "gb" ->
737                                MemSz2*1024*1024;
738                            _ ->
739                                throw(noinfo)
740                        end,
741                    if
742                        (MemSz3 >= 8388608) ->
743                            0;
744                        (MemSz3 >= 4194304) ->
745                            1;
746                        (MemSz3 >= 2097152) ->
747                            3;
748                        true ->
749                            5
750                    end;
751                _X ->
752                    0
753            end;
754        _ ->
755            0
756    catch
757        _:_:_ ->
758            0
759    end.
760
761
762%% Just to be clear: This is ***not*** scientific...
763analyze_and_print_openbsd_host_info(Version) ->
764    io:format("OpenBSD:"
765              "~n   Version: ~p"
766              "~n", [Version]),
767    Extract =
768        fun(Key) ->
769                string:tokens(string:trim(os:cmd("sysctl " ++ Key)), [$=])
770        end,
771    try
772        begin
773            CPU =
774                case Extract("hw.model") of
775                    ["hw.model", Model] ->
776                        string:trim(Model);
777                    _ ->
778                        "-"
779                end,
780            CPUSpeed =
781                case Extract("hw.cpuspeed") of
782                    ["hw.cpuspeed", Speed] ->
783                        list_to_integer(Speed);
784                    _ ->
785                        -1
786                end,
787            NCPU =
788                case Extract("hw.ncpufound") of
789                    ["hw.ncpufound", N] ->
790                        list_to_integer(N);
791                    _ ->
792                        -1
793                end,
794            Memory =
795                case Extract("hw.physmem") of
796                    ["hw.physmem", PhysMem] ->
797                        list_to_integer(PhysMem) div 1024;
798                    _ ->
799                        -1
800                end,
801            io:format("CPU:"
802                      "~n   Model: ~s"
803                      "~n   Speed: ~w"
804                      "~n   N:     ~w"
805                      "~nMemory:"
806                      "~n   ~w KB"
807                      "~n", [CPU, CPUSpeed, NCPU, Memory]),
808            CPUFactor =
809                if
810                    (CPUSpeed =:= -1) ->
811                        1;
812                    (CPUSpeed >= 2000) ->
813                        if
814                            (NCPU >= 4) ->
815                                1;
816                            (NCPU >= 2) ->
817                                2;
818                            true ->
819                                3
820                        end;
821                    true ->
822                        if
823                            (NCPU >= 4) ->
824                                2;
825                            (NCPU >= 2) ->
826                                3;
827                            true ->
828                                4
829                        end
830                end,
831            MemAddFactor =
832                if
833                    (Memory =:= -1) ->
834                        0;
835                    (Memory >= 8388608) ->
836                        0;
837                    (Memory >= 4194304) ->
838                        1;
839                    (Memory >= 2097152) ->
840                        2;
841                    true ->
842                        3
843                end,
844            CPUFactor + MemAddFactor
845        end
846    catch
847        _:_:_ ->
848            1
849    end.
850
851
852analyze_and_print_freebsd_host_info(Version) ->
853    io:format("FreeBSD:"
854              "~n   Version: ~p"
855              "~n", [Version]),
856    %% This test require that the program 'sysctl' is in the path.
857    %% First test with 'which sysctl', if that does not work
858    %% try with 'which /sbin/sysctl'. If that does not work either,
859    %% we skip the test...
860    try
861        begin
862            SysCtl =
863                case string:trim(os:cmd("which sysctl")) of
864                    [] ->
865                        case string:trim(os:cmd("which /sbin/sysctl")) of
866                            [] ->
867                                throw(sysctl);
868                            SC2 ->
869                                SC2
870                        end;
871                    SC1 ->
872                        SC1
873                end,
874            Extract =
875                fun(Key) ->
876                        string:tokens(string:trim(os:cmd(SysCtl ++ " " ++ Key)),
877                                      [$:])
878                end,
879            CPU      = analyze_freebsd_cpu(Extract),
880            CPUSpeed = analyze_freebsd_cpu_speed(Extract),
881            NCPU     = analyze_freebsd_ncpu(Extract),
882            Memory   = analyze_freebsd_memory(Extract),
883            io:format("CPU:"
884                      "~n   Model:          ~s"
885                      "~n   Speed:          ~w"
886                      "~n   N:              ~w"
887                      "~n   Num Schedulers: ~w"
888                      "~nMemory:"
889                      "~n   ~w KB"
890                      "~n",
891                      [CPU, CPUSpeed, NCPU,
892                       erlang:system_info(schedulers), Memory]),
893            CPUFactor =
894                if
895                    (CPUSpeed =:= -1) ->
896                        1;
897                    (CPUSpeed >= 2000) ->
898                        if
899                            (NCPU >= 4) ->
900                                1;
901                            (NCPU >= 2) ->
902                                2;
903                            true ->
904                                3
905                        end;
906                    true ->
907                        if
908                            (NCPU =:= -1) ->
909                                1;
910                            (NCPU >= 4) ->
911                                2;
912                            (NCPU >= 2) ->
913                                3;
914                            true ->
915                                4
916                        end
917                end,
918            MemAddFactor =
919                if
920                    (Memory =:= -1) ->
921                        0;
922                    (Memory >= 8388608) ->
923                        0;
924                    (Memory >= 4194304) ->
925                        1;
926                    (Memory >= 2097152) ->
927                        2;
928                    true ->
929                        3
930                end,
931            CPUFactor + MemAddFactor
932        end
933    catch
934        _:_:_ ->
935            io:format("CPU:"
936                      "~n   Num Schedulers: ~w"
937                      "~n", [erlang:system_info(schedulers)]),
938            case erlang:system_info(schedulers) of
939                1 ->
940                    10;
941                2 ->
942                    5;
943                _ ->
944                    2
945            end
946    end.
947
948analyze_freebsd_cpu(Extract) ->
949    analyze_freebsd_item(Extract, "hw.model", fun(X) -> X end, "-").
950
951analyze_freebsd_cpu_speed(Extract) ->
952    analyze_freebsd_item(Extract,
953                         "hw.clockrate",
954                         fun(X) -> list_to_integer(X) end,
955                         -1).
956
957analyze_freebsd_ncpu(Extract) ->
958    analyze_freebsd_item(Extract,
959                         "hw.ncpu",
960                         fun(X) -> list_to_integer(X) end,
961                         -1).
962
963analyze_freebsd_memory(Extract) ->
964    analyze_freebsd_item(Extract,
965                         "hw.physmem",
966                         fun(X) -> list_to_integer(X) div 1024 end,
967                         -1).
968
969analyze_freebsd_item(Extract, Key, Process, Default) ->
970    try
971        begin
972            case Extract(Key) of
973                [Key, Model] ->
974                    Process(string:trim(Model));
975                _ ->
976                    Default
977            end
978        end
979    catch
980        _:_:_ ->
981            Default
982    end.
983
984
985analyze_and_print_solaris_host_info(Version) ->
986    Release =
987        case file:read_file_info("/etc/release") of
988            {ok, _} ->
989                case [string:trim(S) || S <- string:tokens(os:cmd("cat /etc/release"), [$\n])] of
990                    [Rel | _] ->
991                        Rel;
992                    _ ->
993                        "-"
994                end;
995            _ ->
996                "-"
997        end,
998    %% Display the firmware device tree root properties (prtconf -b)
999    Props = [list_to_tuple([string:trim(PS) || PS <- Prop]) ||
1000                Prop <- [string:tokens(S, [$:]) ||
1001                            S <- string:tokens(os:cmd("prtconf -b"), [$\n])]],
1002    BannerName = case lists:keysearch("banner-name", 1, Props) of
1003                     {value, {_, BN}} ->
1004                         string:trim(BN);
1005                     _ ->
1006                         "-"
1007                 end,
1008    InstructionSet =
1009        case string:trim(os:cmd("isainfo -k")) of
1010            "Pseudo-terminal will not" ++ _ ->
1011                "-";
1012            IS ->
1013                IS
1014        end,
1015    PtrConf = [list_to_tuple([string:trim(S) || S <- Items]) || Items <- [string:tokens(S, [$:]) || S <- string:tokens(os:cmd("prtconf"), [$\n])], length(Items) > 1],
1016    SysConf =
1017        case lists:keysearch("System Configuration", 1, PtrConf) of
1018            {value, {_, SC}} ->
1019                SC;
1020            _ ->
1021                "-"
1022        end,
1023    NumPhysProc =
1024        begin
1025            NPPStr = string:trim(os:cmd("psrinfo -p")),
1026            try list_to_integer(NPPStr) of
1027                _ ->
1028                    NPPStr
1029            catch
1030                _:_:_ ->
1031                    "-"
1032            end
1033        end,
1034    NumProc = try integer_to_list(length(string:tokens(os:cmd("psrinfo"), [$\n]))) of
1035                  NPStr ->
1036                      NPStr
1037              catch
1038                  _:_:_ ->
1039                      "-"
1040              end,
1041    MemSz =
1042        case lists:keysearch("Memory size", 1, PtrConf) of
1043            {value, {_, MS}} ->
1044                MS;
1045            _ ->
1046                "-"
1047        end,
1048    io:format("Solaris: ~s"
1049              "~n   Release:         ~s"
1050              "~n   Banner Name:     ~s"
1051              "~n   Instruction Set: ~s"
1052              "~n   CPUs:            ~s (~s)"
1053              "~n   System Config:   ~s"
1054              "~n   Memory Size:     ~s"
1055              "~n   Num Schedulers:  ~s"
1056              "~n~n", [Version, Release, BannerName, InstructionSet,
1057                       NumPhysProc, NumProc,
1058                       SysConf, MemSz,
1059                       str_num_schedulers()]),
1060    MemFactor =
1061        try string:tokens(MemSz, [$ ]) of
1062            [SzStr, "Mega" ++ _] ->
1063                try list_to_integer(SzStr) of
1064                    Sz when Sz > 8192 ->
1065                        0;
1066                    Sz when Sz > 4096 ->
1067                        1;
1068                    Sz when Sz > 2048 ->
1069                        2;
1070                    _ ->
1071                        5
1072                catch
1073                    _:_:_ ->
1074                        10
1075                end;
1076            [SzStr, "Giga" ++ _] ->
1077                try list_to_integer(SzStr) of
1078                    Sz when Sz > 8 ->
1079                        0;
1080                    Sz when Sz > 4 ->
1081                        1;
1082                    Sz when Sz > 2 ->
1083                        2;
1084                    _ ->
1085                        5
1086                catch
1087                    _:_:_ ->
1088                        10
1089                end;
1090            _ ->
1091                10
1092        catch
1093            _:_:_ ->
1094                10
1095        end,
1096    try erlang:system_info(schedulers) of
1097        1 ->
1098            10;
1099        2 ->
1100            5;
1101        N when (N =< 6) ->
1102            2;
1103        _ ->
1104            1
1105    catch
1106        _:_:_ ->
1107            10
1108    end + MemFactor.
1109
1110
1111analyze_and_print_win_host_info(Version) ->
1112    SysInfo    = which_win_system_info(),
1113    OsName     = win_sys_info_lookup(os_name,             SysInfo),
1114    OsVersion  = win_sys_info_lookup(os_version,          SysInfo),
1115    SysMan     = win_sys_info_lookup(system_manufacturer, SysInfo),
1116    NumProcs   = win_sys_info_lookup(num_processors,      SysInfo),
1117    TotPhysMem = win_sys_info_lookup(total_phys_memory,   SysInfo),
1118    io:format("Windows: ~s"
1119              "~n   OS Version:             ~s (~p)"
1120              "~n   System Manufacturer:    ~s"
1121              "~n   Number of Processor(s): ~s"
1122              "~n   Total Physical Memory:  ~s"
1123              "~n", [OsName, OsVersion, Version, SysMan, NumProcs, TotPhysMem]),
1124    MemFactor =
1125        try
1126            begin
1127                [MStr, MUnit|_] =
1128                    string:tokens(lists:delete($,, TotPhysMem), [$\ ]),
1129                case string:to_lower(MUnit) of
1130                    "gb" ->
1131                        try list_to_integer(MStr) of
1132                            M when M > 8 ->
1133                                0;
1134                            M when M > 4 ->
1135                                1;
1136                            M when M > 2 ->
1137                                2;
1138                            _ ->
1139                                5
1140                        catch
1141                            _:_:_ ->
1142                                10
1143                        end;
1144                    "mb" ->
1145                        try list_to_integer(MStr) of
1146                            M when M > 8192 ->
1147                                0;
1148                            M when M > 4096 ->
1149                                1;
1150                            M when M > 2048 ->
1151                                2;
1152                            _ ->
1153                                5
1154                        catch
1155                            _:_:_ ->
1156                                10
1157                        end;
1158                    _ ->
1159                        10
1160                end
1161            end
1162        catch
1163            _:_:_ ->
1164                10
1165        end,
1166    CPUFactor =
1167        case erlang:system_info(schedulers) of
1168            1 ->
1169                10;
1170            2 ->
1171                5;
1172            _ ->
1173                2
1174        end,
1175    CPUFactor + MemFactor.
1176
1177win_sys_info_lookup(Key, SysInfo) ->
1178    win_sys_info_lookup(Key, SysInfo, "-").
1179
1180win_sys_info_lookup(Key, SysInfo, Def) ->
1181    case lists:keysearch(Key, 1, SysInfo) of
1182        {value, {Key, Value}} ->
1183            Value;
1184        false ->
1185            Def
1186    end.
1187
1188%% This function only extracts the prop we actually care about!
1189which_win_system_info() ->
1190    SysInfo = os:cmd("systeminfo"),
1191    try process_win_system_info(string:tokens(SysInfo, [$\r, $\n]), [])
1192    catch
1193        _:_:_ ->
1194            io:format("Failed process System info: "
1195                      "~s~n", [SysInfo]),
1196            []
1197    end.
1198
1199process_win_system_info([], Acc) ->
1200    Acc;
1201process_win_system_info([H|T], Acc) ->
1202    case string:tokens(H, [$:]) of
1203        [Key, Value] ->
1204            case string:to_lower(Key) of
1205                "os name" ->
1206                    process_win_system_info(T,
1207                                            [{os_name, string:trim(Value)}|Acc]);
1208                "os version" ->
1209                    process_win_system_info(T,
1210                                            [{os_version, string:trim(Value)}|Acc]);
1211                "system manufacturer" ->
1212                    process_win_system_info(T,
1213                                            [{system_manufacturer, string:trim(Value)}|Acc]);
1214                "processor(s)" ->
1215                    [NumProcStr|_] = string:tokens(Value, [$\ ]),
1216                    T2 = lists:nthtail(list_to_integer(NumProcStr), T),
1217                    process_win_system_info(T2,
1218                                            [{num_processors, NumProcStr}|Acc]);
1219                "total physical memory" ->
1220                    process_win_system_info(T,
1221                                            [{total_phys_memory, string:trim(Value)}|Acc]);
1222                _ ->
1223                    process_win_system_info(T, Acc)
1224            end;
1225        _ ->
1226            process_win_system_info(T, Acc)
1227    end.
1228
1229
1230
1231%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1232%% Set kill timer
1233
1234set_kill_timer(Config) ->
1235    case init:get_argument(megaco_test_timeout) of
1236	{ok, _} ->
1237	    Config;
1238	_ ->
1239	    Time =
1240		case lookup_config(tc_timeout, Config) of
1241		    [] ->
1242			timer:minutes(5);
1243		    ConfigTime when is_integer(ConfigTime) ->
1244			ConfigTime
1245		end,
1246	    Dog = test_server:timetrap(Time),
1247	    [{kill_timer, Dog}|Config]
1248
1249
1250    end.
1251
1252reset_kill_timer(Config) ->
1253    case lists:keysearch(kill_timer, 1, Config) of
1254	{value, {kill_timer, Dog}} ->
1255	    test_server:timetrap_cancel(Dog),
1256	    lists:keydelete(kill_timer, 1, Config);
1257	_ ->
1258	    Config
1259    end.
1260
1261
1262%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1263
1264try_tc(TCName, Name, Verbosity, Pre, Case, Post)
1265  when is_function(Pre, 0)  andalso
1266       is_function(Case, 1) andalso
1267       is_function(Post, 1) ->
1268    process_flag(trap_exit, true),
1269    put(verbosity, Verbosity),
1270    put(sname,     Name),
1271    put(tc,        TCName),
1272    p("try_tc -> starting: try pre"),
1273    try Pre() of
1274        State ->
1275            p("try_tc -> pre done: try test case"),
1276            try Case(State) of
1277                Res ->
1278                    p("try_tc -> test case done: try post"),
1279                    (catch Post(State)),
1280                    p("try_tc -> done"),
1281                    Res
1282            catch
1283                throw:{skip, _} = SKIP:_ ->
1284                    p("try_tc -> test case (throw) skip: try post"),
1285                    (catch Post(State)),
1286                    p("try_tc -> test case (throw) skip: done"),
1287                    SKIP;
1288                exit:{skip, _} = SKIP:_ ->
1289                    p("try_tc -> test case (exit) skip: try post"),
1290                    (catch Post(State)),
1291                    p("try_tc -> test case (exit) skip: done"),
1292                    SKIP;
1293                C:E:S ->
1294                    p("try_tc -> test case failed: try post"),
1295                    (catch Post(State)),
1296                    case megaco_test_global_sys_monitor:events() of
1297                        [] ->
1298                            p("try_tc -> test case failed: done"),
1299                            exit({case_catched, C, E, S});
1300                        SysEvs ->
1301                            p("try_tc -> test case failed with system event(s): "
1302                              "~n   ~p", [SysEvs]),
1303                            {skip, "TC failure with system events"}
1304                    end
1305            end
1306    catch
1307        throw:{skip, _} = SKIP:_ ->
1308            p("try_tc -> pre (throw) skip"),
1309            SKIP;
1310        exit:{skip, _} = SKIP:_ ->
1311            p("try_tc -> pre (exit) skip"),
1312            SKIP;
1313        C:E:S ->
1314            case megaco_test_global_sys_monitor:events() of
1315                [] ->
1316                    p("try_tc -> pre failed: done"),
1317                    exit({pre_catched, C, E, S});
1318                SysEvs ->
1319                    p("try_tc -> pre failed with system event(s): "
1320                      "~n   ~p", [SysEvs]),
1321                    {skip, "TC pre failure with system events"}
1322            end
1323    end.
1324
1325
1326
1327%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1328
1329prepare_test_case(Actions, N, Config, File, Line) ->
1330    OrigNodes = lookup_config(nodes, Config),
1331    TestNodes = lookup_config(nodenames, Config), %% For testserver
1332    This      = node(),
1333    SomeNodes = OrigNodes ++ (TestNodes -- OrigNodes),
1334    AllNodes  = [This | (SomeNodes -- [This])],
1335    Nodes     = pick_n_nodes(N, AllNodes, File, Line),
1336    start_nodes(Nodes, File, Line),
1337    do_prepare_test_case(Actions, Nodes, Config, File, Line).
1338
1339do_prepare_test_case([init | Actions], Nodes, Config, File, Line) ->
1340    process_flag(trap_exit, true),
1341    megaco_test_lib:flush(),
1342    do_prepare_test_case(Actions, Nodes, Config, File, Line);
1343do_prepare_test_case([{stop_app, App} | Actions], Nodes, Config, File, Line) ->
1344    _Res = rpc:multicall(Nodes, application, stop, [App]),
1345    do_prepare_test_case(Actions, Nodes, Config, File, Line);
1346do_prepare_test_case([], Nodes, _Config, _File, _Line) ->
1347    Nodes.
1348
1349pick_n_nodes(all, AllNodes, _File, _Line) ->
1350    AllNodes;
1351pick_n_nodes(N, AllNodes, _File, _Line)
1352  when is_integer(N) andalso (length(AllNodes) >= N) ->
1353    AllNodes -- lists:nthtail(N, AllNodes);
1354pick_n_nodes(N, AllNodes, File, Line) ->
1355    fatal_skip({too_few_nodes, N, AllNodes}, File, Line).
1356
1357lookup_config(Key,Config) ->
1358    case lists:keysearch(Key, 1, Config) of
1359	{value,{Key,Val}} ->
1360	    Val;
1361	_ ->
1362	    []
1363    end.
1364
1365mk_nodes(N) when (N > 0) ->
1366    mk_nodes(N, []).
1367
1368mk_nodes(0, Nodes) ->
1369    Nodes;
1370mk_nodes(N, []) ->
1371    mk_nodes(N - 1, [node()]);
1372mk_nodes(N, Nodes) when N > 0 ->
1373    Head = hd(Nodes),
1374    [Name, Host] = node_to_name_and_host(Head),
1375    Nodes ++ [mk_node(I, Name, Host) || I <- lists:seq(1, N)].
1376
1377mk_node(N, Name, Host) ->
1378    list_to_atom(lists:concat([Name ++ integer_to_list(N) ++ "@" ++ Host])).
1379
1380%% Returns [Name, Host]
1381node_to_name_and_host(Node) ->
1382    string:tokens(atom_to_list(Node), [$@]).
1383
1384
1385start_nodes(Nodes, File, Line) when is_list(Nodes) ->
1386    start_nodes(Nodes, false, File, Line).
1387
1388start_nodes(Nodes, Force, File, Line)
1389  when is_list(Nodes) andalso is_boolean(Force) ->
1390    start_nodes(Nodes, Force, File, Line, []).
1391
1392start_nodes([], _Force, _File, _Line, _Started) ->
1393    ok;
1394start_nodes([Node|Nodes], Force, File, Line, Started) ->
1395    try start_node(Node, Force, true, File, Line) of
1396        ok ->
1397            start_nodes(Nodes, Force, File, Line, [Node|Started])
1398    catch
1399        exit:{skip, _} = SKIP:_ ->
1400            (catch stop_nodes(lists:reverse(Started), File, Line)),
1401            exit(SKIP);
1402        C:E:S ->
1403            (catch stop_nodes(lists:reverse(Started), File, Line)),
1404            erlang:raise(C, E, S)
1405    end.
1406
1407start_node(Node, File, Line) ->
1408    start_node(Node, false, false, File, Line).
1409
1410start_node(Node, Force, File, Line)
1411  when is_atom(Node) andalso is_boolean(Force) ->
1412    start_node(Node, Force, false, File, Line).
1413
1414start_node(Node, Force, Retry, File, Line) ->
1415    case net_adm:ping(Node) of
1416        %% Do not require a *new* node
1417	pong when (Force =:= false) ->
1418            p("node ~p already running", [Node]),
1419	    ok;
1420
1421        %% Do require a *new* node, so kill this one and try again
1422	pong when ((Force =:= true) andalso (Retry =:= true)) ->
1423            e("node ~p already running - kill and retry", [Node]),
1424            case stop_node(Node) of
1425                ok ->
1426                    start_node(Node, Force, false, File, Line);
1427                error ->
1428                    e("node ~p already running - failed kill (no retry)", [Node]),
1429                    fatal_skip({node_already_running, Node}, File, Line)
1430            end;
1431
1432        %% Do require a *new* node, but no retry so give up and fail
1433        pong when (Force =:= true) ->
1434            e("node ~p already running", [Node]),
1435            fatal_skip({node_already_running, Node}, File, Line);
1436
1437        % Not (yet) running
1438        pang ->
1439	    [Name, Host] = node_to_name_and_host(Node),
1440            Pa = filename:dirname(code:which(?MODULE)),
1441            Args = " -pa " ++ Pa ++
1442                " -s " ++ atom_to_list(megaco_test_sys_monitor) ++ " start" ++
1443                " -s global sync",
1444            p("try start node ~p", [Node]),
1445	    case slave:start_link(Host, Name, Args) of
1446		{ok, NewNode} when NewNode =:= Node ->
1447                    p("node ~p started - now set path, cwd and sync", [Node]),
1448		    Path = code:get_path(),
1449		    {ok, Cwd} = file:get_cwd(),
1450		    true = rpc:call(Node, code, set_path, [Path]),
1451		    ok = rpc:call(Node, file, set_cwd, [Cwd]),
1452		    true = rpc:call(Node, code, set_path, [Path]),
1453		    {_, []} = rpc:multicall(global, sync, []),
1454		    ok;
1455		Other ->
1456                    e("failed starting node ~p: ~p", [Node, Other]),
1457		    fatal_skip({cannot_start_node, Node, Other}, File, Line)
1458	    end
1459    end.
1460
1461
1462stop_nodes(Nodes, File, Line) when is_list(Nodes) ->
1463    stop_nodes(Nodes, [], File, Line).
1464
1465stop_nodes([], [], _File, _Line) ->
1466    ok;
1467stop_nodes([], StillRunning, File, Line) ->
1468    e("Failed stopping nodes: "
1469      "~n   ~p", [StillRunning]),
1470    fatal_skip({failed_stop_nodes, lists:reverse(StillRunning)}, File, Line);
1471stop_nodes([Node|Nodes], Acc, File, Line) ->
1472    case stop_node(Node) of
1473        ok ->
1474            stop_nodes(Nodes, Acc, File, Line);
1475        error ->
1476            stop_nodes(Nodes, [Node|Acc], File, Line)
1477    end.
1478
1479
1480stop_node(Node, File, Line) when is_atom(Node) ->
1481    p("try stop node ~p", [Node]),
1482    case stop_node(Node) of
1483        ok ->
1484            ok;
1485        error ->
1486            fatal_skip({failed_stop_node, Node}, File, Line)
1487    end.
1488
1489stop_node(Node) ->
1490    p("try stop node ~p", [Node]),
1491    erlang:monitor_node(Node, true),
1492    rpc:call(Node, erlang, halt, []),
1493    receive
1494        {nodedown, Node} ->
1495            ok
1496    after 10000 ->
1497            e("failed stop node ~p", [Node]),
1498            error
1499    end.
1500
1501
1502
1503%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
1504
1505f(F, A) ->
1506    lists:flatten(io_lib:format(F, A)).
1507
1508e(F, A) ->
1509    print("ERROR", F, A).
1510
1511p(F) ->
1512    p(F, []).
1513
1514p(F, A) ->
1515    print("INFO", F, A).
1516
1517print(Pre, F, A) ->
1518    io:format("*** [~s] [~s] ~p " ++ F ++ "~n", [?FTS(), Pre, self() | A]).
1519
1520