1%%
2%% %CopyrightBegin%
3%%
4%% Copyright Ericsson AB 2006-2021. 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-module(observer_SUITE).
22-include_lib("common_test/include/ct.hrl").
23-include_lib("wx/include/wx.hrl").
24-include_lib("observer/src/observer_tv.hrl").
25
26-define(ID_LOGVIEW, 5).
27
28%% Test server specific exports
29-export([all/0, suite/0,groups/0]).
30-export([init_per_testcase/2, end_per_testcase/2,
31	 init_per_group/2, end_per_group/2,
32	 init_per_suite/1, end_per_suite/1
33	]).
34
35%% Test cases
36-export([app_file/1, appup_file/1,
37	 basic/1, process_win/1, table_win/1,
38         port_win_when_tab_not_initiated/1
39	]).
40
41%% Default timetrap timeout (set in init_per_testcase)
42-define(default_timeout, test_server:minutes(2)).
43
44-define(SECS(__S__), timer:seconds(__S__)).
45
46-define(P(F),    print(F)).
47-define(P(F, A), print(F, A)).
48
49
50suite() -> [{timetrap, {minutes, 5}},
51            {ct_hooks,[ts_install_cth]}].
52
53all() ->
54    [app_file, appup_file, {group, gui}].
55
56groups() ->
57    [{gui, [],
58      [basic,
59       process_win,
60       table_win,
61       port_win_when_tab_not_initiated
62      ]
63     }].
64
65
66init_per_suite(Config) ->
67    ?P("init_per_suite -> entry with"
68       "~n   Config: ~p", [Config]),
69    Config.
70
71end_per_suite(Config) ->
72    ?P("end_per_suite -> entry with"
73       "~n   Config: ~p", [Config]),
74    ok.
75
76
77init_per_testcase(Case, Config) ->
78    ?P("init_per_testcase(~w) -> entry with"
79       "~n   Config: ~p", [Case, Config]),
80    Dog = test_server:timetrap(?default_timeout),
81    [{watchdog, Dog} | Config].
82
83end_per_testcase(Case, Config) ->
84    ?P("end_per_testcase(~w) -> entry with"
85       "~n   Config: ~p", [Case, Config]),
86    Dog = ?config(watchdog, Config),
87    test_server:timetrap_cancel(Dog),
88    ok.
89
90
91init_per_group(gui = Group, Config) ->
92    ?P("init_per_group(~w) -> entry with"
93       "~n   Config: ~p", [Group, Config]),
94    try
95	case os:type() of
96	    {unix,darwin} ->
97		?P("init_per_group(~w) -> skip", [Group]),
98		exit("Can not test on MacOSX");
99	    {unix, _} ->
100		?P("init_per_group(~w) -> DISPLAY ~s",
101                   [Group, os:getenv("DISPLAY")]),
102		case ct:get_config(xserver, none) of
103		    none -> ignore;
104		    Server -> os:putenv("DISPLAY", Server)
105		end;
106	    _ -> ignore
107	end,
108	wx:new(),
109	wx:destroy(),
110	Config
111    catch
112	_:undef ->
113            ?P("init_per_group(~w) -> undef", [Group]),
114	    {skipped, "No wx compiled for this platform"};
115	C:Reason:S ->
116            ?P("init_per_group(~w) -> "
117               "~n   Class:  ~p"
118               "~n   Reason: ~p"
119               "~n   Stack:  ~p", [Group, C, Reason, S]),
120	    SkipReason = io_lib:format("Start wx failed: ~p", [Reason]),
121	    {skipped, lists:flatten(SkipReason)}
122    end.
123
124end_per_group(_Group, _Config) ->
125    ?P("end_per_group(~w) -> entry with"
126       "~n   Config: ~p", [_Group, _Config]),
127    ok.
128
129
130app_file(suite) ->
131    [];
132app_file(doc) ->
133    ["Testing .app file"];
134app_file(Config) when is_list(Config) ->
135    ?line ok = test_server:app_test(observer),
136    ok.
137
138%% Testing .appup file
139appup_file(Config) when is_list(Config) ->
140    ok = test_server:appup_test(observer).
141
142-define(DBG(Foo), io:format("~p: ~p~n",[?LINE, catch Foo])).
143
144basic(suite) -> [];
145basic(doc) -> [""];
146basic(Config) when is_list(Config) ->
147    ?P("basic -> entry with"
148       "~n   Config: ~p", [Config]),
149
150    %% Start these before
151    ?P("basic -> try wx new"),
152    wx:new(),
153    ?P("basic -> try wx destroy"),
154    wx:destroy(),
155    timer:send_after(100, "foobar"),
156    ?P("basic -> try start distribution"),
157    {foo, node@machine} ! dummy_msg,  %% start distribution stuff
158    %% Otherwise ever lasting servers gets added to procs
159    ?P("basic -> procs before"),
160    ProcsBefore = processes(),
161    ProcInfoBefore = [{P,process_info(P)} || P <- ProcsBefore],
162    NumProcsBefore = length(ProcsBefore),
163
164    ?P("basic -> try start observer"),
165    ok = observer:start(),
166    Notebook = setup_whitebox_testing(),
167    ?P("basic -> Notebook ~p", [Notebook]),
168
169    Count = wxNotebook:getPageCount(Notebook),
170    ?P("basic -> page count ~p", [Count]),
171    true = Count >= 7,
172    ?P("basic -> check selection (=0)"),
173    0 = wxNotebook:getSelection(Notebook),
174    ?P("basic -> wait some time..."),
175    timer:sleep(500),
176    Check = fun(N, TestMore) ->
177		    TestMore andalso
178			test_page(wxNotebook:getPageText(Notebook, N),
179				  wxNotebook:getCurrentPage(Notebook)),
180		    timer:sleep(200),
181		    ok = wxNotebook:advanceSelection(Notebook)
182	    end,
183    %% Just verify that we can toggle through all pages
184    ?P("basic -> try verify that we can toggle through all pages"),
185    [_|_] = [Check(N, false) || N <- lists:seq(1, Count)],
186    %% Cause it to resize
187    ?P("basic -> try resize"),
188    Frame = get_top_level_parent(Notebook),
189    {W,H} = wxWindow:getSize(Frame),
190    wxWindow:setSize(Frame, W+10, H+10),
191    ?P("basic -> try verify that we resized"),
192    [_|_] = [Check(N, true) || N <- lists:seq(0, Count-1)],
193
194    ?P("basic -> try stop observer (async)"),
195    ok = observer:stop(),
196    timer:sleep(2000), %% stop is async
197    ?P("basic -> try verify observer stopped"),
198    ProcsAfter = processes(),
199    NumProcsAfter = length(ProcsAfter),
200    if NumProcsAfter =/= NumProcsBefore ->
201            BeforeNotAfter = ProcsBefore -- ProcsAfter,
202            ?P("basic -> *not* fully stopped:"
203               "~n   Number of Procs before: ~p"
204               "~n   Number of Procs after:  ~p",
205               [NumProcsAfter, NumProcsBefore]),
206	    ct:log("Before but not after:~n~p~n",
207		   [[{P,I} || {P,I} <- ProcInfoBefore,
208                              lists:member(P,BeforeNotAfter)]]),
209	    ct:log("After but not before:~n~p~n",
210		   [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
211	    ensure_observer_stopped(),
212	    ct:fail("leaking processes");
213       true ->
214	    ok
215    end,
216    ?P("ensure observer stopped"),
217    ensure_observer_stopped(?SECS(2)),
218    ?P("basic -> done"),
219    ok.
220
221test_page("Load Charts" ++ _, _Window) ->
222    ?P("test_page(load charts) -> entry"),
223    %% Just let it display some info and hopefully it doesn't crash
224    timer:sleep(2000),
225    ok;
226test_page("Applications" ++ _, _Window) ->
227    ?P("test_page(applications) -> entry"),
228    ok = application:start(mnesia),
229    timer:sleep(1000),  %% Give it time to refresh
230    Active = get_active(),
231    FakeEv = #wx{event=#wxCommand{type=command_listbox_selected, cmdString="mnesia"}},
232    Active ! FakeEv,
233    timer:sleep(1000),  %% Give it time to refresh
234    ok = application:stop(mnesia),
235    timer:sleep(1000),  %% Give it time to refresh
236    ok;
237
238test_page("Processes" ++ _, _Window) ->
239    ?P("test_page(processes) -> entry"),
240    timer:sleep(500),  %% Give it time to refresh
241    Active = get_active(),
242    Active ! refresh_interval,
243    ChangeSort = fun(N) ->
244			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
245			 Active ! FakeEv,
246			 timer:sleep(200)
247		 end,
248    [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
249    Focus = #wx{event=#wxList{type=command_list_item_focused, itemIndex=2}},
250    Active ! Focus,
251    Activate = #wx{event=#wxList{type=command_list_item_activated}},
252    Active ! Activate,
253    timer:sleep(1000),  %% Give it time to refresh
254    ok;
255
256test_page("Ports" ++ _, _Window) ->
257    ?P("test_page(ports) -> entry"),
258    timer:sleep(500),  %% Give it time to refresh
259    Active = get_active(),
260    Active ! refresh_interval,
261    ChangeSort = fun(N) ->
262			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
263			 Active ! FakeEv,
264			 timer:sleep(200)
265		 end,
266    [ChangeSort(N) || N <- lists:seq(1,4) ++ [0]],
267    Activate = #wx{event=#wxList{type=command_list_item_activated,
268				 itemIndex=2}},
269    Active ! Activate,
270    timer:sleep(1000),  %% Give it time to refresh
271    ok;
272
273test_page("Sockets" ++ _, _Window) ->
274    ?P("test_page(sockets) -> entry"),
275    %% (maybe) We cannot count on having any 'sockets', so create some.
276    %% We don't actually need more then a 4 open *existing* sockets.
277    Sockets = sock_create([{[any],         {inet,  stream,    tcp}},
278                           {[any],         {inet,  stream,    tcp}},
279                           {[any],         {inet,  dgram,     udp}},
280                           {[any],         {inet,  dgram,     udp}},
281                           {[ipv6],        {inet6, stream,    tcp}},
282                           {[ipv6],        {inet6, dgram,     udp}},
283                           {[sctp],        {inet,  seqpacket, sctp}},
284                           {[ipv6, sctp],  {inet6, seqpacket, sctp}}]),
285    timer:sleep(500),  %% Give it time to refresh
286    Active = get_active(),
287    Active ! refresh_interval,
288    %% And this stuff only works if we actually have some (>= 4) sockets
289    %% so check that we do
290    try socket:number_of() of
291        NumSocks when NumSocks >= 4 ->
292            ChangeSort =
293                fun(N) ->
294                        FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
295                        Active ! FakeEv,
296                        timer:sleep(200)
297                end,
298            [ChangeSort(N) || N <- lists:seq(1,4) ++ [0]],
299            Activate = #wx{event=#wxList{type=command_list_item_activated,
300                                         itemIndex=2}},
301            Active ! Activate,
302            timer:sleep(1000);  %% Give it time to refresh
303        _ ->
304            ?P("not enough sockets - skip list-col-click test")
305    catch
306        _:_:_ ->
307            ?P("'socket' not supported")
308    end,
309    %% (maybe) Cleanup
310    sock_close(Sockets),
311    ok;
312
313test_page("Table" ++ _, _Window) ->
314    ?P("test_page(table) -> entry"),
315    Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
316    Table = lists:nth(3, Tables),
317    ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
318
319    Active = get_active(),
320    Active ! refresh_interval,
321    ChangeSort = fun(N) ->
322			 FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
323			 Active ! FakeEv,
324			 timer:sleep(200)
325		 end,
326    [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
327    timer:sleep(1000),
328    Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}},
329    Active ! Focus,
330    Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}},
331    Active ! Activate,
332
333    Info = 407, %% whitebox...
334    Active ! #wx{id=Info},
335    timer:sleep(1000),
336    ok;
337
338test_page("Trace Overview" ++ _, _Window) ->
339    ?P("test_page(trace overview) -> entry"),
340    timer:sleep(500),  %% Give it time to refresh
341    Active = get_active(),
342    Active ! refresh_interval,
343    timer:sleep(1000),  %% Give it time to refresh
344    ok;
345
346test_page(Title, Window) ->
347    ?P("test_page -> entry with"
348       "~n   Title:  ~p"
349       "~n   Window: ~p", [Title, Window]),
350    %% Just let it display some info and hopefully it doesn't crash
351    timer:sleep(1000),
352    ok.
353
354
355process_win(suite) -> [];
356process_win(doc) -> [""];
357process_win(Config) when is_list(Config) ->
358    ?P("process_win -> entry"),
359    % Stop SASL if already started
360    SaslStart = case whereis(sasl_sup) of
361                  undefined -> false;
362                  _         -> application:stop(sasl),
363                               true
364                end,
365    % Define custom sasl and log_mf_h app vars
366    Privdir=?config(priv_dir,Config),
367    application:set_env(sasl, sasl_error_logger, tty),
368    application:set_env(sasl, error_logger_mf_dir, Privdir),
369    application:set_env(sasl, error_logger_mf_maxbytes, 1000),
370    application:set_env(sasl, error_logger_mf_maxfiles, 5),
371    application:start(sasl),
372    ok = observer:start(),
373    ObserverNB = setup_whitebox_testing(),
374    Parent = get_top_level_parent(ObserverNB),
375    % Activate log view
376    whereis(observer) ! #wx{id = ?ID_LOGVIEW, event = #wxCommand{type = command_menu_selected}},
377    timer:sleep(1000),
378    % Process window tests (use sasl_sup for a non empty Log tab)
379    Frame = observer_procinfo:start(whereis(sasl_sup), Parent, self()),
380    PIPid = wx_object:get_pid(Frame),
381    PIPid ! {get_debug_info, self()},
382    Notebook = receive {procinfo_debug, NB} -> NB end,
383    Count = wxNotebook:getPageCount(Notebook),
384    Check = fun(_N) ->
385		    ok = wxNotebook:advanceSelection(Notebook),
386		    timer:sleep(400)
387	    end,
388    [_|_] = [Check(N) || N <- lists:seq(1, Count)],
389    PIPid ! #wx{event=#wxClose{type=close_window}},
390    observer:stop(),
391    application:stop(sasl),
392    case SaslStart of
393         true  -> application:start(sasl);
394         false -> ok
395    end,
396    ?P("ensure observer stopped"),
397    ensure_observer_stopped(?SECS(2)),
398    ?P("process_win -> done"),
399    ok.
400
401table_win(suite) -> [];
402table_win(doc) -> [""];
403table_win(Config) when is_list(Config) ->
404    ?P("table_win -> entry"),
405    Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
406    Table = lists:nth(3, Tables),
407    ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
408    ok = observer:start(),
409    Notebook = setup_whitebox_testing(),
410    Parent = get_top_level_parent(Notebook),
411    TObj = observer_tv_table:start_link(Parent, [{node,node()}, {type,ets}, {table,#tab{name=foo, id=Table}}]),
412    %% Modal cannot test edit..
413    %% TPid = wx_object:get_pid(TObj),
414    %% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}},
415    timer:sleep(3000),
416    wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}},
417    observer:stop(),
418    ?P("ensure observer stopped"),
419    ensure_observer_stopped(?SECS(3)),
420    ?P("table_win -> done"),
421    ok.
422
423%% Test PR-1296/OTP-14151
424%% Clicking a link to a port before the port tab has been activated the
425%% first time crashes observer.
426port_win_when_tab_not_initiated(_Config) ->
427    ?P("port_win_when_tab_not_initiated -> entry"),
428    {ok,Port} = gen_tcp:listen(0,[]),
429    ok = observer:start(),
430    _Notebook = setup_whitebox_testing(),
431    observer ! {open_link,erlang:port_to_list(Port)},
432    timer:sleep(1000),
433    observer:stop(),
434    ?P("ensure observer stopped"),
435    ensure_observer_stopped(?SECS(3)),
436    ?P("port_win_when_tab_not_initiated -> done"),
437    ok.
438
439
440%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
441
442get_top_level_parent(Window) ->
443    Parent =  wxWindow:getParent(Window),
444    case wx:is_null(Parent) of
445	true -> Window;
446	false -> get_top_level_parent(Parent)
447    end.
448
449setup_whitebox_testing() ->
450    %% So that if we die observer exists
451    link(whereis(observer)),
452    {Env, Notebook, _Active} = get_observer_debug(),
453    wx:set_env(Env),
454    Notebook.
455
456get_active() ->
457    {_, _, Active} = get_observer_debug(),
458    Active.
459
460get_observer_debug() ->
461    observer ! {get_debug_info, self()},
462    receive
463	{observer_debug, Env, Notebook, Active} ->
464	    {Env, Notebook, Active}
465    end.
466
467
468%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
469
470ensure_observer_stopped() ->
471    ensure_observer_stopped(0).
472
473ensure_observer_stopped(T) when is_integer(T) andalso (T > 0) ->
474    case erlang:whereis(observer) of
475	undefined ->
476	    ?P("observer *not* running"),
477	    ok;
478	Pid when is_pid(Pid) ->
479	    ?P("observer process still running: "
480	       "~n   ~p", [erlang:process_info(Pid)]),
481	    ct:sleep(?SECS(1)),
482	    ensure_observer_stopped(T - 1000),
483	    ok
484    end;
485ensure_observer_stopped(_) ->
486    case erlang:whereis(observer) of
487	undefined ->
488	    ?P("observer *not* running"),
489	    ok;
490	Pid when is_pid(Pid) ->
491	    ?P("observer process still running: kill"
492	       "~n   ~p", [erlang:process_info(Pid)]),
493	    exit(kill, Pid),
494	    ok
495    end.
496
497
498%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
499
500sock_create(Conds) when is_list(Conds) ->
501    sock_create(Conds, []).
502
503sock_create([], Acc) ->
504    Acc;
505sock_create([{Features, Args}|Conds], Acc) ->
506    case sock_cond_action(Features, fun() -> sock_open(Args) end) of
507        undefined ->
508            sock_create(Conds, Acc);
509        Socket ->
510            sock_create(Conds, [Socket|Acc])
511    end.
512
513sock_cond_action(Features, Action) ->
514    sock_cond_action(Features, Action, undefined).
515
516sock_cond_action(Features, Action, Default) ->
517    case sock_is_supported(Features) of
518        true ->
519            Action();
520        false ->
521            Default
522    end.
523
524
525sock_is_supported([]) ->
526    true;
527sock_is_supported([Feature|Features]) ->
528    sock_is_supported(Feature) andalso sock_is_supported(Features);
529sock_is_supported(any) ->
530    try socket:supports() of
531        Features when is_list(Features) ->
532            true
533    catch
534        _:_:_ ->
535            false
536    end;
537sock_is_supported(Feature) ->
538    try socket:is_supported(Feature)
539    catch
540        _:_:_ ->
541            false
542    end.
543
544
545sock_open({Domain, Type, Proto}) ->
546    case socket:open(Domain, Type, Proto) of
547        {ok, Socket} ->
548            ?P("created socket: ~w, ~w, ~w", [Domain, Type, Proto]),
549            Socket;
550        {error, _} ->
551            undefined
552    end.
553
554
555sock_close([]) ->
556    ok;
557sock_close([Socket|Sockets]) ->
558    sock_close(Socket),
559    sock_close(Sockets);
560sock_close(undefined) ->
561    ok;
562sock_close(Socket) ->
563    (catch socket:close(Socket)).
564
565
566%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
567
568%% f(F, A) ->
569%%     lists:flatten(io_lib:format(F, A)).
570
571formated_timestamp() ->
572    format_timestamp(os:timestamp()).
573
574format_timestamp({_N1, _N2, N3} = TS) ->
575    {_Date, Time}   = calendar:now_to_local_time(TS),
576    {Hour, Min, Sec} = Time,
577    FormatTS = io_lib:format("~.2.0w:~.2.0w:~.2.0w.~.3.0w",
578                             [Hour, Min, Sec, N3 div 1000]),
579    lists:flatten(FormatTS).
580
581print(F) ->
582    print(F, []).
583
584print(F, A) ->
585    io:format("~s ~p " ++ F ++ "~n", [formated_timestamp(), self() | A]).
586
587