aboutsummaryrefslogtreecommitdiffstats
path: root/lib/observer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/observer')
-rw-r--r--lib/observer/src/cdv_dist_cb.erl12
-rw-r--r--lib/observer/src/crashdump_viewer.erl14
-rw-r--r--lib/observer/src/observer.erl7
-rw-r--r--lib/observer/src/observer_lib.erl2
-rw-r--r--lib/observer/src/observer_procinfo.erl5
-rw-r--r--lib/observer/src/observer_tv_wx.erl6
-rw-r--r--lib/observer/src/observer_wx.erl13
-rw-r--r--lib/observer/test/crashdump_viewer_SUITE.erl82
-rw-r--r--lib/observer/test/observer_SUITE.erl232
9 files changed, 321 insertions, 52 deletions
diff --git a/lib/observer/src/cdv_dist_cb.erl b/lib/observer/src/cdv_dist_cb.erl
index 3860324d6f..f7e6c9aded 100644
--- a/lib/observer/src/cdv_dist_cb.erl
+++ b/lib/observer/src/cdv_dist_cb.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2013. All Rights Reserved.
+%% Copyright Ericsson AB 2013-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -22,7 +22,8 @@
get_info/1,
get_detail_cols/1,
get_details/1,
- detail_pages/0]).
+ detail_pages/0,
+ format/1]).
-include_lib("wx/include/wx.hrl").
-include("crashdump_viewer.hrl").
@@ -75,6 +76,11 @@ init_gen_page(Parent, Info) ->
Fields = info_fields(),
cdv_info_wx:start_link(Parent,{Fields,Info,[]}).
+format({creations,Creations}) ->
+ string:join([integer_to_list(C) || C <- Creations],",");
+format(D) ->
+ D.
+
%%%-----------------------------------------------------------------
%%% Internal
info_fields() ->
@@ -83,7 +89,7 @@ info_fields() ->
{"Type", conn_type},
{"Channel", channel},
{"Controller", {click,controller}},
- {"Creation", creation},
+ {"Creation", {{format,fun format/1},creation}},
{"Extra Info", error}]},
{scroll_boxes,
[{"Remote Links",1,{click,remote_links}},
diff --git a/lib/observer/src/crashdump_viewer.erl b/lib/observer/src/crashdump_viewer.erl
index a17efbccb0..a08d27d070 100644
--- a/lib/observer/src/crashdump_viewer.erl
+++ b/lib/observer/src/crashdump_viewer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2003-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2003-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -1576,7 +1576,13 @@ get_nodeinfo(Fd,Nod) ->
"Controller" ->
get_nodeinfo(Fd,Nod#nod{controller=val(Fd)});
"Creation" ->
- get_nodeinfo(Fd,Nod#nod{creation=list_to_integer(val(Fd))});
+ %% Throwing away elements like "(refc=1)", which might be
+ %% printed from a debug compiled emulator.
+ Creations = lists:flatmap(fun(C) -> try [list_to_integer(C)]
+ catch error:badarg -> []
+ end
+ end, string:tokens(val(Fd)," ")),
+ get_nodeinfo(Fd,Nod#nod{creation={creations,Creations}});
"Remote link" ->
Procs = val(Fd), % e.g. "<0.31.0> <4322.54.0>"
{Local,Remote} = split(Procs),
@@ -2559,11 +2565,11 @@ progress_pmap(Report,File,Fun,List) ->
{L1,L2} = if length(L)>=NPerProc -> lists:split(NPerProc,L);
true -> {L,[]} % last chunk
end,
- P = spawn(
+ {P,_Ref} =
+ spawn_monitor(
fun() ->
progress_map(Collector,ReportInterval,File,Fun,L1)
end),
- erlang:monitor(process,P),
{L2,[P|Ps]}
end,
{List,[]},
diff --git a/lib/observer/src/observer.erl b/lib/observer/src/observer.erl
index 098100e8ee..a30ceecc63 100644
--- a/lib/observer/src/observer.erl
+++ b/lib/observer/src/observer.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -18,8 +18,11 @@
-module(observer).
--export([start/0]).
+-export([start/0, stop/0]).
start() ->
observer_wx:start().
+
+stop() ->
+ observer_wx:stop().
diff --git a/lib/observer/src/observer_lib.erl b/lib/observer/src/observer_lib.erl
index cedaf7d2b8..34c7b127ff 100644
--- a/lib/observer/src/observer_lib.erl
+++ b/lib/observer/src/observer_lib.erl
@@ -249,6 +249,8 @@ to_str({func, {F,A}}) when is_atom(F), is_integer(A) ->
lists:concat([F, "/", A]);
to_str({func, {F,'_'}}) when is_atom(F) ->
atom_to_list(F);
+to_str({{format,Fun},Value}) when is_function(Fun) ->
+ Fun(Value);
to_str({A, B}) when is_atom(A), is_atom(B) ->
lists:concat([A, ":", B]);
to_str({M,F,A}) when is_atom(M), is_atom(F), is_integer(A) ->
diff --git a/lib/observer/src/observer_procinfo.erl b/lib/observer/src/observer_procinfo.erl
index 3ffa5fc77d..8e8a37fc93 100644
--- a/lib/observer/src/observer_procinfo.erl
+++ b/lib/observer/src/observer_procinfo.erl
@@ -34,6 +34,7 @@
-record(state, {parent,
frame,
+ notebook,
pid,
pages=[],
expand_table,
@@ -76,6 +77,7 @@ init([Pid, ParentFrame, Parent]) ->
{Frame, #state{parent=Parent,
pid=Pid,
frame=Frame,
+ notebook=Notebook,
pages=[ProcessPage,MessagePage,DictPage,StackPage,StatePage],
expand_table=Table
}}
@@ -158,6 +160,9 @@ handle_event(#wx{event=#wxHtmlLink{linkInfo=#wxHtmlLinkInfo{href=Info}}}, State)
handle_event(Event, _State) ->
error({unhandled_event, Event}).
+handle_info({get_debug_info, From}, State = #state{notebook=Notebook}) ->
+ From ! {procinfo_debug, Notebook},
+ {noreply, State};
handle_info(_Info, State) ->
%% io:format("~p: ~p, Handle info: ~p~n", [?MODULE, ?LINE, Info]),
{noreply, State}.
diff --git a/lib/observer/src/observer_tv_wx.erl b/lib/observer/src/observer_tv_wx.erl
index b276965f83..da4cb8e041 100644
--- a/lib/observer/src/observer_tv_wx.erl
+++ b/lib/observer/src/observer_tv_wx.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2011-2012. All Rights Reserved.
+%% Copyright Ericsson AB 2011-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -132,8 +132,8 @@ handle_event(#wx{event=#wxSize{size={W,_}}}, State=#state{grid=Grid}) ->
observer_lib:set_listctrl_col_size(Grid, W),
{noreply, State};
-handle_event(#wx{obj=Grid, event=#wxList{type=command_list_item_activated,
- itemIndex=Index}},
+handle_event(#wx{event=#wxList{type=command_list_item_activated,
+ itemIndex=Index}},
State=#state{grid=Grid, node=Node, opt=#opt{type=Type}, tabs=Tabs}) ->
Table = lists:nth(Index+1, Tabs),
case Table#tab.protection of
diff --git a/lib/observer/src/observer_wx.erl b/lib/observer/src/observer_wx.erl
index ecb8e132fe..ced26f7119 100644
--- a/lib/observer/src/observer_wx.erl
+++ b/lib/observer/src/observer_wx.erl
@@ -19,7 +19,7 @@
-behaviour(wx_object).
--export([start/0]).
+-export([start/0, stop/0]).
-export([create_menus/2, get_attrib/1, get_tracer/0, set_status/1,
create_txt_dialog/4, try_rpc/4, return_to_localnode/2]).
@@ -69,6 +69,9 @@ start() ->
_Obj -> ok
end.
+stop() ->
+ wx_object:call(observer, stop).
+
create_menus(Object, Menus) when is_list(Menus) ->
wx_object:call(Object, {create_menus, Menus}).
@@ -331,6 +334,10 @@ handle_call({get_attrib, Attrib}, _From, State) ->
handle_call(get_tracer, _From, State=#state{trace_panel=TraceP}) ->
{reply, TraceP, State};
+handle_call(stop, _, State = #state{frame = Frame}) ->
+ wxFrame:destroy(Frame),
+ {stop, normal, ok, State};
+
handle_call(_Msg, _From, State) ->
{reply, ok, State}.
@@ -367,6 +374,10 @@ handle_info({open_link, Pid0}, State = #state{pro_panel=ProcViewer, frame=Frame}
end,
{noreply, State};
+handle_info({get_debug_info, From}, State = #state{notebook=Notebook, active_tab=Pid}) ->
+ From ! {observer_debug, wx:get_env(), Notebook, Pid},
+ {noreply, State};
+
handle_info({'EXIT', Pid, _Reason}, State) ->
io:format("Child (~s) crashed exiting: ~p ~p~n",
[pid2panel(Pid, State), Pid,_Reason]),
diff --git a/lib/observer/test/crashdump_viewer_SUITE.erl b/lib/observer/test/crashdump_viewer_SUITE.erl
index 7a582436b4..f6e7d18f65 100644
--- a/lib/observer/test/crashdump_viewer_SUITE.erl
+++ b/lib/observer/test/crashdump_viewer_SUITE.erl
@@ -120,27 +120,63 @@ start_stop(Config) when is_list(Config) ->
Dump = hd(?config(dumps,Config)),
timer:sleep(1000),
- ProcsBefore = length(processes()),
+ ProcsBefore = processes(),
+ NumProcsBefore = length(ProcsBefore),
ok = crashdump_viewer:start(Dump),
- true = is_pid(whereis(crashdump_viewer_server)),
- true = is_pid(whereis(cdv_wx)),
- true = is_pid(whereis(cdv_proc_cb)),
- true = is_pid(whereis(cdv_port_cb)),
- true = is_pid(whereis(cdv_ets_cb)),
- true = is_pid(whereis(cdv_timer_cb)),
- true = is_pid(whereis(cdv_fun_cb)),
- true = is_pid(whereis(cdv_atom_cb)),
- true = is_pid(whereis(cdv_dist_cb)),
- true = is_pid(whereis(cdv_mod_cb)),
+ ExpectedRegistered = [crashdump_viewer_server,
+ cdv_wx,
+ cdv_proc_cb,
+ cdv_proc_cb__holder,
+ cdv_port_cb,
+ cdv_port_cb__holder,
+ cdv_ets_cb,
+ cdv_ets_cb__holder,
+ cdv_timer_cb,
+ cdv_timer_cb__holder,
+ cdv_fun_cb,
+ cdv_fun_cb__holder,
+ cdv_atom_cb,
+ cdv_atom_cb__holder,
+ cdv_dist_cb,
+ cdv_dist_cb__holder,
+ cdv_mod_cb,
+ cdv_mod_cb__holder],
+ Regs=[begin
+ P=whereis(N),
+ {P,N,erlang:monitor(process,P)}
+ end || N <- ExpectedRegistered],
+ ct:log("CDV procs: ~n~p~n",[Regs]),
+ [true=is_pid(P) || {P,_,_} <- Regs],
timer:sleep(5000), % give some time to live
ok = crashdump_viewer:stop(),
- timer:sleep(1000), % give some time to stop
- undefined = whereis(crashdump_viewer_server),
- undefined = whereis(cdv_wx),
- ProcsAfter=length(processes()),
- ProcsAfter=ProcsBefore,
+ recv_downs(Regs),
+ timer:sleep(2000),
+ ProcsAfter = processes(),
+ NumProcsAfter = length(ProcsAfter),
+ if NumProcsAfter=/=NumProcsBefore ->
+ ct:log("Before but not after:~n~p~n",
+ [[{P,process_info(P)} || P <- ProcsBefore -- ProcsAfter]]),
+ ct:log("After but not before:~n~p~n",
+ [[{P,process_info(P)} || P <- ProcsAfter -- ProcsBefore]]),
+ ct:fail("leaking processes");
+ true ->
+ ok
+ end,
ok.
+recv_downs([]) ->
+ ok;
+recv_downs(Regs) ->
+ receive
+ {'DOWN',Ref,process,_Pid,_} ->
+ ct:log("Got 'DOWN' for process ~n~p~n",[_Pid]),
+ recv_downs(lists:keydelete(Ref,3,Regs))
+ after 30000 ->
+ ct:log("Timeout waiting for down:~n~p~n",
+ [[{Reg,process_info(P)} || {P,_,_}=Reg <- Regs]]),
+ ct:log("Message queue:~n~p~n",[process_info(self(),messages)])
+ end.
+
%% Try to load nonexisting file
non_existing(Config) when is_list(Config) ->
ExpectedReason = "non-existing-file is not an Erlang crash dump\n",
@@ -428,8 +464,10 @@ do_create_dumps(DataDir,Rel) ->
end.
-%% Create a dump which has two visible nodes, one hidden and one
+%% Create a dump which has three visible nodes, one hidden and one
%% not connected node, and with monitors and links between nodes.
+%% One of the visible nodes is stopped and started again in order to
+%% get multiple creations.
full_dist_dump(DataDir,Rel) ->
Opt = rel_opt(Rel),
Pz = "-pz \"" ++ filename:dirname(code:which(?MODULE)) ++ "\"",
@@ -450,6 +488,15 @@ full_dist_dump(DataDir,Rel) ->
get_response(P4),
get_response(P1),
+ %% start, stop and start a node in order to get multiple 'creations'
+ {ok,N5} = ?t:start_node(n5,peer,Opt ++ PzOpt),
+ P51 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]),
+ get_response(P51),
+ ?t:stop_node(N5),
+ {ok,N5} = ?t:start_node(n5,peer,Opt ++ PzOpt),
+ P52 = rpc:call(N5,?helper_mod,remote_proc,[P1,Creator]),
+ get_response(P52),
+
{aaaaaaaa,N1} ! {hello,from,other,node}, % distribution message
?t:stop_node(N3),
@@ -458,6 +505,7 @@ full_dist_dump(DataDir,Rel) ->
?t:stop_node(N2),
?t:stop_node(N4),
+ ?t:stop_node(N5),
CD.
get_response(P) ->
diff --git a/lib/observer/test/observer_SUITE.erl b/lib/observer/test/observer_SUITE.erl
index c076c5e81e..af07165456 100644
--- a/lib/observer/test/observer_SUITE.erl
+++ b/lib/observer/test/observer_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2006-2013. All Rights Reserved.
+%% Copyright Ericsson AB 2006-2014. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
@@ -19,34 +19,35 @@
-module(observer_SUITE).
-include_lib("test_server/include/test_server.hrl").
+-include_lib("wx/include/wx.hrl").
+-include_lib("observer/src/observer_tv.hrl").
%% Test server specific exports
--export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
- init_per_group/2,end_per_group/2]).
--export([init_per_testcase/2, end_per_testcase/2]).
+-export([all/0, suite/0,groups/0]).
+-export([init_per_testcase/2, end_per_testcase/2,
+ init_per_group/2, end_per_group/2,
+ init_per_suite/1, end_per_suite/1
+ ]).
%% Test cases
--export([app_file/1, appup_file/1]).
+-export([app_file/1, appup_file/1,
+ basic/1, process_win/1, table_win/1
+ ]).
%% Default timetrap timeout (set in init_per_testcase)
-define(default_timeout, ?t:minutes(1)).
-init_per_testcase(_Case, Config) ->
- Dog = ?t:timetrap(?default_timeout),
- [{watchdog, Dog} | Config].
-
-end_per_testcase(_Case, Config) ->
- Dog = ?config(watchdog, Config),
- ?t:timetrap_cancel(Dog),
- ok.
-
suite() -> [{ct_hooks,[ts_install_cth]}].
-all() ->
- [app_file, appup_file].
+all() ->
+ [app_file, appup_file, {group, gui}].
-groups() ->
- [].
+groups() ->
+ [{gui, [],
+ [basic
+ %% , process_win, table_win
+ ]
+ }].
init_per_suite(Config) ->
Config.
@@ -54,12 +55,40 @@ init_per_suite(Config) ->
end_per_suite(_Config) ->
ok.
-init_per_group(_GroupName, Config) ->
- Config.
+init_per_testcase(_Case, Config) ->
+ Dog = ?t:timetrap(?default_timeout),
+ [{watchdog, Dog} | Config].
-end_per_group(_GroupName, Config) ->
- Config.
+end_per_testcase(_Case, Config) ->
+ Dog = ?config(watchdog, Config),
+ ?t:timetrap_cancel(Dog),
+ ok.
+init_per_group(gui, Config) ->
+ try
+ case os:type() of
+ {unix,darwin} ->
+ exit("Can not test on MacOSX");
+ {unix, _} ->
+ io:format("DISPLAY ~s~n", [os:getenv("DISPLAY")]),
+ case ct:get_config(xserver, none) of
+ none -> ignore;
+ Server -> os:putenv("DISPLAY", Server)
+ end;
+ _ -> ignore
+ end,
+ wx:new(),
+ wx:destroy(),
+ Config
+ catch
+ _:undef ->
+ {skipped, "No wx compiled for this platform"};
+ _:Reason ->
+ SkipReason = io_lib:format("Start wx failed: ~p", [Reason]),
+ {skipped, lists:flatten(SkipReason)}
+ end.
+end_per_group(_, _) ->
+ ok.
app_file(suite) ->
[];
@@ -72,3 +101,162 @@ app_file(Config) when is_list(Config) ->
%% Testing .appup file
appup_file(Config) when is_list(Config) ->
ok = ?t:appup_test(observer).
+
+-define(DBG(Foo), io:format("~p: ~p~n",[?LINE, catch Foo])).
+
+basic(suite) -> [];
+basic(doc) -> [""];
+basic(Config) when is_list(Config) ->
+ ok = observer:start(),
+ Notebook = setup_whitebox_testing(),
+
+ io:format("Notebook ~p~n",[Notebook]),
+ Count = wxNotebook:getPageCount(Notebook),
+ true = Count >= 6,
+ 0 = wxNotebook:getSelection(Notebook),
+ timer:sleep(500),
+ Check = fun(N, TestMore) ->
+ ok = wxNotebook:advanceSelection(Notebook),
+ TestMore andalso
+ test_page(wxNotebook:getPageText(Notebook, N),
+ wxNotebook:getCurrentPage(Notebook)),
+ timer:sleep(200)
+ end,
+ %% Just verify that we can toogle trough all pages
+ [_|_] = [Check(N, false) || N <- lists:seq(1, Count)],
+ %% Cause it to resize
+ Frame = get_top_level_parent(Notebook),
+ {W,H} = wxWindow:getSize(Frame),
+ wxWindow:setSize(Frame, W+10, H+10),
+ [_|_] = [Check(N, true) || N <- lists:seq(1, Count)],
+
+ ok = observer:stop().
+
+test_page("Load Charts" ++ _, _Window) ->
+ %% Just let it display some info and hopefully it doesn't crash
+ timer:sleep(2000),
+ ok;
+test_page("Applications" ++ _, _Window) ->
+ ok = application:start(mnesia),
+ timer:sleep(1000), %% Give it time to refresh
+ Active = get_active(),
+ FakeEv = #wx{event=#wxCommand{type=command_listbox_selected, cmdString="mnesia"}},
+ Active ! FakeEv,
+ timer:sleep(1000), %% Give it time to refresh
+ ok = application:stop(mnesia),
+ timer:sleep(1000), %% Give it time to refresh
+ ok;
+
+test_page("Processes" ++ _, _Window) ->
+ timer:sleep(500), %% Give it time to refresh
+ Active = get_active(),
+ ChangeSort = fun(N) ->
+ FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
+ Active ! FakeEv,
+ timer:sleep(200)
+ end,
+ [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
+ Focus = #wx{event=#wxList{type=command_list_item_focused, itemIndex=2}},
+ Active ! Focus,
+ Activate = #wx{event=#wxList{type=command_list_item_activated}},
+ Active ! Activate,
+ timer:sleep(1000), %% Give it time to refresh
+ ok;
+
+test_page("Table" ++ _, _Window) ->
+ Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
+ Active = get_active(),
+ Active ! refresh_interval,
+ ChangeSort = fun(N) ->
+ FakeEv = #wx{event=#wxList{type=command_list_col_click, col=N}},
+ Active ! FakeEv,
+ timer:sleep(200)
+ end,
+ [ChangeSort(N) || N <- lists:seq(1,5) ++ [0]],
+ timer:sleep(1000),
+ Table = lists:nth(3, Tables),
+ ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
+ Focus = #wx{event=#wxList{type=command_list_item_selected, itemIndex=2}},
+ Active ! Focus,
+ Activate = #wx{event=#wxList{type=command_list_item_activated, itemIndex=2}},
+ Active ! Activate,
+
+ Info = 407, %% whitebox...
+ Active ! #wx{id=Info},
+ timer:sleep(1000),
+ ok;
+
+test_page(Title, Window) ->
+ io:format("Page ~p: ~p~n", [Title, Window]),
+ %% Just let it display some info and hopefully it doesn't crash
+ timer:sleep(1000),
+ ok.
+
+
+process_win(suite) -> [];
+process_win(doc) -> [""];
+process_win(Config) when is_list(Config) ->
+ ok = observer:start(),
+ ObserverNB = setup_whitebox_testing(),
+ Parent = get_top_level_parent(ObserverNB),
+ Frame = observer_procinfo:start(self(), Parent, self()),
+ PIPid = wx_object:get_pid(Frame),
+ PIPid ! {get_debug_info, self()},
+ Notebook = receive {procinfo_debug, NB} -> NB end,
+ Count = wxNotebook:getPageCount(Notebook),
+ Check = fun(_N) ->
+ ok = wxNotebook:advanceSelection(Notebook),
+ timer:sleep(400)
+ end,
+ [_|_] = [Check(N) || N <- lists:seq(1, Count)],
+ PIPid ! #wx{event=#wxClose{type=close_window}},
+ observer:stop(),
+ ok.
+
+table_win(suite) -> [];
+table_win(doc) -> [""];
+table_win(Config) when is_list(Config) ->
+ Tables = [ets:new(list_to_atom("Test-" ++ [C]), [public]) || C <- lists:seq($A, $Z)],
+ Table = lists:nth(3, Tables),
+ ets:insert(Table, [{N,100-N} || N <- lists:seq(1,100)]),
+ ok = observer:start(),
+ Notebook = setup_whitebox_testing(),
+ Parent = get_top_level_parent(Notebook),
+ TObj = observer_tv_table:start_link(Parent, [{node,node()}, {type,ets}, {table,#tab{name=foo, id=Table}}]),
+ %% Modal can not test edit..
+ %% TPid = wx_object:get_pid(TObj),
+ %% TPid ! #wx{event=#wxList{type=command_list_item_activated, itemIndex=12}},
+ timer:sleep(2000),
+ wx_object:get_pid(TObj) ! #wx{event=#wxClose{type=close_window}},
+ observer:stop(),
+ ok.
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+get_top_level_parent(Window) ->
+ Parent = wxWindow:getParent(Window),
+ case wx:is_null(Parent) of
+ true -> Window;
+ false -> get_top_level_parent(Parent)
+ end.
+
+setup_whitebox_testing() ->
+ %% So that if we die observer exists
+ link(whereis(observer)),
+ {Env, Notebook, _Active} = get_observer_debug(),
+ wx:set_env(Env),
+ Notebook.
+
+get_active() ->
+ {_, _, Active} = get_observer_debug(),
+ Active.
+
+get_observer_debug() ->
+ observer ! {get_debug_info, self()},
+ receive
+ {observer_debug, Env, Notebook, Active} ->
+ {Env, Notebook, Active}
+ end.