%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2008-2017. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% %CopyrightEnd%
%%%-------------------------------------------------------------------
%%% File : wx_basic_SUITE.erl
%%% Author : Dan Gudmundsson <[email protected]>
%%% Description : Basic SUITE, some simple tests to show that the basics
%%% are working.
%%% Created : 3 Nov 2008 by Dan Gudmundsson <[email protected]>
%%%-------------------------------------------------------------------
-module(wx_basic_SUITE).
-export([all/0, suite/0,groups/0,init_per_group/2,end_per_group/2,
init_per_suite/1, end_per_suite/1,
init_per_testcase/2, end_per_testcase/2]).
-compile(export_all).
-include("wx_test_lib.hrl").
%% Initialization functions.
init_per_suite(Config) ->
wx_test_lib:init_per_suite(Config).
end_per_suite(Config) ->
wx_test_lib:end_per_suite(Config).
init_per_testcase(Func,Config) ->
wx_test_lib:init_per_testcase(Func,Config).
end_per_testcase(Func,Config) ->
wx_test_lib:end_per_testcase(Func,Config).
%% SUITE specification
suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}].
all() ->
[silent_start, create_window, several_apps, wx_api, wx_misc,
data_types, wx_object, {group, undef_callbacks},
undef_in_handle_info, undef_in_terminate].
groups() ->
[{undef_callbacks, [],
[undef_handle_event, undef_handle_call, undef_handle_cast, undef_handle_info,
undef_code_change, undef_terminate1, undef_terminate2]}].
init_per_group(_GroupName, Config) ->
Config.
end_per_group(_GroupName, Config) ->
Config.
%% The test cases
%% test silent start of wx
silent_start(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
silent_start(_Config) ->
?mr(wx_ref, wx:new([])),
wx:destroy(),
?mr(wx_ref, wx:new([{silent_start, true}])),
wx:destroy(),
?mr(wx_ref, wx:new([{silent_start, true}, {debug, verbose}])),
wx:destroy(),
?mr(wx_ref, wx:new([{silent_start, false}])),
wx:destroy(),
?mr('EXIT', catch wx:new([{silent_start, foo}])),
ok.
%% create and test creating a window
create_window(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
create_window(Config) ->
Wx = ?mr(wx_ref, wx:new()),
Frame = ?mt(wxFrame, wxFrame:new(Wx, 1, "Hello World")),
timer:sleep(1000),
?m(true,wxWindow:show(Frame, [])),
wx_test_lib:wx_destroy(Frame, Config).
%% create several windows from independent processes
%% to simulate several applications and test creating a window
several_apps(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
several_apps(Config) ->
Parent = self(),
Pids = [spawn_link(fun() -> several_apps(Parent, N, Config) end)
|| N <- lists:seq(1,4)],
process_flag(trap_exit,true),
Wait = fun(Pid,Acc) ->
receive {complete, Pid} -> Acc
after 20000 -> [Pid|Acc]
end
end,
Res = lists:foldl(Wait, [], Pids),
[Pid ! quit || Pid <- Pids],
Dbg = fun(Pid) ->
io:format("Stack ~p~n",[erlang:process_info(Pid, current_stacktrace)]),
io:format("Stack ~p~n",[erlang:process_info(Pid)])
end,
case Res of
[] -> ok;
Failed ->
[Dbg(Pid)|| Pid <- Failed]
end,
case wx_test_lib:user_available(Config) of
true ->
receive {'EXIT',_,foo} -> ok end;
false ->
ok
end.
several_apps(Parent, N, Config) ->
Wx = ?mr(wx_ref, wx:new()),
Frame = ?mt(wxFrame, wxFrame:new(Wx, 1, "Hello World No:" ++
integer_to_list(N))),
create_menus(Frame),
wxFrame:connect(Frame,size),
?m(true,wxWindow:show(Frame, [])),
receive
#wx{obj=Frame, event=#wxSize{}} ->
Parent ! {complete, self()}
end,
receive quit -> ok end,
wx_test_lib:wx_destroy(Frame, Config),
exit(foo).
%% Test the wx.erl api functionality.
wx_api(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
wx_api(Config) ->
Wx = ?mr(wx_ref, wx:new()),
?m(true, wx:is_null(Wx)),
Null = ?mr(wx_ref, wx:null()),
?m(true, wx:is_null(Null)),
Frame = ?mt(wxFrame, wxFrame:new(Wx, 1, "WX API: " ++ unicode:characters_to_list("åäöÅÄÖ"))),
?m(false, wx:is_null(Frame)),
?m(wxFrame, wx:getObjectType(Frame)),
Env = ?mr(wx_env, wx:get_env()),
%% Test some error cases
erase(wx_env),
?m({'EXIT', {{wxe,unknown_port},_}},wxWindow:show(Frame, [])),
?m({'EXIT', {{wxe,unknown_port},_}},wx:debug(2)),
?m(ok,wx:set_env(Env)),
?m(ok,wx:debug(1)),
?m(ok,wx:debug(2)),
?m(ok,wx:debug(0)),
?m(ok,wx:debug(none)),
?m(ok,wx:debug(verbose)),
?m(ok,wx:debug(trace)),
Mem = ?mr(wx_mem, wx:create_memory(10)),
?m(true, is_binary(wx:get_memory_bin(Mem))),
?mt(foo, wx:typeCast(Frame, foo)),
RecBatch = fun() ->
wx:batch(fun() -> create_menus(Frame) end)
end,
?m(batch_ret, wx:batch(fun() -> RecBatch(), batch_ret end)),
?m(ok, wx:foreach(fun(A) -> true = lists:member(A,[1,2,3,4,5]) end,
lists:seq(1,5))),
?m([2,3,4,5,6], wx:map(fun(A) -> A+1 end, lists:seq(1,5))),
?m({5,15}, wx:foldl(fun(A,{_,Acc}) -> {A,A+Acc} end, {0,0},
lists:seq(1,5))),
?m({1,15}, wx:foldr(fun(A,{_,Acc}) -> {A,A+Acc} end, {0,0},
lists:seq(1,5))),
?m(ok,wx:debug(none)),
?m(ball, wx:batch(fun() -> throw(ball), batch_ret end)),
?m({'EXIT', door}, wx:batch(fun() -> exit(door), batch_ret end)),
?m({'EXIT',{message,_ST}},
wx:batch(fun() -> erlang:error(message), batch_ret end)),
?m({'EXIT',_},wxWindow:show(wx:null(), [])),
?m(true,wxWindow:show(Frame, [])),
Temp = ?mt(wxButton, wxButton:new(Frame, -1)),
?m(ok,wxButton:setLabel(Temp, "Testing")),
?m(ok,wxButton:destroy(Temp)),
?m({'EXIT',_},wxButton:getLabel(Temp)),
?m(ok,wxButton:setLabel(Temp, "Testing")), %% Should generate an error report
?m({'EXIT',_},wxButton:getLabel(Temp)),
case wx_test_lib:user_available(Config) of
true ->
%% Hmm popup doesn't return until mouse is pressed.
Menu = wxMenu:new(),
wxMenu:append(Menu, 0, "Press", []),
wxMenu:append(Menu, 1, "Me", []),
?m(true, wxWindow:popupMenu(Frame, Menu)),
%% This didn't work for a while
?m(true, wx:batch(fun() ->
wxMenu:append(Menu, 2, "AGAIN", []),
wxWindow:popupMenu(Frame, Menu)
end)),
ok;
_ ->
ignore
end,
%% dbg:tracer(),
%% {_, _Port, Server, _Dbg} = wx:get_env(),
%% dbg:p(Server, [m, call]),
%% dbg:p(new, [m, call]),
%% dbg:tpl(wxe_server,'_', [{'_', [], [{return_trace}]}]),
wx_test_lib:wx_destroy(Frame,Config).
create_menus(Frame) ->
MenuBar = ?mt(wxMenuBar, wxMenuBar:new()),
File = ?mt(wxMenu, wxMenu:new([])),
Help = ?mt(wxMenu, wxMenu:new([])),
?mt(wxMenuItem, wxMenu:append(Help, ?wxID_ABOUT, "&About", [])),
?mt(wxMenuItem, wxMenu:append(Help, ?wxID_HELP, "&Help", [])),
?mt(wxMenuItem, wxMenu:append(File, ?wxID_EXIT, "Exit", [])),
?m(ok,wxFrame:connect(Frame, command_menu_selected)),
?m(true, wxMenuBar:append(MenuBar, File, "&File")),
?m(true, wxMenuBar:append(MenuBar, Help, "&Help")),
?m(ok, wxFrame:setMenuBar(Frame,MenuBar)).
%% Test the wx_misc.erl api functionality.
wx_misc(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
wx_misc(_Config) ->
wx:new([{debug, trace}]),
put(wx_test_verbose, true),
?m(ok, wx_misc:bell()),
?m(true, length(wx_misc:getUserId()) > 0),
?m(true, is_list(wx_misc:getEmailAddress())),
Home = ?m([_|_], wx_misc:getHomeDir()),
?m(true, filelib:is_dir(Home)),
?m(true, length(wx_misc:getOsDescription()) > 0),
IsLitte = case <<1:32/native>> of
<<1:8, 0:24>> -> true;
<<0:24,1:16>> -> false
end,
?m(IsLitte, wx_misc:isPlatformLittleEndian()),
?m(true, is_boolean(wx_misc:isPlatform64Bit())),
?mr(wxMouseState, wx_misc:getMouseState()),
?m({_,_}, wx_misc:getMousePosition()),
%% Don't hold home down when testing :-)
?m(false, wx_misc:getKeyState(?WXK_HOME)),
%% wx:shutdown() %% How do you test this?
?m(false, wx_misc:isBusy()),
?m(ok, wx_misc:beginBusyCursor([])),
?m(true, wx_misc:isBusy()),
?m(ok, wx_misc:endBusyCursor()),
%%?m(true, is_boolean(wx_misc:setDetectableAutoRepeat(true)),
Curr = wx_misc:getCurrentId(),
?m(true, is_integer(Curr)),
NewId = wx_misc:newId(),
?m(ok, wx_misc:registerId(NewId+1)),
?m(true, (NewId+1) /= wx_misc:newId()),
wx:destroy().
%% Check that all the data_types works in communication
%% between erlang and c++ thread.
data_types(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
data_types(_Config) ->
Wx = ?mr(wx_ref, wx:new()),
Frame = wxFrame:new(Wx, 1, "Data Types"),
wxFrame:connect(Frame, show),
wxFrame:show(Frame),
receive #wx{event=#wxShow{}} -> ok
after 1000 -> exit(show_timeout)
end,
CDC = wxClientDC:new(Frame),
%% From wx.erl
%% The following classes are implemented directly as erlang types: <br />
%% wxPoint={x,y},wxSize={w,h},wxRect={x,y,w,h},wxColour={r,g,b [,a]},wxString=[integer],
%% wxGBPosition={r,c},wxGBSpan={rs,cs},wxGridCellCoords={r,c}.
%% Strings
?m("Data Types", wxFrame:getTitle(Frame)),
%% Doubles
?m(ok, wxDC:setUserScale(CDC, 123.45, 234.67)),
?m({123.45,234.67}, wxDC:getUserScale(CDC)),
%% Array of doubles
try wxGraphicsContext:create(CDC) of
GC ->
wxGraphicsContext:setFont(GC, ?wxITALIC_FONT, {0, 0, 50}),
Ws = wxGraphicsContext:getPartialTextExtents(GC, "a String With More Than 16 Characters"),
_ = lists:foldl(fun(Width, {Index, Acc}) ->
if Width >= Acc, Width < 500 -> {Index+1, Width};
true -> throw({bad_float, Width, Index, Acc})
end
end, {0,0.0}, Ws),
ok
catch _:_ -> %% GC not supported on this platform
ok
end,
%% Colors input is 3 or 4 tuple, returns are 4 tuples
?m(ok, wxDC:setTextForeground(CDC, {100,10,1})),
?m({100,10,1,255}, wxDC:getTextForeground(CDC)),
?m(ok, wxDC:setTextForeground(CDC, {100,10,1,43})),
?m({100,10,1,43}, wxDC:getTextForeground(CDC)),
%% Bool
?m(ok, wxDC:setAxisOrientation(CDC, true, false)),
?m(true, is_boolean(wxDC:isOk(CDC))),
%% wxCoord
?m(true, is_integer(wxDC:maxX(CDC))),
%% wxSize
?m({_,_}, wxWindow:getSize(Frame)),
%% DateTime
DateTime = {Date, _Time} = calendar:now_to_datetime(os:timestamp()),
io:format("DateTime ~p ~n",[DateTime]),
Cal = ?mt(wxCalendarCtrl, wxCalendarCtrl:new(Frame, ?wxID_ANY, [{date,DateTime}])),
?m({Date,_}, wxCalendarCtrl:getDate(Cal)),
?m(true, is_boolean(wxCalendarCtrl:setDate(Cal,DateTime))),
?m({Date,_}, wxCalendarCtrl:getDate(Cal)),
%% Images, test sending and reading binaries
Colors = << <<200:8, 199:8, 198:8 >> || _ <- lists:seq(1, 128*64) >>,
Alpha = << <<255:8>> || _ <- lists:seq(1, 128*64) >>,
ImgRGB = ?mt(wxImage, wxImage:new(128, 64, Colors)),
?m(true, wxImage:ok(ImgRGB)),
?m(false, wxImage:hasAlpha(ImgRGB)),
?m(ok, case wxImage:getData(ImgRGB) of Colors -> ok; Other -> Other end),
ImgRGBA = ?mt(wxImage, wxImage:new(128, 64, Colors, Alpha)),
?m(true, wxImage:ok(ImgRGBA)),
?m(true, wxImage:hasAlpha(ImgRGBA)),
?m(ok, case wxImage:getData(ImgRGBA) of Colors -> ok; Other -> Other end),
?m(ok, case wxImage:getAlpha(ImgRGBA) of Alpha -> ok; Other -> Other end),
wxClientDC:destroy(CDC),
%%wx_test_lib:wx_destroy(Frame,Config).
wx:destroy().
wx_object(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
wx_object(Config) ->
wx:new(),
Me = self(),
Init = fun() ->
Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]),
Frame = wx_object:set_pid(Frame0, self()),
Sz = wxBoxSizer:new(?wxHORIZONTAL),
Panel = wxPanel:new(Frame),
wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxPanel:connect(Panel, size, [{skip, true}]),
wxPanel:connect(Panel, paint, [callback, {userData, Me}]),
wxWindow:show(Frame),
{Frame, {Frame, Panel}}
end,
Frame = ?mt(wxFrame, wx_obj_test:start([{init, Init}])),
timer:sleep(500),
?m(ok, check_events(flush())),
Me = self(),
?m({call, foobar, {Me, _}}, wx_object:call(Frame, foobar)),
?m(ok, wx_object:cast(Frame, foobar2)),
?m([{cast, foobar2}|_], flush()),
?m(Frame, wx_obj_test:who_are_you(Frame)),
{call, {Frame,Panel}, _} = wx_object:call(Frame, fun(US) -> US end),
?m(false, wxWindow:getParent(Panel) =:= Frame),
?m(true, wx:equal(wxWindow:getParent(Panel),Frame)),
FramePid = wx_object:get_pid(Frame),
io:format("wx_object pid ~p~n",[FramePid]),
FramePid ! foo3,
?m([{info, foo3}|_], flush()),
?m(ok, wx_object:cast(Frame, fun(_) -> hehe end)),
?m([{cast, hehe}|_], flush()),
wxWindow:refresh(Frame),
?m([{sync_event, #wx{event=#wxPaint{}}, _}|_], flush()),
?m(ok, wx_object:cast(Frame, fun(_) -> timer:sleep(200), slept end)),
%% The sleep above should not hinder the Paint event below
%% Which it did in my buggy handling of the sync_callback
wxWindow:refresh(Frame),
timer:sleep(500),
?m([{sync_event, #wx{event=#wxPaint{}}, _}, {cast, slept}|_], flush()),
Monitor = erlang:monitor(process, FramePid),
case proplists:get_value(user, Config, false) of
false ->
timer:sleep(100),
wxFrame:destroy(Frame);
true ->
timer:sleep(500),
?m(ok, wxFrame:destroy(Frame));
_ ->
?m(ok, wxEvtHandler:connect(Frame, close_window, [{skip,true}])),
wx_test_lib:wait_for_close()
end,
?m(ok, receive
{'DOWN', Monitor, _, _, _} ->
?m([{terminate, wx_deleted}], flush()),
ok
after 1000 ->
Msgs = flush(),
io:format("Error ~p Alive ~p~n",[Msgs, is_process_alive(FramePid)])
end),
catch wx:destroy(),
ok.
%% Test that the server crashes correctly if the handle_event callback is
%% not exported in the callback module
undef_handle_event(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_handle_event(_Config) ->
wx:new(),
{_, _, _, Pid} = wx_object:start(wx_oc_object, [], []),
MRef = monitor(process, Pid),
%% Mock a call to handle_event
Pid ! {wx, a, b, c, d},
ok = receive
{'DOWN', MRef, process, Pid,
{undef, [{wx_oc_object, handle_event, _, _}|_]}} ->
ok
after 5000 ->
ct:fail(should_crash)
end.
%% Test that the server crashes correctly if the handle_call callback is
%% not exported in the callback module
undef_handle_call(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_handle_call(_Config) ->
wx:new(),
Frame = wx_object:start(wx_oc_object, [], []),
try
wx_object:call(Frame, call_msg),
ct:fail(should_crash)
catch error:{{undef, [{wx_oc_object,handle_call, _, _}|_]},
{wx_object,call,_}} ->
ok
end.
%% Test that the server crashes correctly if the handle_cast callback is
%% not exported in the callback module
undef_handle_cast(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_handle_cast(_Config) ->
wx:new(),
{_, _, _, Pid} = Frame = wx_object:start(wx_oc_object, [], []),
MRef = monitor(process, Pid),
wx_object:cast(Frame, cast_msg),
ok = receive
{'DOWN', MRef, process, Pid,
{undef, [{wx_oc_object, handle_cast, _, _}|_]}} ->
ok
after 5000 ->
ct:fail(should_crash)
end.
%% Test the default implementation of handle_info if the callback module
%% does not export it
undef_handle_info(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_handle_info(_Config) ->
wx:new(),
{_, _, _, Pid} = wx_object:start(wx_oc_object, [], []),
MRef = monitor(process, Pid),
Pid ! test,
receive
{'DOWN', MRef, process, Pid, _} ->
ct:fail(should_not_crash)
after 500 ->
ok
end,
ok = wx_object:stop(Pid).
%% Test the server crashes correctly if called and the code_change callback is
%% not exported in the callback module
undef_code_change(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_code_change(_Config) ->
wx:new(),
{_, _, _, Pid} = wx_object:start(wx_oc_object, [], []),
sys:suspend(Pid),
sys:replace_state(Pid, fun([P, S, M, T]) -> [P, {new, S}, M, T] end),
{error, {'EXIT', {undef, [{wx_oc_object,code_change, [_, _, _], _}|_]}}}
= sys:change_code(Pid, wx_oc_object, old_vsn, []),
ok = sys:resume(Pid),
ok = wx_object:stop(Pid).
%% Test the default implementation of terminate if the callback module
%% does not export it
undef_terminate1(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_terminate1(_Config) ->
ok = terminate([], normal).
%% Test the default implementation of terminate if the callback module
%% does not export it
undef_terminate2(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_terminate2(_Config) ->
ok = terminate([{error, test}, infinity], {error, test}).
terminate(ArgsTl, Reason) ->
wx:new(),
{_, _, _, Pid} = wx_object:start(wx_oc_object, [], []),
MRef = monitor(process, Pid),
ok = apply(wx_object, stop, [Pid|ArgsTl]),
receive
{'DOWN', MRef, process, Pid, Reason} ->
ok
after 1000 ->
ct:fail(failed)
end.
%% Test that the server crashes correctly if the handle_info callback is
%% calling an undefined function
undef_in_handle_info(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_in_handle_info(_Config) ->
wx:new(),
Init = ui_init_fun(),
{_, _, _, Pid} = wx_object:start(wx_obj_test,
[{parent, self()}, {init, Init}], []),
unlink(Pid),
MRef = monitor(process, Pid),
Pid ! {call_undef_fun, {wx_obj_test, handle_info}},
receive
{'DOWN', MRef, process, Pid,
{undef, [{wx_obj_test, handle_info, _, _}|_]}} ->
ok
after 1000 ->
ct:fail(failed)
end,
ok.
%% Test that the server crashes correctly if the terminate callback is
%% calling an undefined function
undef_in_terminate(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo);
undef_in_terminate(_Config) ->
wx:new(),
Init = ui_init_fun(),
Frame = wx_object:start(wx_obj_test,
[{parent, self()}, {init, Init},
{terminate, {wx_obj_test, terminate}}], []),
try
wx_object:stop(Frame),
ct:fail(should_crash)
catch error:{{undef, [{wx_obj_test, terminate, _, _}|_]}, _} ->
ok
end.
ui_init_fun() ->
Init = fun() ->
Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]),
Frame = wx_object:set_pid(Frame0, self()),
Sz = wxBoxSizer:new(?wxHORIZONTAL),
Panel = wxPanel:new(Frame),
wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]),
wxWindow:show(Frame),
{Frame, {Frame, Panel}}
end,
Init.
check_events(Msgs) ->
check_events(Msgs, 0,0).
check_events([{event, #wx{event=#wxSize{}}}|Rest], Async, Sync) ->
check_events(Rest, Async+1, Sync);
check_events([{sync_event, #wx{event=#wxPaint{}}, Obj}|Rest], Async, Sync) ->
?mt(wxPaintEvent, Obj),
check_events(Rest, Async, Sync+1);
check_events([], Async, Sync) ->
case Async > 0 of %% Test sync explictly
true -> ok;
false -> {Async, Sync}
end.
flush() ->
flush([], 1500).
flush(Acc, Wait) ->
receive
Msg -> flush([Msg|Acc], Wait div 10)
after Wait ->
lists:reverse(Acc)
end.