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