diff options
Diffstat (limited to 'lib')
40 files changed, 1184 insertions, 745 deletions
diff --git a/lib/common_test/doc/src/install_chapter.xml b/lib/common_test/doc/src/install_chapter.xml index 7f8c606324..31125b945c 100644 --- a/lib/common_test/doc/src/install_chapter.xml +++ b/lib/common_test/doc/src/install_chapter.xml @@ -34,7 +34,7 @@ <title>General information</title> <p>The two main interfaces for running tests with Common Test - are an executable program named ct_run and an + are an executable program named <c>ct_run</c> and an erlang module named <c>ct</c>. The ct_run program is compiled for the underlying operating system (e.g. Unix/Linux or Windows) during the build of the Erlang/OTP system, and is @@ -43,67 +43,10 @@ The <c>ct</c> interface functions can be called from the Erlang shell, or from any Erlang function, on any supported platform.</p> - <p>A legacy Bourne shell script - named run_test - exists, - which may be manually generated and installed. This script may be used - instead of the ct_run program mentioned above, e.g. if the user - wishes to modify or customize the Common Test start flags in a simpler - way than making changes to the ct_run C program.</p> - <p>The Common Test application is installed with the Erlang/OTP system and no additional installation step is required to start using - Common Test by means of the ct_run executable program, and/or the interface - functions in the <c>ct</c> module. If you wish to use the legacy Bourne - shell script version run_test, however, this script needs to be - generated first, according to the instructions below.</p> - - <note><p>Before reading on, please note that since Common Test version - 1.5, the run_test shell script is no longer required for starting - tests with Common Test from the OS command line. The ct_run - program (descibed above) is the new recommended command line interface - for Common Test. The shell script exists mainly for legacy reasons and - may not be updated in future releases of Common Test. It may even be removed. - </p></note> - - <p>Optional step to generate a shell script for starting Common Test:</p> - <p>To generate the run_test shell script, navigate to the - <c><![CDATA[common_test-<vsn>]]></c> directory, located among the other - OTP applications (under the OTP lib directory). Here execute the - <c>install.sh</c> script with argument <c>local</c>:</p> - - <p><c> - $ ./install.sh local - </c></p> - - <p>This generates the executable run_test script in the - <c><![CDATA[common_test-<vsn>/priv/bin]]></c> directory. The script - will include absolute paths to the Common Test and Test Server - application directories, so it's possible to copy or move the script to - a different location on the file system, if desired, without having to - update it. It's of course possible to leave the script under the - <c>priv/bin</c> directory and update the PATH variable accordingly (or - create a link or alias to it).</p> - - <p>If you, for any reason, have copied Common Test and Test Server - to a different location than the default OTP lib directory, you can - generate a run_test script with a different top level directory, - simply by specifying the directory, instead of <c>local</c>, when running - <c>install.sh</c>. Example:</p> - - <p><c> - $ install.sh /usr/local/test_tools - </c></p> - - <p>Note that the <c><![CDATA[common_test-<vsn>]]></c> and - <c><![CDATA[test_server-<vsn>]]></c> directories must be located under the - same top directory. Note also that the install script does not copy files - or update environment variables. It only generates the run_test - script.</p> - - <p>Whenever you install a new version of Erlang/OTP, the run_test - script needs to be regenerated, or updated manually with new directory names - (new version numbers), for it to "see" the latest Common Test and Test Server - versions.</p> - + Common Test by means of the <c>ct_run</c> executable program, and/or + the interface functions in the <c>ct</c> module.</p> </section> </chapter> diff --git a/lib/common_test/doc/src/run_test_chapter.xml b/lib/common_test/doc/src/run_test_chapter.xml index 864f82cb63..df60e5f7f2 100644 --- a/lib/common_test/doc/src/run_test_chapter.xml +++ b/lib/common_test/doc/src/run_test_chapter.xml @@ -1005,6 +1005,31 @@ for starting the tests, the relaxed scanner mode is enabled by means of the tuple: <c>{allow_user_terms,true}</c></p> </section> + <section> + <title>Reading test specification terms</title> + <p>It's possible to look up terms in the current test specification + (i.e. the spec that's been used to configure and run the current test). + The function <c>get_testspec_terms()</c> returns a list of all test spec + terms (both config- and test terms) and <c>get_testspec_terms(Tags)</c> + returns the term (or a list of terms) matching the tag (or tags) in + <c>Tags</c>.</p> + <p>For example, in the test specification:</p> + <pre> + ... + {label, my_server_smoke_test}. + {config, "../../my_server_setup.cfg"}. + {config, "../../my_server_interface.cfg"}. + ...</pre> + <p>And in e.g. a test suite or a CT hook function:</p> + <pre> + ... + [{label,[{_Node,TestType}]}, {config,CfgFiles}] = + ct:get_testspec_terms([label,config]), + + [verify_my_server_cfg(TestType, CfgFile) || {Node,CfgFile} <- CfgFiles, + Node == node()]; + ...</pre> + </section> </section> <section> diff --git a/lib/common_test/install.sh.in b/lib/common_test/install.sh.in deleted file mode 100644 index 5108c7a259..0000000000 --- a/lib/common_test/install.sh.in +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/sh - -CT_ROOT=$1 -CT_VSN=@CT_VSN@ -TS_VSN=@TS_VSN@ - -if [ -z "$CT_ROOT" ] -then - echo "install.sh: need CT_ROOT (absolute) directory or 'local' as argument" - exit 1 -fi - -if [ $CT_ROOT = "local" ] -then - CT_DIR=`pwd` - cd priv - sed -e "s,@CTPATH@,$CT_DIR/ebin," \ - -e "s,@TSPATH@,$CT_DIR/../test_server/ebin," \ - run_test.in > bin/run_test - chmod 775 bin/run_test - echo "install successful, start script created in " $CT_ROOT/common_test-$CT_VSN/priv/bin -else - - if [ ! -d "$CT_ROOT" ] - then - echo "install.sh: CT_ROOT argument must be a valid directory" - exit 1 - fi - - if [ `echo $CT_ROOT | awk '{ print substr($1,1,1) }'` != "/" ] - then - echo "install.sh: need an absolute path to CT_ROOT" - exit 1 - fi - - if [ ! -d $CT_ROOT/common_test-$CT_VSN ] - then - echo "install.sh: The directory $CT_ROOT/common_test-$CT_VSN does not exist" - exit 1 - fi - - if [ -d $CT_ROOT/common_test-$CT_VSN/priv ] - then - cd $CT_ROOT/common_test-$CT_VSN/priv - sed -e "s;@CTPATH@;$CT_ROOT/common_test-$CT_VSN/ebin;" \ - -e "s;@TSPATH@;$CT_ROOT/test_server-$TS_VSN/ebin;" \ - run_test.in > bin/run_test - chmod 775 bin/run_test - echo "install successful, start script created in " $CT_ROOT/common_test-$CT_VSN/priv/bin - fi -fi - - diff --git a/lib/common_test/priv/Makefile.in b/lib/common_test/priv/Makefile.in index 1bc6b82ebb..7765b06f95 100644 --- a/lib/common_test/priv/Makefile.in +++ b/lib/common_test/priv/Makefile.in @@ -66,12 +66,7 @@ JS = jquery-latest.js jquery.tablesorter.min.js # Rules # -include ../../test_server/vsn.mk debug opt: - $(V_at)sed -e 's;@CT_VSN@;$(VSN);' \ - -e 's;@TS_VSN@;$(TEST_SERVER_VSN);' \ - ../install.sh.in > install.sh - - $(V_at)chmod -f 775 install.sh docs: diff --git a/lib/common_test/priv/bin/.gitignore b/lib/common_test/priv/bin/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 --- a/lib/common_test/priv/bin/.gitignore +++ /dev/null diff --git a/lib/common_test/priv/run_test.in b/lib/common_test/priv/run_test.in deleted file mode 100644 index 1508751e4f..0000000000 --- a/lib/common_test/priv/run_test.in +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/sh - -args="" - -while [ $1 ]; do - if [ $1 = "-config" ]; then - args="$args -ct_config"; - elif [ $1 = "-decrypt_key" ]; then - args="$args -ct_decrypt_key"; - elif [ $1 = "-decrypt_file" ]; then - args="$args -ct_decrypt_file"; - elif [ $1 = "-vts" ]; then - vts=1; - args="$args $1"; - elif [ $1 = "-browser" ]; then - browser=$2; - args="$args $1"; - elif [ $1 = "-shell" ]; then - shell=1; - args="$args $1"; - elif [ $1 = "-ctname" ]; then - ctname=$2; - args="$args"; - elif [ $1 = "-ctmaster" ]; then - master=1; - args="$args"; - else - args="$args $1" - fi - shift -done - -if [ $vts ]; then - erl -sname ct \ - -pa @CTPATH@ \ - -pa @TSPATH@ \ - -s webtool script_start vts $browser \ - -s ct_run script_start \ - $args; -elif [ $shell ]; then - erl -sname ct \ - -pa @CTPATH@ \ - -pa @TSPATH@ \ - -s ct_run script_start \ - $args; -elif [ $ctname ]; then - erl -sname $ctname \ - -pa @CTPATH@ \ - -pa @TSPATH@ \ - $args; -elif [ $master ]; then - erl -sname ct_master \ - -pa @CTPATH@ \ - -pa @TSPATH@ \ - $args; -else - erl -sname ct \ - -pa @CTPATH@ \ - -pa @TSPATH@ \ - -s ct_run script_start \ - -s erlang halt \ - $args -fi diff --git a/lib/common_test/src/ct.erl b/lib/common_test/src/ct.erl index 9d8fce2789..5ed1346f1e 100644 --- a/lib/common_test/src/ct.erl +++ b/lib/common_test/src/ct.erl @@ -79,6 +79,7 @@ %% Other interface functions -export([get_status/0, abort_current_testcase/1, get_event_mgr_ref/0, + get_testspec_terms/0, get_testspec_terms/1, encrypt_config_file/2, encrypt_config_file/3, decrypt_config_file/2, decrypt_config_file/3]). @@ -463,6 +464,50 @@ reload_config(Required)-> ct_config:reload_config(Required). %%%----------------------------------------------------------------- +%%% @spec get_testspec_terms() -> TestSpecTerms | undefined +%%% TestSpecTerms = [{Tag,Value}] +%%% Value = [term()] +%%% +%%% @doc Get a list of all test specification terms used to +%%% configure and run this test. +%%% +get_testspec_terms() -> + case ct_util:get_testdata(testspec) of + undefined -> + undefined; + CurrSpecRec -> + ct_testspec:testspec_rec2list(CurrSpecRec) + end. + +%%%----------------------------------------------------------------- +%%% @spec get_testspec_terms(Tags) -> TestSpecTerms | undefined +%%% Tags = [Tag] | Tag +%%% Tag = atom() +%%% TestSpecTerms = [{Tag,Value}] | {Tag,Value} +%%% Value = [{Node,term()}] | [term()] +%%% Node = atom() +%%% +%%% @doc Read one or more terms from the test specification used +%%% to configure and run this test. Tag is any valid test specification +%%% tag, such as e.g. <c>label</c>, <c>config</c>, <c>logdir</c>. +%%% User specific terms are also available to read if the +%%% <c>allow_user_terms</c> option has been set. Note that all value tuples +%%% returned, except user terms, will have the node name as first element. +%%% Note also that in order to read test terms, use <c>Tag = tests</c> +%%% (rather than <c>suites</c>, <c>groups</c> or <c>cases</c>). Value is +%%% then the list of *all* tests on the form: +%%% <c>[{Node,Dir,[{TestSpec,GroupsAndCases1},...]},...], where +%%% GroupsAndCases = [{Group,[Case]}] | [Case]</c>. +get_testspec_terms(Tags) -> + case ct_util:get_testdata(testspec) of + undefined -> + undefined; + CurrSpecRec -> + ct_testspec:testspec_rec2list(Tags, CurrSpecRec) + end. + + +%%%----------------------------------------------------------------- %%% @spec log(Format) -> ok %%% @equiv log(default,50,Format,[]) log(Format) -> diff --git a/lib/common_test/src/ct_framework.erl b/lib/common_test/src/ct_framework.erl index ea3d7c8218..91368d3137 100644 --- a/lib/common_test/src/ct_framework.erl +++ b/lib/common_test/src/ct_framework.erl @@ -113,6 +113,7 @@ init_tc1(?MODULE,_,error_in_suite,[Config0]) when is_list(Config0) -> ct_event:notify(#event{name=tc_start, node=node(), data={?MODULE,error_in_suite}}), + ct_suite_init(?MODULE, error_in_suite, [], Config0), case ?val(error, Config0) of undefined -> {fail,"unknown_error_in_suite"}; @@ -635,7 +636,20 @@ try_set_default(Name,Key,Info,Where) -> end_tc(Mod, Fun, Args) -> %% Have to keep end_tc/3 for backwards compatibility issues end_tc(Mod, Fun, Args, '$end_tc_dummy'). -end_tc(?MODULE,error_in_suite,_, _) -> % bad start! +end_tc(?MODULE,error_in_suite,{Result,[Args]},Return) -> + %% this clause gets called if CT has encountered a suite that + %% can't be executed + FinalNotify = + case ct_hooks:end_tc(?MODULE, error_in_suite, Args, Result, Return) of + '$ct_no_change' -> + Result; + HookResult -> + HookResult + end, + Event = #event{name=tc_done, + node=node(), + data={?MODULE,error_in_suite,tag(FinalNotify)}}, + ct_event:sync_notify(Event), ok; end_tc(Mod,Func,{TCPid,Result,[Args]}, Return) when is_pid(TCPid) -> end_tc(Mod,Func,TCPid,Result,Args,Return); @@ -1062,18 +1076,35 @@ get_all_cases1(_, []) -> get_all(Mod, ConfTests) -> case catch apply(Mod, all, []) of - {'EXIT',_} -> + {'EXIT',{undef,[{Mod,all,[],_} | _]}} -> Reason = case code:which(Mod) of non_existing -> list_to_atom(atom_to_list(Mod)++ - " can not be compiled or loaded"); + " can not be compiled or loaded"); _ -> list_to_atom(atom_to_list(Mod)++":all/0 is missing") end, %% this makes test_server call error_in_suite as first %% (and only) test case so we can report Reason properly [{?MODULE,error_in_suite,[[{error,Reason}]]}]; + {'EXIT',ExitReason} -> + case ct_util:get_testdata({error_in_suite,Mod}) of + undefined -> + ErrStr = io_lib:format("~n*** ERROR *** " + "~w:all/0 failed: ~p~n", + [Mod,ExitReason]), + io:format(user, ErrStr, []), + %% save the error info so it doesn't get printed twice + ct_util:set_testdata_async({{error_in_suite,Mod}, + ExitReason}); + _ExitReason -> + ct_util:delete_testdata({error_in_suite,Mod}) + end, + Reason = list_to_atom(atom_to_list(Mod)++":all/0 failed"), + %% this makes test_server call error_in_suite as first + %% (and only) test case so we can report Reason properly + [{?MODULE,error_in_suite,[[{error,Reason}]]}]; AllTCs when is_list(AllTCs) -> case catch save_seqs(Mod,AllTCs) of {error,What} -> @@ -1293,6 +1324,8 @@ report(What,Data) -> end, ct_logs:unregister_groupleader(ReportingPid), case {Func,Result} of + {error_in_suite,_} when Suite == ?MODULE -> + ok; {init_per_suite,_} -> ok; {end_per_suite,_} -> diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl index 56082086f6..8da10ee0f3 100644 --- a/lib/common_test/src/ct_gen_conn.erl +++ b/lib/common_test/src/ct_gen_conn.erl @@ -24,10 +24,9 @@ -module(ct_gen_conn). --compile(export_all). - --export([start/4, stop/1, get_conn_pid/1]). +-export([start/4, stop/1, get_conn_pid/1, check_opts/1]). -export([call/2, call/3, return/2, do_within_time/2]). +-export([log/3, start_log/1, cont_log/2, end_log/0]). %%---------------------------------------------------------------------- %% Exported types diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 4d5a75d354..7c8c720e13 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -2054,6 +2054,13 @@ runentry(Dir, Totals={Node,Label,Logs, ?testname_width-3)), lists:flatten(io_lib:format("~ts...",[Trunc])) end, + TotMissingStr = + if NotBuilt > 0 -> + ["<font color=\"red\">", + integer_to_list(NotBuilt),"</font>"]; + true -> + integer_to_list(NotBuilt) + end, Total = TotSucc+TotFail+AllSkip, A = xhtml(["<td align=center><font size=\"-1\">",Node, "</font></td>\n", @@ -2073,7 +2080,7 @@ runentry(Dir, Totals={Node,Label,Logs, "<td align=right>",TotFailStr,"</td>\n", "<td align=right>",integer_to_list(AllSkip), " (",UserSkipStr,"/",AutoSkipStr,")</td>\n", - "<td align=right>",integer_to_list(NotBuilt),"</td>\n"], + "<td align=right>",TotMissingStr,"</td>\n"], TotalsStr = A++B++C, XHTML = [xhtml("<tr>\n", ["<tr class=\"",odd_or_even(),"\">\n"]), diff --git a/lib/common_test/src/ct_run.erl b/lib/common_test/src/ct_run.erl index 4d74fd6a80..33c4f352b0 100644 --- a/lib/common_test/src/ct_run.erl +++ b/lib/common_test/src/ct_run.erl @@ -77,7 +77,8 @@ multiply_timetraps = 1, scale_timetraps = false, create_priv_dir, - testspecs = [], + testspec_files = [], + current_testspec, tests, starter}). @@ -485,8 +486,11 @@ execute_one_spec(TS, Opts, Args) -> case check_and_install_configfiles(AllConfig, TheLogDir, Opts) of ok -> % read tests from spec {Run,Skip} = ct_testspec:prepare_tests(TS, node()), - do_run(Run, Skip, Opts#opts{config=AllConfig, - logdir=TheLogDir}, Args); + Result = do_run(Run, Skip, Opts#opts{config=AllConfig, + logdir=TheLogDir, + current_testspec=TS}, Args), + ct_util:delete_testdata(testspec), + Result; Error -> Error end. @@ -577,7 +581,7 @@ combine_test_opts(TS, Specs, Opts) -> Opts#opts{label = Label, profile = Profile, - testspecs = Specs, + testspec_files = Specs, cover = Cover, cover_stop = CoverStop, logdir = which(logdir, LogDir), @@ -702,7 +706,7 @@ script_start4(#opts{label = Label, profile = Profile, logopts = LogOpts, verbosity = Verbosity, enable_builtin_hooks = EnableBuiltinHooks, - logdir = LogDir, testspecs = Specs}, _Args) -> + logdir = LogDir, testspec_files = Specs}, _Args) -> %% label - used by ct_logs application:set_env(common_test, test_label, Label), @@ -1103,7 +1107,7 @@ run_test2(StartOpts) -> undefined -> case lists:keysearch(prepared_tests, 1, StartOpts) of {value,{_,{Run,Skip},Specs}} -> % use prepared tests - run_prepared(Run, Skip, Opts#opts{testspecs = Specs}, + run_prepared(Run, Skip, Opts#opts{testspec_files = Specs}, StartOpts); false -> run_dir(Opts, StartOpts) @@ -1111,11 +1115,11 @@ run_test2(StartOpts) -> Specs -> Relaxed = get_start_opt(allow_user_terms, value, false, StartOpts), %% using testspec(s) as input for test - run_spec_file(Relaxed, Opts#opts{testspecs = Specs}, StartOpts) + run_spec_file(Relaxed, Opts#opts{testspec_files = Specs}, StartOpts) end. run_spec_file(Relaxed, - Opts = #opts{testspecs = Specs}, + Opts = #opts{testspec_files = Specs}, StartOpts) -> Specs1 = case Specs of [X|_] when is_integer(X) -> [Specs]; @@ -1154,7 +1158,10 @@ run_all_specs([{Specs,TS} | TSs], Opts, StartOpts, TotResult) -> log_ts_names(Specs), Combined = #opts{config = TSConfig} = combine_test_opts(TS, Specs, Opts), AllConfig = merge_vals([Opts#opts.config, TSConfig]), - try run_one_spec(TS, Combined#opts{config = AllConfig}, StartOpts) of + try run_one_spec(TS, + Combined#opts{config = AllConfig, + current_testspec=TS}, + StartOpts) of Result -> run_all_specs(TSs, Opts, StartOpts, [Result | TotResult]) catch @@ -1399,7 +1406,7 @@ run_testspec2(TestSpec) -> case check_and_install_configfiles( Opts#opts.config, LogDir1, Opts) of ok -> - Opts1 = Opts#opts{testspecs = [], + Opts1 = Opts#opts{testspec_files = [], logdir = LogDir1, include = AllInclude}, {Run,Skip} = ct_testspec:prepare_tests(TS, node()), @@ -1706,6 +1713,9 @@ compile_and_run(Tests, Skip, Opts, Args) -> ct_util:set_testdata({stylesheet,Opts#opts.stylesheet}), %% save logopts ct_util:set_testdata({logopts,Opts#opts.logopts}), + %% save info about current testspec (testspec record or undefined) + ct_util:set_testdata({testspec,Opts#opts.current_testspec}), + %% enable silent connections case Opts#opts.silent_connections of [] -> @@ -1720,7 +1730,7 @@ compile_and_run(Tests, Skip, Opts, Args) -> ct_logs:log("Silent connections", "~p", [Conns]) end end, - log_ts_names(Opts#opts.testspecs), + log_ts_names(Opts#opts.testspec_files), TestSuites = suite_tuples(Tests), {_TestSuites1,SuiteMakeErrors,AllMakeErrors} = diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index d906a267a1..96af55195f 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -29,7 +29,7 @@ %% Command timeout = 10 sec (time to wait for a command to return) %% Max no of reconnection attempts = 3 %% Reconnection interval = 5 sek (time to wait in between reconnection attempts) -%% Keep alive = true (will send NOP to the server every 10 sec if connection is idle) +%% Keep alive = true (will send NOP to the server every 8 sec if connection is idle) %% Polling limit = 0 (max number of times to poll to get a remaining string terminated) %% Polling interval = 1 sec (sleep time between polls)</pre> %% <p>These parameters can be altered by the user with the following @@ -1007,7 +1007,7 @@ silent_teln_expect(Name,Pid,Data,Pattern,Prx,Opts) -> put(silent,Old), Result. -%% teln_expect/5 +%% teln_expect/6 %% %% This function implements the expect functionality over telnet. In %% general there are three possible ways to go: diff --git a/lib/common_test/src/ct_telnet_client.erl b/lib/common_test/src/ct_telnet_client.erl index b0734d8d65..757ccc0aae 100644 --- a/lib/common_test/src/ct_telnet_client.erl +++ b/lib/common_test/src/ct_telnet_client.erl @@ -39,7 +39,7 @@ -define(TELNET_PORT, 23). -define(OPEN_TIMEOUT,10000). --define(IDLE_TIMEOUT,10000). +-define(IDLE_TIMEOUT,8000). %% telnet control characters -define(SE, 240). @@ -114,7 +114,7 @@ get_data(Pid) -> %%%----------------------------------------------------------------- %%% Internal functions init(Parent, Server, Port, Timeout, KeepAlive, ConnName) -> - case gen_tcp:connect(Server, Port, [list,{packet,0}], Timeout) of + case gen_tcp:connect(Server, Port, [list,{packet,0},{nodelay,true}], Timeout) of {ok,Sock} -> dbg("~p connected to: ~p (port: ~w, keep_alive: ~w)\n", [ConnName,Server,Port,KeepAlive]), diff --git a/lib/common_test/src/ct_testspec.erl b/lib/common_test/src/ct_testspec.erl index 10a9bdac67..10c3f2a938 100644 --- a/lib/common_test/src/ct_testspec.erl +++ b/lib/common_test/src/ct_testspec.erl @@ -27,6 +27,8 @@ collect_tests_from_list/2, collect_tests_from_list/3, collect_tests_from_file/2, collect_tests_from_file/3]). +-export([testspec_rec2list/1, testspec_rec2list/2]). + -include("ct_util.hrl"). -define(testspec_fields, record_info(fields, testspec)). @@ -973,7 +975,8 @@ add_tests([Term={Tag,all_nodes,Data}|Ts],Spec) -> should_be_added(Tag,Node,Data,Spec)], add_tests(Tests++Ts,Spec); invalid -> % ignore term - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; %% create one test entry per node in Nodes and reinsert add_tests([{Tag,[],Data}|Ts],Spec) -> @@ -1001,7 +1004,8 @@ add_tests([Term={Tag,NodeOrOther,Data}|Ts],Spec) -> handle_data(Tag,Node,Data,Spec), add_tests(Ts,mod_field(Spec,Tag,NodeIxData)); invalid -> % ignore term - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; false -> add_tests([{Tag,all_nodes,{NodeOrOther,Data}}|Ts],Spec) @@ -1012,13 +1016,15 @@ add_tests([Term={Tag,Data}|Ts],Spec) -> valid -> add_tests([{Tag,all_nodes,Data}|Ts],Spec); invalid -> - add_tests(Ts,Spec) + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Term]}) end; %% some other data than a tuple add_tests([Other|Ts],Spec) -> case get(relaxed) of - true -> - add_tests(Ts,Spec); + true -> + Unknown = Spec#testspec.unknown, + add_tests(Ts,Spec#testspec{unknown=Unknown++[Other]}); false -> throw({error,{undefined_term_in_spec,Other}}) end; @@ -1149,6 +1155,24 @@ per_node([N|Ns],Tag,Data,Refs) -> per_node([],_,_,_) -> []. +%% Change the testspec record "back" to a list of tuples +testspec_rec2list(Rec) -> + {Terms,_} = lists:mapfoldl(fun(unknown, Pos) -> + {element(Pos, Rec),Pos+1}; + (F, Pos) -> + {{F,element(Pos, Rec)},Pos+1} + end,2,?testspec_fields), + lists:flatten(Terms). + +%% Extract one or more values from a testspec record and +%% return the result as a list of tuples +testspec_rec2list(Field, Rec) when is_atom(Field) -> + [Term] = testspec_rec2list([Field], Rec), + Term; +testspec_rec2list(Fields, Rec) -> + Terms = testspec_rec2list(Rec), + [{Field,proplists:get_value(Field, Terms)} || Field <- Fields]. + %% read the value for FieldName in record Rec#testspec read_field(Rec, FieldName) -> catch lists:foldl(fun(F, Pos) when F == FieldName -> diff --git a/lib/common_test/src/ct_util.hrl b/lib/common_test/src/ct_util.hrl index 845bb55486..f4cf407856 100644 --- a/lib/common_test/src/ct_util.hrl +++ b/lib/common_test/src/ct_util.hrl @@ -55,6 +55,7 @@ create_priv_dir=[], alias=[], tests=[], + unknown=[], merge_tests=true}). -record(cover, {app=none, diff --git a/lib/common_test/test/ct_auto_compile_SUITE.erl b/lib/common_test/test/ct_auto_compile_SUITE.erl index cc546ed30d..3e4da31ab4 100644 --- a/lib/common_test/test/ct_auto_compile_SUITE.erl +++ b/lib/common_test/test/ct_auto_compile_SUITE.erl @@ -108,6 +108,8 @@ ac_spec(Config) when is_list(Config) -> PrivDir = ?config(priv_dir, Config), file:copy(filename:join(DataDir, "bad_SUITE.erl"), filename:join(PrivDir, "bad_SUITE.erl")), + Suite = filename:join(DataDir, "dummy_SUITE"), + compile:file(Suite, [{outdir,PrivDir}]), TestSpec = [{label,ac_spec}, {auto_compile,false}, {suites,PrivDir,all}], @@ -160,28 +162,34 @@ events_to_check(Test, N) -> test_events(ac_flag) -> [ - {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}}, - {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {ct_test_support_eh,start_info,{1,1,3}}, - {ct_test_support_eh,tc_start,{dummy_SUITE,init_per_suite}}, - {ct_test_support_eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, - {ct_test_support_eh,test_stats,{1,1,{1,0}}}, - {ct_test_support_eh,tc_start,{dummy_SUITE,end_per_suite}}, - {ct_test_support_eh,tc_done,{dummy_SUITE,end_per_suite,ok}}, - {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}}, - {ct_test_support_eh,stop_logging,[]} + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,3}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done,{ct_framework,error_in_suite, + {failed,{error,'bad_SUITE can not be compiled or loaded'}}}}, + {?eh,tc_start,{dummy_SUITE,init_per_suite}}, + {?eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, + {?eh,test_stats,{1,1,{1,0}}}, + {?eh,tc_start,{dummy_SUITE,end_per_suite}}, + {?eh,tc_done,{dummy_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} ]; test_events(ac_spec) -> [ - {ct_test_support_eh,start_logging,{'DEF','RUNDIR'}}, - {ct_test_support_eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, - {ct_test_support_eh,start_info,{1,1,3}}, - {ct_test_support_eh,tc_start,{dummy_SUITE,init_per_suite}}, - {ct_test_support_eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, - {ct_test_support_eh,test_stats,{1,1,{1,0}}}, - {ct_test_support_eh,tc_start,{dummy_SUITE,end_per_suite}}, - {ct_test_support_eh,tc_done,{dummy_SUITE,end_per_suite,ok}}, - {ct_test_support_eh,test_done,{'DEF','STOP_TIME'}}, - {ct_test_support_eh,stop_logging,[]} + {?eh,start_logging,{'DEF','RUNDIR'}}, + {?eh,test_start,{'DEF',{'START_TIME','LOGDIR'}}}, + {?eh,start_info,{1,1,3}}, + {?eh,tc_start,{ct_framework,error_in_suite}}, + {?eh,tc_done,{ct_framework,error_in_suite, + {failed,{error,'bad_SUITE can not be compiled or loaded'}}}}, + {?eh,tc_start,{dummy_SUITE,init_per_suite}}, + {?eh,tc_done,{dummy_SUITE,init_per_suite,ok}}, + {?eh,test_stats,{1,1,{1,0}}}, + {?eh,tc_start,{dummy_SUITE,end_per_suite}}, + {?eh,tc_done,{dummy_SUITE,end_per_suite,ok}}, + {?eh,test_done,{'DEF','STOP_TIME'}}, + {?eh,stop_logging,[]} ]. diff --git a/lib/common_test/test/ct_test_server_if_1_SUITE.erl b/lib/common_test/test/ct_test_server_if_1_SUITE.erl index b6ef3062d4..214cb60c0d 100644 --- a/lib/common_test/test/ct_test_server_if_1_SUITE.erl +++ b/lib/common_test/test/ct_test_server_if_1_SUITE.erl @@ -236,14 +236,13 @@ test_events(ts_if_1) -> {ts_if_2_SUITE,end_per_suite, {failed,{error,{suite0_failed,{exited,suite0_goes_boom}}}}}}, - {?eh,tc_start,{ct_framework,error_in_suite}}, - {?eh,test_stats,{2,6,{4,7}}}, - + {?eh,tc_done,{ct_framework,error_in_suite, + {failed,{error,'ts_if_3_SUITE:all/0 is missing'}}}}, {?eh,tc_start,{ct_framework,error_in_suite}}, - {?eh,test_stats,{2,7,{4,7}}}, - + {?eh,tc_done,{ct_framework,error_in_suite, + {failed,{error,'Bad return value from ts_if_4_SUITE:all/0'}}}}, {?eh,tc_start,{ts_if_5_SUITE,init_per_suite}}, {?eh,tc_done,{ts_if_5_SUITE,init_per_suite, @@ -252,7 +251,7 @@ test_events(ts_if_1) -> {?eh,tc_auto_skip, {ts_if_5_SUITE,my_test_case, {require_failed_in_suite0,{not_available,undef_variable}}}}, - {?eh,test_stats,{2,7,{4,8}}}, + {?eh,test_stats,{2,5,{4,8}}}, {?eh,tc_auto_skip, {ts_if_5_SUITE,end_per_suite, {require_failed_in_suite0,{not_available,undef_variable}}}}, @@ -264,7 +263,7 @@ test_events(ts_if_1) -> {?eh,tc_auto_skip, {ts_if_6_SUITE,tc1, {failed,{error,{suite0_failed,{exited,suite0_byebye}}}}}}, - {?eh,test_stats,{2,7,{4,9}}}, + {?eh,test_stats,{2,5,{4,9}}}, {?eh,tc_auto_skip, {ct_framework,end_per_suite, {failed,{error,{suite0_failed,{exited,suite0_byebye}}}}}}, @@ -274,13 +273,13 @@ test_events(ts_if_1) -> {?eh,tc_done,{ct_framework,init_per_suite,ok}}, {?eh,tc_done, {ts_if_7_SUITE,tc1,{auto_skipped,{testcase0_failed,bad_return_value}}}}, - {?eh,test_stats,{2,7,{4,10}}}, + {?eh,test_stats,{2,5,{4,10}}}, {?eh,tc_done,{ts_if_7_SUITE, {init_per_group,g1,[]}, {auto_skipped,{group0_failed,bad_return_value}}}}, {?eh,tc_auto_skip, {ts_if_7_SUITE,{tc2,g1},{group0_failed,bad_return_value}}}, - {?eh,test_stats,{2,7,{4,11}}}, + {?eh,test_stats,{2,5,{4,11}}}, {?eh,tc_auto_skip, {ts_if_7_SUITE,{end_per_group,g1},{group0_failed,bad_return_value}}}, @@ -288,7 +287,7 @@ test_events(ts_if_1) -> {?eh,tc_done,{ts_if_7_SUITE,{init_per_group,g2,[]},ok}}, {?eh,tc_done,{ts_if_7_SUITE,tc2, {auto_skipped,{testcase0_failed,bad_return_value}}}}, - {?eh,test_stats,{2,7,{4,12}}}, + {?eh,test_stats,{2,5,{4,12}}}, {?eh,tc_start,{ts_if_7_SUITE,{end_per_group,g2,[]}}}, {?eh,tc_done,{ts_if_7_SUITE,{end_per_group,g2,[]},ok}}], @@ -300,17 +299,17 @@ test_events(ts_if_1) -> {?eh,tc_done,{ct_framework,init_per_suite,ok}}, {?eh,tc_start,{ts_if_8_SUITE,tc1}}, {?eh,tc_done,{ts_if_8_SUITE,tc1,{failed,{error,failed_on_purpose}}}}, - {?eh,test_stats,{2,8,{4,12}}}, + {?eh,test_stats,{2,6,{4,12}}}, {?eh,tc_start,{ct_framework,end_per_suite}}, {?eh,tc_done,{ct_framework,end_per_suite,ok}}, {?eh,tc_user_skip,{skipped_by_spec_1_SUITE,all,"should be skipped"}}, - {?eh,test_stats,{2,8,{5,12}}}, + {?eh,test_stats,{2,6,{5,12}}}, {?eh,tc_start,{skipped_by_spec_2_SUITE,init_per_suite}}, {?eh,tc_done,{skipped_by_spec_2_SUITE,init_per_suite,ok}}, {?eh,tc_user_skip,{skipped_by_spec_2_SUITE,tc1,"should be skipped"}}, - {?eh,test_stats,{2,8,{6,12}}}, + {?eh,test_stats,{2,6,{6,12}}}, {?eh,tc_start,{skipped_by_spec_2_SUITE,end_per_suite}}, {?eh,tc_done,{skipped_by_spec_2_SUITE,end_per_suite,ok}}, diff --git a/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t11_SUITE.erl b/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t11_SUITE.erl index b8216c3596..cfc6fa93d7 100644 --- a/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t11_SUITE.erl +++ b/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t11_SUITE.erl @@ -41,8 +41,12 @@ suite() -> %% @end %%-------------------------------------------------------------------- init_per_suite(Config) -> + + TCName = ct:get_config(tcname), + CfgFiles = ct:get_config(file,undefined,[all]), + %% verify that expected config file can be read - case {ct:get_config(tcname),ct:get_config(file,undefined,[all])} of + case {TCName,CfgFiles} of {start_separate,[cfg11]} -> ok; {start_join,[cfg11,cfg21]} -> ok; {incl_separate1,[cfg11]} -> ok; @@ -56,6 +60,28 @@ init_per_suite(Config) -> _ -> ok end, + + %% test the get_testspec_terms functionality + if CfgFiles /= undefined -> + TSTerms = case ct:get_testspec_terms() of + undefined -> exit('testspec should not be undefined'); + Result -> Result + end, + true = lists:keymember(config, 1, TSTerms), + {config,TSCfgFiles} = ct:get_testspec_terms(config), + [{config,TSCfgFiles},{tests,Tests}] = + ct:get_testspec_terms([config,tests]), + CfgNames = [list_to_atom(filename:basename(TSCfgFile)) || + {Node,TSCfgFile} <- TSCfgFiles, Node == node()], + true = (length(CfgNames) == length(CfgFiles)), + [true = lists:member(CfgName,CfgFiles) || CfgName <- CfgNames], + true = lists:any(fun({{_Node,_Dir},Suites}) -> + lists:keymember(?MODULE, 1, Suites) + end, Tests); + true -> + ok + end, + Config. %%-------------------------------------------------------------------- diff --git a/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t12_SUITE.erl b/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t12_SUITE.erl index 7c51aca246..c3faebbd64 100644 --- a/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t12_SUITE.erl +++ b/lib/common_test/test/ct_testspec_3_SUITE_data/tests1/t12_SUITE.erl @@ -55,7 +55,7 @@ init_per_suite(Config) -> {incl_both2,[cfg11,cfg12,cfg21]} -> ok; {incl_both2,[cfg21]} -> ok; _ -> ok - end, + end, Config. %%-------------------------------------------------------------------- diff --git a/lib/common_test/test/ct_testspec_3_SUITE_data/tests2/t21_SUITE.erl b/lib/common_test/test/ct_testspec_3_SUITE_data/tests2/t21_SUITE.erl index 36c1b4279b..e189b168c7 100644 --- a/lib/common_test/test/ct_testspec_3_SUITE_data/tests2/t21_SUITE.erl +++ b/lib/common_test/test/ct_testspec_3_SUITE_data/tests2/t21_SUITE.erl @@ -41,8 +41,11 @@ suite() -> %% @end %%-------------------------------------------------------------------- init_per_suite(Config) -> + TCName = ct:get_config(tcname), + CfgFiles = ct:get_config(file,undefined,[all]), + %% verify that expected config file can be read - case {ct:get_config(tcname),ct:get_config(file,undefined,[all])} of + case {TCName,CfgFiles} of {start_separate,[cfg11]} -> ok; {start_join,[cfg11,cfg21]} -> ok; {incl_separate1,[cfg11]} -> ok; @@ -55,6 +58,28 @@ init_per_suite(Config) -> {incl_both2,[cfg11]} -> ok; _ -> ok end, + + %% test the get_testspec_terms functionality + if CfgFiles /= undefined -> + TSTerms = case ct:get_testspec_terms() of + undefined -> exit('testspec should not be undefined'); + Result -> Result + end, + true = lists:keymember(config, 1, TSTerms), + {config,TSCfgFiles} = ct:get_testspec_terms(config), + [{config,TSCfgFiles},{tests,Tests}] = + ct:get_testspec_terms([config,tests]), + CfgNames = [list_to_atom(filename:basename(TSCfgFile)) || + {Node,TSCfgFile} <- TSCfgFiles, Node == node()], + true = (length(CfgNames) == length(CfgFiles)), + [true = lists:member(CfgName,CfgFiles) || CfgName <- CfgNames], + true = lists:any(fun({{_Node,_Dir},Suites}) -> + lists:keymember(?MODULE, 1, Suites) + end, Tests); + true -> + ok + end, + Config. %%-------------------------------------------------------------------- diff --git a/lib/inets/src/inets_app/inets_lib.erl b/lib/inets/src/inets_app/inets_lib.erl index e79959f678..fa6adaebd0 100644 --- a/lib/inets/src/inets_app/inets_lib.erl +++ b/lib/inets/src/inets_app/inets_lib.erl @@ -26,7 +26,7 @@ %% Help function, elapsed milliseconds since T0 millisec_passed({_,_,_} = T0 ) -> %% OTP 17 and earlier - timer:now_diff(inets_time_compat:monotonic_time(), T0) div 1000; + timer:now_diff(inets_time_compat:timestamp(), T0) div 1000; millisec_passed(T0) -> %% OTP 18 diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl index e6ce85c379..380c685869 100644 --- a/lib/kernel/src/user_drv.erl +++ b/lib/kernel/src/user_drv.erl @@ -133,6 +133,7 @@ server1(Iport, Oport, Shell) -> flatten(io_lib:format("~ts\n", [erlang:system_info(system_version)]))}, Iport, Oport), + %% Enter the server loop. server_loop(Iport, Oport, Curr, User, Gr, queue:new()). @@ -315,6 +316,9 @@ handle_escape(Iport, Oport, User, Gr, IOQueue) -> _ -> % {ok,jcl} | undefined io_request({put_chars,unicode,"\nUser switch command\n"}, Iport, Oport), + %% init edlin used by switch command and have it copy the + %% text buffer from current group process + edlin:init(gr_cur_pid(Gr)), server_loop(Iport, Oport, User, switch_loop(Iport, Oport, Gr), IOQueue) end. diff --git a/lib/stdlib/src/edlin.erl b/lib/stdlib/src/edlin.erl index b3bc5f6d92..362669545e 100644 --- a/lib/stdlib/src/edlin.erl +++ b/lib/stdlib/src/edlin.erl @@ -21,7 +21,7 @@ %% A simple Emacs-like line editor. %% About Latin-1 characters: see the beginning of erl_scan.erl. --export([init/0,start/1,start/2,edit_line/2,prefix_arg/1]). +-export([init/0,init/1,start/1,start/2,edit_line/2,prefix_arg/1]). -export([erase_line/1,erase_inp/1,redraw_line/1]). -export([length_before/1,length_after/1,prompt/1]). -export([current_line/1, current_chars/1]). @@ -44,6 +44,20 @@ init() -> put(kill_buffer, []). +init(Pid) -> + %% copy the kill_buffer from the process Pid + CopiedKillBuf = + case erlang:process_info(Pid, dictionary) of + {dictionary,Dict} -> + case proplists:get_value(kill_buffer, Dict) of + undefined -> []; + Buf -> Buf + end; + undefined -> + [] + end, + put(kill_buffer, CopiedKillBuf). + %% start(Prompt) %% edit(Characters, Continuation) %% Return diff --git a/lib/test_server/doc/src/Makefile b/lib/test_server/doc/src/Makefile index 8c7fa99886..421079ac94 100644 --- a/lib/test_server/doc/src/Makefile +++ b/lib/test_server/doc/src/Makefile @@ -27,6 +27,10 @@ include ../../vsn.mk VSN=$(TEST_SERVER_VSN) APPLICATION=test_server +DOC_EXTRA_FRONT_PAGE_INFO=Important note: \ +The Test Server application is obsolete and will be removed \ +in the next major OTP release + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- diff --git a/lib/test_server/src/test_server_ctrl.erl b/lib/test_server/src/test_server_ctrl.erl index bef0658b6d..d0c8a1ebe8 100644 --- a/lib/test_server/src/test_server_ctrl.erl +++ b/lib/test_server/src/test_server_ctrl.erl @@ -4776,17 +4776,25 @@ collect_case_subcases(Mod, Case, SubCases, St0, Mode) -> collect_files(Dir, Pattern, St, Mode) -> {ok,Cwd} = file:get_cwd(), Dir1 = filename:join(Cwd, Dir), - Wc = filename:join([Dir1,Pattern++code:objfile_extension()]), + Wc = filename:join([Dir1,Pattern++"{.erl,"++code:objfile_extension()++"}"]), case catch filelib:wildcard(Wc) of {'EXIT', Reason} -> io:format("Could not collect files: ~p~n", [Reason]), {error,{collect_fail,Dir,Pattern}}; - Mods0 -> - Mods = [{path_to_module(Mod),all} || Mod <- lists:sort(Mods0)], - collect_cases(Mods, St, Mode) + Files -> + %% convert to module names and remove duplicates + Mods = lists:foldl(fun(File, Acc) -> + Mod = fullname_to_mod(File), + case lists:member(Mod, Acc) of + true -> Acc; + false -> [Mod | Acc] + end + end, [], Files), + Tests = [{Mod,all} || Mod <- lists:sort(Mods)], + collect_cases(Tests, St, Mode) end. -path_to_module(Path) when is_list(Path) -> +fullname_to_mod(Path) when is_list(Path) -> %% If this is called with a binary, then we are probably in +fnu %% mode and have found a beam file with name encoded as latin1. We %% will let this crash since it can not work to load such a module diff --git a/lib/test_server/src/ts.erl b/lib/test_server/src/ts.erl index d6d2e865e2..469593e947 100644 --- a/lib/test_server/src/ts.erl +++ b/lib/test_server/src/ts.erl @@ -24,15 +24,20 @@ -module(ts). --export([run/0, run/1, run/2, run/3, run/4, run/5, - tests/0, tests/1, +-export([cl_run/1, + run/0, run/1, run/2, run/3, run/4, run/5, + run_category/1, run_category/2, run_category/3, + tests/0, tests/1, suites/1, categories/1, install/0, install/1, - bench/0, bench/1, bench/2, benchmarks/0, - smoke_test/0, smoke_test/1,smoke_test/2, smoke_tests/0, estone/0, estone/1, cross_cover_analyse/1, compile_testcases/0, compile_testcases/1, help/0]). + +%% Functions kept for backwards compatibility +-export([bench/0, bench/1, bench/2, benchmarks/0, + smoke_test/0, smoke_test/1,smoke_test/2, smoke_tests/0]). + -export([i/0, l/1, r/0, r/1, r/2, r/3]). %%%---------------------------------------------------------------------- @@ -82,10 +87,13 @@ -define( install_help, [ - " ts:install() - Install TS with no Options.\n" - " ts:install([Options]) - Install TS with Options\n" + " ts:install()\n", + " Install ts with no options.\n", + "\n", + " ts:install(Options)\n", + " Install ts with a list of options, see below.\n", "\n", - "Installation options supported:\n", + "Installation options supported:\n\n", " {longnames, true} - Use fully qualified hostnames\n", " {verbose, Level} - Sets verbosity level for TS output (0,1,2), 0 is\n" " quiet(default).\n" @@ -110,21 +118,64 @@ help() -> end. help(uninstalled) -> - H = ["TS is not installed yet. To install use:\n\n"], + H = ["ts is not yet installed. To install use:\n\n"], show_help([H,?install_help]); help(installed) -> - H = ["Run functions:\n", - " ts:run() - Run all available tests.\n", - " ts:run(Spec) - Run all tests in given test spec file.\n", - " The spec file is actually ../*_test/Spec.spec\n", - " ts:run([Specs]) - Run all tests in all given test spec files.\n", - " ts:run(Spec, Mod) - Run a single test suite.\n", - " ts:run(Spec, Mod, Case)\n", - " - Run a single test case.\n", - " All above run functions can have an additional Options argument\n", - " which is a list of options.\n", + H = ["\n", + "Run functions:\n\n", + " ts:run()\n", + " Run the tests for all apps. The tests are defined by the\n", + " main test specification for each app: ../App_test/App.spec.\n", + "\n", + " ts:run(Apps)\n", + " Apps = atom() | [atom()]\n", + " Run the tests for an app, or set of apps. The tests are\n", + " defined by the main test specification for each app:\n", + " ../App_test/App.spec.\n", + "\n", + " ts:run(App, Suites)\n", + " App = atom(), Suites = atom() | [atom()]\n", + " Run one or more test suites for App (i.e. modules named\n", + " *_SUITE.erl, located in ../App_test/).\n", + "\n", + " ts:run(App, Suite, TestCases)\n", + " App = atom(), Suite = atom(),\n", + " TestCases = TCs | {testcase,TCs}, TCs = atom() | [atom()]\n", + " Run one or more test cases (functions) in Suite.\n", + "\n", + " ts:run(App, Suite, {group,Groups})\n", + " App = atom(), Suite = atom(), Groups = atom() | [atom()]\n", + " Run one or more test case groups in Suite.\n", + "\n", + " ts:run(App, Suite, {group,Group}, {testcase,TestCases})\n", + " App = atom(), Suite = atom(), Group = atom(),\n", + " TestCases = atom() | [atom()]\n", + " Run one or more test cases in a test case group in Suite.\n", + "\n", + " ts:run_category(TestCategory)\n", + " TestCategory = smoke | essential | bench | atom()\n", + " Run the specified category of tests for all apps.\n", + " For each app, the tests are defined by the specification:\n", + " ../App_test/App_TestCategory.spec.\n", + "\n", + " ts:run_category(Apps, TestCategory)\n", + " Apps = atom() | [atom()],\n", + " TestCategory = smoke | essential | bench | atom()\n", + " Run the specified category of tests for the given app or apps.\n", "\n", - "Run options supported:\n", + " Note that the test category parameter may have arbitrary value,\n", + " but should correspond to an existing test specification with file\n", + " name: ../App_test/App_TestCategory.spec.\n", + " Predefined categories exist for smoke tests, essential tests and\n", + " benchmark tests. The corresponding specs are:\n", + " ../*_test/Spec_smoke.spec, ../*_test/Spec_essential.spec and\n", + " ../*_test/Spec_bench.spec.\n", + "\n", + " All above run functions can take an additional last argument,\n", + " Options, which is a list of options (e.g. ts:run(App, Options),\n", + " or ts:run_category(Apps, TestCategory, Options)).\n", + "\n", + "Run options supported:\n\n", " batch - Do not start a new xterm\n", " {verbose, Level} - Same as the verbosity option for install\n", " verbose - Same as {verbose, 1}\n", @@ -143,47 +194,46 @@ help(installed) -> " files are. The default location is\n" " tests/test_server/.\n" "\n", - "Supported trace information elements\n", + "Supported trace information elements:\n\n", " {tp | tpl, Mod, [] | match_spec()}\n", " {tp | tpl, Mod, Func, [] | match_spec()}\n", " {tp | tpl, Mod, Func, Arity, [] | match_spec()}\n", " {ctp | ctpl, Mod}\n", " {ctp | ctpl, Mod, Func}\n", " {ctp | ctpl, Mod, Func, Arity}\n", + "\n\n", + "Support functions:\n\n", + " ts:tests()\n", + " Returns all apps available for testing.\n", + "\n", + " ts:tests(TestCategory)\n", + " Returns all apps that provide tests in the given category.\n", + "\n", + " ts:suites(App)\n", + " Returns all available test suites for App,\n", + " i.e. ../App_test/*_SUITE.erl\n", + "\n", + " ts:categories(App)\n", + " Returns all test categories available for App.\n", "\n", - "Support functions:\n", - " ts:tests() - Shows all available families of tests.\n", - " ts:tests(Spec) - Shows all available test modules in Spec,\n", - " i.e. ../Spec_test/*_SUITE.erl\n", - " ts:estone() - Run estone_SUITE in kernel application with\n" - " no run options\n", - " ts:estone(Opts) - Run estone_SUITE in kernel application with\n" - " the given run options\n", - " ts:cross_cover_analyse(Level)\n" - " - Used after ts:run with option cover or \n" - " cover_details. Analyses modules specified with\n" - " a 'cross' statement in the cover spec file.\n" - " Level can be 'overview' or 'details'.\n", - " ts:compile_testcases()~n" - " ts:compile_testcases(Apps)~n" - " - Compile all testcases for usage in a cross ~n" - " compile environment." - " \n" - "Benchmark functions:\n" - " ts:benchmarks() - Get all available families of benchmarks\n" - " ts:bench() - Runs all benchmarks\n" - " ts:bench(Spec) - Runs all benchmarks in the given spec file.\n" - " The spec file is actually ../*_test/Spec_bench.spec\n\n" - " ts:bench can take the same Options argument as ts:run.\n" - "Smoke test functions:\n" - " ts:smoke_tests() - Get all available families of smoke tests\n" - " ts:smoke_test() - Runs all smoke tests\n" - " ts:smoke_test(Spec)\n" - " - Runs all smoke tests in the given spec file.\n" - " The spec file is actually ../*_test/Spec_smoke.spec\n\n" - " ts:smoke_test can take the same Options argument as ts:run.\n" - "\n" - "Installation (already done):\n" + " ts:estone()\n", + " Runs estone_SUITE in the kernel application with no run options\n", + "\n", + " ts:estone(Opts)\n", + " Runs estone_SUITE in the kernel application with the given\n", + " run options\n", + "\n", + " ts:cross_cover_analyse(Level)\n", + " Use after ts:run with option cover or cover_details. Analyses\n", + " modules specified with a 'cross' statement in the cover spec file.\n", + " Level can be 'overview' or 'details'.\n", + "\n", + " ts:compile_testcases()\n", + " ts:compile_testcases(Apps)\n", + " Compiles all test cases for the given apps, for usage in a\n", + " cross compilation environment.\n", + "\n\n", + "Installation (already done):\n\n" ], show_help([H,?install_help]). @@ -212,86 +262,128 @@ run_all(_Vars) -> run_some([], _Opts) -> ok; -run_some([{Spec,Mod}|Specs], Opts) -> - case run(Spec, Mod, Opts) of +run_some([{App,Mod}|Apps], Opts) -> + case run(App, Mod, Opts) of ok -> ok; - Error -> io:format("~p: ~p~n",[{Spec,Mod},Error]) + Error -> io:format("~p: ~p~n",[{App,Mod},Error]) end, - run_some(Specs, Opts); -run_some([Spec|Specs], Opts) -> - case run(Spec, Opts) of + run_some(Apps, Opts); +run_some([App|Apps], Opts) -> + case run(App, Opts) of ok -> ok; - Error -> io:format("~p: ~p~n",[Spec,Error]) + Error -> io:format("~p: ~p~n",[App,Error]) end, - run_some(Specs, Opts). + run_some(Apps, Opts). + +%% This can be used from command line. Both App and +%% TestCategory must be specified. App may be 'all' +%% and TestCategory may be 'main'. Examples: +%% erl -s ts cl_run kernel smoke <options> +%% erl -s ts cl_run kernel main <options> +%% erl -s ts cl_run all essential <options> +%% erl -s ts cl_run all main <options> +%% When using the 'main' category and running with cover, +%% one can also use the cross_cover_analysis flag. +cl_run([App,Cat|Options0]) when is_atom(App) -> -%% Runs one test spec (interactive). -run(Testspec) when is_atom(Testspec) -> - Options=check_test_get_opts(Testspec, []), - File = atom_to_list(Testspec), - run_test(File, [{spec,[File++".spec"]}], Options); - -%% This can be used from command line, e.g. -%% erl -s ts run all_tests <config> -%% When using the all_tests flag and running with cover, one can also -%% use the cross_cover_analysis flag. -run([all_tests|Config0]) -> AllAtomsFun = fun(X) when is_atom(X) -> true; (_) -> false end, - Config1 = - case lists:all(AllAtomsFun,Config0) of + Options1 = + case lists:all(AllAtomsFun, Options0) of true -> %% Could be from command line - lists:map(fun(Conf)->to_erlang_term(Conf) end,Config0)--[batch]; + lists:map(fun(Opt) -> + to_erlang_term(Opt) + end, Options0) -- [batch]; false -> - Config0--[batch] + Options0 -- [batch] end, %% Make sure there is exactly one occurence of 'batch' - Config2 = [batch|Config1], - - R = run(tests(),Config2), - - case check_for_cross_cover_analysis_flag(Config2) of + Options2 = [batch|Options1], + + Result = + case {App,Cat} of + {all,main} -> + run(tests(), Options2); + {all,Cat} -> + run_category(Cat, Options2); + {_,main} -> + run(App, Options2); + {_,Cat} -> + run_category(App, Cat, Options2) + end, + case check_for_cross_cover_analysis_flag(Options2) of false -> ok; Level -> cross_cover_analyse(Level) end, + Result. - R; +%% run/1 +%% Runs tests for one app (interactive). +run(App) when is_atom(App) -> + Options = check_test_get_opts(App, []), + File = atom_to_list(App), + run_test(File, [{spec,[File++".spec"]},{allow_user_terms,true}], Options); -%% ts:run(ListOfTests) -run(List) when is_list(List) -> - run(List, [batch]). - -run(List, Opts) when is_list(List), is_list(Opts) -> - run_some(List, Opts); +%% This can be used from command line, e.g. +%% erl -s ts run all <options> +%% erl -s ts run main <options> +run([all,main|Opts]) -> + cl_run([all,main|Opts]); +run([all|Opts]) -> + cl_run([all,main|Opts]); +run([main|Opts]) -> + cl_run([all,main|Opts]); +%% Backwards compatible +run([all_tests|Opts]) -> + cl_run([all,main|Opts]); + +%% run/1 +%% Runs the main tests for all available apps +run(Apps) when is_list(Apps) -> + run(Apps, [batch]). %% run/2 -%% Runs one test spec with list of suites or with options -run(Testspec, ModsOrConfig) when is_atom(Testspec), - is_list(ModsOrConfig) -> - case is_list_of_suites(ModsOrConfig) of +%% Runs the main tests for all available apps +run(Apps, Opts) when is_list(Apps), is_list(Opts) -> + run_some(Apps, Opts); + +%% Runs tests for one app with list of suites or with options +run(App, ModsOrOpts) when is_atom(App), + is_list(ModsOrOpts) -> + case is_list_of_suites(ModsOrOpts) of false -> - run(Testspec, {config_list,ModsOrConfig}); + run(App, {opts_list,ModsOrOpts}); true -> - run_some([{Testspec,M} || M <- ModsOrConfig], + run_some([{App,M} || M <- ModsOrOpts], [batch]) end; -run(Testspec, {config_list,Config}) -> - Options=check_test_get_opts(Testspec, Config), - IsSmoke=proplists:get_value(smoke,Config), - File=atom_to_list(Testspec), + +run(App, {opts_list,Opts}) -> + Options = check_test_get_opts(App, Opts), + File = atom_to_list(App), + + %% check if other test category than main has been specified + {CatSpecName,TestCat} = + case proplists:get_value(test_category, Opts) of + undefined -> + {"",main}; + Cat -> + {"_" ++ atom_to_list(Cat),Cat} + end, + WhatToDo = - case Testspec of + case App of %% Known to exist but fails generic tests below emulator -> test; system -> test; erl_interface -> test; epmd -> test; _ -> - case code:lib_dir(Testspec) of + case code:lib_dir(App) of {error,bad_name} -> %% Application does not exist skip; @@ -313,92 +405,167 @@ run(Testspec, {config_list,Config}) -> end end end, - Spec = - case WhatToDo of - skip -> - create_skip_spec(Testspec, tests(Testspec)); - test when IsSmoke -> - File++"_smoke.spec"; - test -> - File++".spec" - end, - run_test(File, [{spec,[Spec]}], Options); -%% Runs one module in a spec (interactive) -run(Testspec, Mod) when is_atom(Testspec), is_atom(Mod) -> - run_test({atom_to_list(Testspec),Mod}, + case WhatToDo of + skip -> + SkipSpec = create_skip_spec(App, suites(App)), + run_test(File, [{spec,[SkipSpec]}], Options); + test when TestCat == bench -> + check_and_run(fun(Vars) -> + ts_benchmark:run([App], Options, Vars) + end); + test -> + Spec = File ++ CatSpecName ++ ".spec", + run_test(File, [{spec,[Spec]},{allow_user_terms,true}], Options) + end; + +%% Runs one module for an app (interactive) +run(App, Mod) when is_atom(App), is_atom(Mod) -> + run_test({atom_to_list(App),Mod}, [{suite,Mod}], [interactive]). %% run/3 -%% Run one module in a spec with Config -run(Testspec, Mod, Config) when is_atom(Testspec), - is_atom(Mod), - is_list(Config) -> - Options=check_test_get_opts(Testspec, Config), - run_test({atom_to_list(Testspec),Mod}, +%% Run one module for an app with Opts +run(App, Mod, Opts) when is_atom(App), + is_atom(Mod), + is_list(Opts) -> + Options = check_test_get_opts(App, Opts), + run_test({atom_to_list(App),Mod}, [{suite,Mod}], Options); -%% Run multiple modules with Config -run(Testspec, Mods, Config) when is_atom(Testspec), - is_list(Mods), - is_list(Config) -> - run_some([{Testspec,M} || M <- Mods], Config); + +%% Run multiple modules with Opts +run(App, Mods, Opts) when is_atom(App), + is_list(Mods), + is_list(Opts) -> + run_some([{App,M} || M <- Mods], Opts); + %% Runs one test case in a module. -run(Testspec, Mod, Case) when is_atom(Testspec), - is_atom(Mod), - is_atom(Case) -> - Options=check_test_get_opts(Testspec, []), +run(App, Mod, Case) when is_atom(App), + is_atom(Mod), + is_atom(Case) -> + Options = check_test_get_opts(App, []), Args = [{suite,Mod},{testcase,Case}], - run_test(atom_to_list(Testspec), Args, Options); + run_test(atom_to_list(App), Args, Options); + %% Runs one or more groups in a module. -run(Testspec, Mod, Grs={group,_Groups}) when is_atom(Testspec), - is_atom(Mod) -> - Options=check_test_get_opts(Testspec, []), +run(App, Mod, Grs={group,_Groups}) when is_atom(App), + is_atom(Mod) -> + Options = check_test_get_opts(App, []), Args = [{suite,Mod},Grs], - run_test(atom_to_list(Testspec), Args, Options); + run_test(atom_to_list(App), Args, Options); + %% Runs one or more test cases in a module. -run(Testspec, Mod, TCs={testcase,_Cases}) when is_atom(Testspec), - is_atom(Mod) -> - Options=check_test_get_opts(Testspec, []), +run(App, Mod, TCs={testcase,_Cases}) when is_atom(App), + is_atom(Mod) -> + Options = check_test_get_opts(App, []), Args = [{suite,Mod},TCs], - run_test(atom_to_list(Testspec), Args, Options). + run_test(atom_to_list(App), Args, Options). %% run/4 %% Run one test case in a module with Options. -run(Testspec, Mod, Case, Config) when is_atom(Testspec), - is_atom(Mod), - is_atom(Case), - is_list(Config) -> - Options=check_test_get_opts(Testspec, Config), +run(App, Mod, Case, Opts) when is_atom(App), + is_atom(Mod), + is_atom(Case), + is_list(Opts) -> + Options = check_test_get_opts(App, Opts), Args = [{suite,Mod},{testcase,Case}], - run_test(atom_to_list(Testspec), Args, Options); + run_test(atom_to_list(App), Args, Options); + %% Run one or more test cases in a module with Options. -run(Testspec, Mod, {testcase,Cases}, Config) when is_atom(Testspec), - is_atom(Mod) -> - run(Testspec, Mod, Cases, Config); -run(Testspec, Mod, Cases, Config) when is_atom(Testspec), - is_atom(Mod), - is_list(Cases), - is_list(Config) -> - Options=check_test_get_opts(Testspec, Config), +run(App, Mod, {testcase,Cases}, Opts) when is_atom(App), + is_atom(Mod) -> + run(App, Mod, Cases, Opts); +run(App, Mod, Cases, Opts) when is_atom(App), + is_atom(Mod), + is_list(Cases), + is_list(Opts) -> + Options = check_test_get_opts(App, Opts), Args = [{suite,Mod},Cases], - run_test(atom_to_list(Testspec), Args, Options); + run_test(atom_to_list(App), Args, Options); + +%% Run one or more test cases in a group. +run(App, Mod, Gr={group,_Group}, {testcase,Cases}) when is_atom(App), + is_atom(Mod) -> + run(App, Mod, Gr, Cases, [batch]); + + %% Run one or more groups in a module with Options. -run(Testspec, Mod, Grs={group,_Groups}, Config) when is_atom(Testspec), - is_atom(Mod) -> - Options=check_test_get_opts(Testspec, Config), +run(App, Mod, Grs={group,_Groups}, Opts) when is_atom(App), + is_atom(Mod), + is_list(Opts) -> + Options = check_test_get_opts(App, Opts), Args = [{suite,Mod},Grs], - run_test(atom_to_list(Testspec), Args, Options). + run_test(atom_to_list(App), Args, Options). %% run/5 %% Run one or more test cases in a group with Options. -run(Testspec, Mod, Group, Cases, Config) when is_atom(Testspec), - is_atom(Mod), - is_list(Config) -> +run(App, Mod, Group, Cases, Opts) when is_atom(App), + is_atom(Mod), + is_list(Opts) -> Group1 = if is_tuple(Group) -> Group; true -> {group,Group} end, Cases1 = if is_tuple(Cases) -> Cases; true -> {testcase,Cases} end, - Options=check_test_get_opts(Testspec, Config), + Options = check_test_get_opts(App, Opts), Args = [{suite,Mod},Group1,Cases1], - run_test(atom_to_list(Testspec), Args, Options). + run_test(atom_to_list(App), Args, Options). + +%% run_category/1 +run_category(TestCategory) when is_atom(TestCategory) -> + run_category(TestCategory, [batch]). + +%% run_category/2 +run_category(TestCategory, Opts) when is_atom(TestCategory), + is_list(Opts) -> + case ts:tests(TestCategory) of + [] -> + {error, no_tests_available}; + Apps -> + Opts1 = [{test_category,TestCategory} | Opts], + run_some(Apps, Opts1) + end; + +run_category(Apps, TestCategory) when is_atom(TestCategory) -> + run_category(Apps, TestCategory, [batch]). + +%% run_category/3 +run_category(App, TestCategory, Opts) -> + Apps = if is_atom(App) -> [App]; + is_list(App) -> App + end, + Opts1 = [{test_category,TestCategory} | Opts], + run_some(Apps, Opts1). + +%%----------------------------------------------------------------- +%% Functions kept for backwards compatibility + +bench() -> + run_category(bench, []). +bench(Opts) when is_list(Opts) -> + run_category(bench, Opts); +bench(App) -> + run_category(App, bench, []). +bench(App, Opts) when is_atom(App) -> + run_category(App, bench, Opts); +bench(Apps, Opts) when is_list(Apps) -> + run_category(Apps, bench, Opts). + +benchmarks() -> + tests(bench). + +smoke_test() -> + run_category(smoke, []). +smoke_test(Opts) when is_list(Opts) -> + run_category(smoke, Opts); +smoke_test(App) -> + run_category(App, smoke, []). +smoke_test(App, Opts) when is_atom(App) -> + run_category(App, smoke, Opts); +smoke_test(Apps, Opts) when is_list(Apps) -> + run_category(Apps, smoke, Opts). + +smoke_tests() -> + tests(smoke). + +%%----------------------------------------------------------------- is_list_of_suites(List) -> lists:all(fun(Suite) -> @@ -416,29 +583,29 @@ is_list_of_suites(List) -> %% Create a spec to skip all SUITES, this is used when the application %% to be tested is not part of the OTP release to be tested. -create_skip_spec(Testspec, SuitesToSkip) -> +create_skip_spec(App, SuitesToSkip) -> {ok,Cwd} = file:get_cwd(), - TestspecString = atom_to_list(Testspec), - Specname = TestspecString++"_skip.spec", + AppString = atom_to_list(App), + Specname = AppString++"_skip.spec", {ok,D} = file:open(filename:join([filename:dirname(Cwd), - TestspecString++"_test",Specname]), + AppString++"_test",Specname]), [write]), - TestDir = "\"../"++TestspecString++"_test\"", + TestDir = "\"../"++AppString++"_test\"", io:format(D,"{suites, "++TestDir++", all}.~n",[]), io:format(D,"{skip_suites, "++TestDir++", ~w, \"Skipped as application" " is not in path!\"}.",[SuitesToSkip]), Specname. -%% Check testspec to be valid and get possible Options -%% from the config. -check_test_get_opts(Testspec, Config) -> - validate_test(Testspec), - Mode = configmember(batch, {batch, interactive}, Config), - Vars = configvars(Config), - Trace = get_config(trace,Config), - ConfigPath = get_config(config,Config), - KeepTopcase = configmember(keep_topcase, {keep_topcase,[]}, Config), - Cover = configcover(Testspec,Config), +%% Check testspec for App to be valid and get possible options +%% from the list. +check_test_get_opts(App, Opts) -> + validate_test(App), + Mode = configmember(batch, {batch, interactive}, Opts), + Vars = configvars(Opts), + Trace = get_config(trace,Opts), + ConfigPath = get_config(config,Opts), + KeepTopcase = configmember(keep_topcase, {keep_topcase,[]}, Opts), + Cover = configcover(App,Opts), lists:flatten([Vars,Mode,Trace,KeepTopcase,Cover,ConfigPath]). to_erlang_term(Atom) -> @@ -447,7 +614,7 @@ to_erlang_term(Atom) -> {ok, Term} = erl_parse:parse_term(Tokens), Term. -%% Validate that a Testspec really is a testspec, +%% Validate that Testspec really is a testspec, %% and exit if not. validate_test(Testspec) -> case lists:member(Testspec, tests()) of @@ -460,10 +627,10 @@ validate_test(Testspec) -> exit(self(), {error, test_not_available}) end. -configvars(Config) -> - case lists:keysearch(vars, 1, Config) of +configvars(Opts) -> + case lists:keysearch(vars, 1, Opts) of {value, {vars, List}} -> - List0 = special_vars(Config), + List0 = special_vars(Opts), Key = fun(T) -> element(1,T) end, DelDupList = lists:filter(fun(V) -> @@ -474,17 +641,17 @@ configvars(Config) -> end, List), {vars, [List0|DelDupList]}; _ -> - {vars, special_vars(Config)} + {vars, special_vars(Opts)} end. -%% Allow some shortcuts in the Options... -special_vars(Config) -> +%% Allow some shortcuts in the options... +special_vars(Opts) -> SpecVars = - case lists:member(verbose, Config) of + case lists:member(verbose, Opts) of true -> [{verbose, 1}]; false -> - case lists:keysearch(verbose, 1, Config) of + case lists:keysearch(verbose, 1, Opts) of {value, {verbose, Lvl}} -> [{verbose, Lvl}]; _ -> @@ -492,13 +659,13 @@ special_vars(Config) -> end end, SpecVars1 = - case lists:keysearch(diskless, 1, Config) of + case lists:keysearch(diskless, 1, Opts) of {value,{diskless, true}} -> [{diskless, true} | SpecVars]; _ -> SpecVars end, - case lists:keysearch(testcase_callback, 1, Config) of + case lists:keysearch(testcase_callback, 1, Opts) of {value,{testcase_callback, CBM, CBF}} -> [{ts_testcase_callback, {CBM,CBF}} | SpecVars1]; {value,{testcase_callback, CB}} -> @@ -566,50 +733,31 @@ check_for_cross_cover_analysis_flag([_|Config],Level,CrossFlag) -> check_for_cross_cover_analysis_flag([],_,_) -> false. -%% Returns a list of available test suites. +%% Returns all available apps. tests() -> {ok, Cwd} = file:get_cwd(), ts_lib:specs(Cwd). -tests(Spec) -> +%% Returns all apps that provide tests in the given test category +tests(main) -> {ok, Cwd} = file:get_cwd(), - ts_lib:suites(Cwd, atom_to_list(Spec)). - -%% Benchmark related functions - -bench() -> - bench([]). - -bench(Opts) when is_list(Opts) -> - bench(benchmarks(),Opts); -bench(Spec) -> - bench([Spec],[]). - -bench(Spec, Opts) when is_atom(Spec) -> - bench([Spec],Opts); -bench(Specs, Opts) -> - check_and_run(fun(Vars) -> ts_benchmark:run(Specs, Opts, Vars) end). - -benchmarks() -> - ts_benchmark:benchmarks(). - -smoke_test() -> - smoke_test([]). - -smoke_test(Opts) when is_list(Opts) -> - smoke_test(smoke_tests(),Opts); -smoke_test(Spec) -> - smoke_test([Spec],[]). - -smoke_test(Spec, Opts) when is_atom(Spec) -> - smoke_test([Spec],Opts); -smoke_test(Specs, Opts) -> - run(Specs, [{smoke,true}|Opts]). + ts_lib:specs(Cwd); +tests(bench) -> + ts_benchmark:benchmarks(); +tests(TestCategory) -> + {ok, Cwd} = file:get_cwd(), + ts_lib:specialized_specs(Cwd, atom_to_list(TestCategory)). + +%% Returns a list of available test suites for App. +suites(App) -> + {ok, Cwd} = file:get_cwd(), + ts_lib:suites(Cwd, atom_to_list(App)). -smoke_tests() -> +%% Returns all available test categories for App +categories(App) -> {ok, Cwd} = file:get_cwd(), - ts_lib:specialized_specs(Cwd,"smoke"). + ts_lib:test_categories(Cwd, atom_to_list(App)). %% %% estone/0, estone/1 diff --git a/lib/test_server/src/ts_lib.erl b/lib/test_server/src/ts_lib.erl index 5368960446..d27bc55b3a 100644 --- a/lib/test_server/src/ts_lib.erl +++ b/lib/test_server/src/ts_lib.erl @@ -27,7 +27,7 @@ erlang_type/1, initial_capital/1, specs/1, suites/2, - specialized_specs/2, + test_categories/2, specialized_specs/2, subst_file/3, subst/2, print_data/1, make_non_erlang/2, maybe_atom_to_list/1, progress/4, @@ -96,26 +96,47 @@ specialized_specs(Dir,PostFix) -> Specs = filelib:wildcard(filename:join([filename:dirname(Dir), "*_test", "*_"++PostFix++".spec"])), sort_tests([begin - Base = filename:basename(Name), - list_to_atom(string:substr(Base,1,string:rstr(Base,"_")-1)) + DirPart = filename:dirname(Name), + AppTest = hd(lists:reverse(filename:split(DirPart))), + list_to_atom(string:substr(AppTest, 1, length(AppTest)-5)) end || Name <- Specs]). specs(Dir) -> Specs = filelib:wildcard(filename:join([filename:dirname(Dir), "*_test", "*.{dyn,}spec"])), - % Filter away all spec which end with {_bench,_smoke}.spec - NoBench = fun(SpecName) -> - case lists:reverse(SpecName) of - "ceps.hcneb_"++_ -> false; - "ceps.ekoms_"++_ -> false; - _ -> true - end - end, - - sort_tests([filename_to_atom(Name) || Name <- Specs, NoBench(Name)]). - -suites(Dir, Spec) -> - Glob=filename:join([filename:dirname(Dir), Spec++"_test", + %% Make sure only to include the main spec for each application + MainSpecs = + lists:flatmap(fun(FullName) -> + [Spec,TestDir|_] = + lists:reverse(filename:split(FullName)), + [_TestSuffix|TDParts] = + lists:reverse(string:tokens(TestDir,[$_,$.])), + [_SpecSuffix|SParts] = + lists:reverse(string:tokens(Spec,[$_,$.])), + if TDParts == SParts -> + [filename_to_atom(FullName)]; + true -> + [] + end + end, Specs), + sort_tests(MainSpecs). + +test_categories(Dir, App) -> + Specs = filelib:wildcard(filename:join([filename:dirname(Dir), + App++"_test", "*.spec"])), + lists:flatmap(fun(FullName) -> + [Spec,_TestDir|_] = + lists:reverse(filename:split(FullName)), + case filename:rootname(Spec -- App) of + "" -> + []; + [_Sep | Cat] -> + [list_to_atom(Cat)] + end + end, Specs). + +suites(Dir, App) -> + Glob=filename:join([filename:dirname(Dir), App++"_test", "*_SUITE.erl"]), Suites=filelib:wildcard(Glob), [filename_to_atom(Name) || Name <- Suites]. diff --git a/lib/webtool/doc/src/Makefile b/lib/webtool/doc/src/Makefile index 32269e9424..08292fcca8 100644 --- a/lib/webtool/doc/src/Makefile +++ b/lib/webtool/doc/src/Makefile @@ -25,6 +25,10 @@ include ../../vsn.mk VSN=$(WEBTOOL_VSN) APPLICATION=webtool +DOC_EXTRA_FRONT_PAGE_INFO=Important note: \ +The Webtool application is obsolete and will be removed \ +in the next major OTP release + # ---------------------------------------------------- # Release directory specification # ---------------------------------------------------- diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl index 720526b3b9..8e32aeddc8 100644 --- a/lib/wx/api_gen/wx_gen_cpp.erl +++ b/lib/wx/api_gen/wx_gen_cpp.erl @@ -197,8 +197,8 @@ gen_funcs(Defs) -> w(" if(recurse_level > 1 && refd->type != 4) {~n"), w(" delayed_delete->Append(Ecmd.Save());~n"), w(" } else {~n"), - w(" ((WxeApp *) wxTheApp)->clearPtr(This);~n"), - w(" delete_object(This, refd); }~n"), + w(" delete_object(This, refd);~n"), + w(" ((WxeApp *) wxTheApp)->clearPtr(This);}~n"), w(" } } break;~n"), w(" case WXE_REGISTER_OBJECT: {~n" " registerPid(bp, Ecmd.caller, memenv);~n" diff --git a/lib/wx/api_gen/wxapi.conf b/lib/wx/api_gen/wxapi.conf index 09f21af0f3..bbf9add59e 100644 --- a/lib/wx/api_gen/wxapi.conf +++ b/lib/wx/api_gen/wxapi.conf @@ -1358,7 +1358,8 @@ wxEVT_SCROLL_THUMBRELEASE,wxEVT_SCROLL_CHANGED]}], ['GetOrientation','GetPosition']}. {class, wxScrollWinEvent,wxEvent, - [{event, + [{acc, [{m_commandInt, "GetPosition()"}, {m_extraLong, "GetOrientation()"}]}, + {event, [wxEVT_SCROLLWIN_TOP,wxEVT_SCROLLWIN_BOTTOM,wxEVT_SCROLLWIN_LINEUP, wxEVT_SCROLLWIN_LINEDOWN,wxEVT_SCROLLWIN_PAGEUP, wxEVT_SCROLLWIN_PAGEDOWN,wxEVT_SCROLLWIN_THUMBTRACK, @@ -1383,7 +1384,9 @@ 'ShiftDown' ]}. -{class, wxSetCursorEvent, wxEvent, [{event,[wxEVT_SET_CURSOR]}], +{class, wxSetCursorEvent, wxEvent, + [{acc, [{m_x, "GetX()"}, {m_y, "GetY()"}, {m_cursor, "GetCursor()"}]}, + {event,[wxEVT_SET_CURSOR]}], ['GetCursor','GetX','GetY','HasCursor','SetCursor']}. {class, wxKeyEvent, wxEvent, @@ -1398,7 +1401,7 @@ {class, wxSizeEvent, wxEvent, [{event,[wxEVT_SIZE]}], ['GetSize']}. -{class, wxMoveEvent, wxEvent, [{event,[wxEVT_MOVE]}], +{class, wxMoveEvent, wxEvent, [{acc, [{m_pos, "GetPosition()"}, {m_rect, "GetRect()"}]}, {event,[wxEVT_MOVE]}], ['GetPosition']}. {class, wxPaintEvent, wxEvent, [{event,[wxEVT_PAINT]}],[]}. %%{class, wxNcPaintEvent, wxEvent, [{event,[wxEVT_NC_PAINT]}],[]}. @@ -1407,28 +1410,28 @@ {event, [wxEVT_ERASE_BACKGROUND]}], ['GetDC']}. {class, wxFocusEvent, wxEvent, - [{event,[wxEVT_SET_FOCUS,wxEVT_KILL_FOCUS]}], + [{acc, [{m_win, "GetWindow()"}]}, + {event,[wxEVT_SET_FOCUS,wxEVT_KILL_FOCUS]}], ['GetWindow']}. {class,wxChildFocusEvent,wxCommandEvent, [{event,[wxEVT_CHILD_FOCUS]}], ['GetWindow']}. -%% {class, wxActivateEvent, wxEvent, [{event, -%% [wxEVT_ACTIVATE,wxEVT_ACTIVATE_APP,wxEVT_HIBERNATE]}],[]}. - -%%{class, wxInitDialogEvent, wxEvent, [{event, []}],[]}. - -{class, wxMenuEvent, wxEvent, - [{event, [wxEVT_MENU_OPEN,wxEVT_MENU_CLOSE,wxEVT_MENU_HIGHLIGHT]}], +{class, wxMenuEvent, wxEvent, + [{acc, [{m_menuId, "GetMenuId()"}, {m_menu, "GetMenu()"}]}, + {event, [wxEVT_MENU_OPEN,wxEVT_MENU_CLOSE,wxEVT_MENU_HIGHLIGHT]}], ['GetMenu','GetMenuId','IsPopup']}. {class, wxCloseEvent, wxEvent, [{event, [wxEVT_CLOSE_WINDOW,wxEVT_END_SESSION,wxEVT_QUERY_END_SESSION]}], ['CanVeto','GetLoggingOff','SetCanVeto','SetLoggingOff','Veto']}. -{class, wxShowEvent, wxEvent, [{event,[wxEVT_SHOW]}],['SetShow','GetShow']}. -{class, wxIconizeEvent, wxEvent, [{event,[wxEVT_ICONIZE]}],['Iconized']}. +{class, wxShowEvent, wxEvent, [{acc, [{m_show, "GetShow()"}]},{event,[wxEVT_SHOW]}],['SetShow','GetShow']}. +{class, wxIconizeEvent, wxEvent, [{acc, [{m_iconized, "Iconized()"}]},{event,[wxEVT_ICONIZE]}],['Iconized']}. {class, wxMaximizeEvent, wxEvent, [{event,[wxEVT_MAXIMIZE]}],[]}. -{class, wxJoystickEvent, wxEvent, - [{event,[wxEVT_JOY_BUTTON_DOWN,wxEVT_JOY_BUTTON_UP, +{class, wxJoystickEvent, wxEvent, + [{acc, [{m_pos, "GetPosition()"},{m_zPosition, "GetZPosition()"}, + {m_buttonChange, "GetButtonChange()"}, {m_buttonState, "GetButtonState()"}, + {m_joyStick, "GetJoystick()"}]}, + {event,[wxEVT_JOY_BUTTON_DOWN,wxEVT_JOY_BUTTON_UP, wxEVT_JOY_MOVE,wxEVT_JOY_ZMOVE]}], ['ButtonDown','ButtonIsDown','ButtonUp','GetButtonChange','GetButtonState', 'GetJoystick','GetPosition','GetZPosition','IsButton','IsMove','IsZMove']}. @@ -1466,7 +1469,8 @@ 'SetOrigin', 'SetPosition']}. -{class, wxContextMenuEvent, wxCommandEvent, [{event,[wxEVT_CONTEXT_MENU]}], +{class, wxContextMenuEvent, wxCommandEvent, + [{acc, [{m_pos, "GetPosition()"}]}, {event,[wxEVT_CONTEXT_MENU]}], ['GetPosition','SetPosition']}. {enum, wxIdleMode, "wxIDLE_"}. {class, wxIdleEvent, wxEvent, [{event,[wxEVT_IDLE]}], @@ -1525,7 +1529,8 @@ ]}. {class, wxCalendarEvent, wxDateEvent, - [{event,[wxEVT_CALENDAR_SEL_CHANGED, wxEVT_CALENDAR_DAY_CHANGED, + [{acc, [{m_date, "GetDate()"}, {m_wday, "GetWeekDay()"}]}, + {event,[wxEVT_CALENDAR_SEL_CHANGED, wxEVT_CALENDAR_DAY_CHANGED, wxEVT_CALENDAR_MONTH_CHANGED, wxEVT_CALENDAR_YEAR_CHANGED, wxEVT_CALENDAR_DOUBLECLICKED, wxEVT_CALENDAR_WEEKDAY_CLICKED]}], [ @@ -1730,8 +1735,9 @@ ['GetKeyCode','GetItem','GetKeyEvent','GetLabel','GetOldItem','GetPoint', 'IsEditCancelled','SetToolTip']}. -{class, wxNotebookEvent, wxNotifyEvent, - [{event, [wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, +{class, wxNotebookEvent, wxNotifyEvent, + [{acc, [{m_nSel, "GetSelection()"}, {m_nOldSel, "GetOldSelection()"}]}, + {event, [wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGED, wxEVT_COMMAND_NOTEBOOK_PAGE_CHANGING]}], ['GetOldSelection','GetSelection','SetOldSelection','SetSelection']}. diff --git a/lib/wx/c_src/gen/wxe_events.cpp b/lib/wx/c_src/gen/wxe_events.cpp index ae85931d8d..e042b4d890 100644 --- a/lib/wx/c_src/gen/wxe_events.cpp +++ b/lib/wx/c_src/gen/wxe_events.cpp @@ -375,10 +375,13 @@ case 165: {// wxScrollEvent or wxSpinEvent break; } case 166: {// wxScrollWinEvent + wxScrollWinEvent * ev = (wxScrollWinEvent *) event; evClass = (char*)"wxScrollWinEvent"; rt.addAtom((char*)"wxScrollWin"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addInt(ev->GetPosition()); + rt.addInt(ev->GetOrientation()); + rt.addTupleCount(4); break; } case 167: {// wxMouseEvent @@ -406,10 +409,16 @@ case 167: {// wxMouseEvent break; } case 168: {// wxSetCursorEvent + wxSetCursorEvent * ev = (wxSetCursorEvent *) event; + wxCursor * GetCursor = new wxCursor(ev->GetCursor()); + app->newPtr((void *) GetCursor,3, memenv); evClass = (char*)"wxSetCursorEvent"; rt.addAtom((char*)"wxSetCursor"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addInt(ev->GetX()); + rt.addInt(ev->GetY()); + rt.addRef(getRef((void *)GetCursor,memenv), "wxCursor"); + rt.addTupleCount(5); break; } case 169: {// wxKeyEvent @@ -450,10 +459,13 @@ case 170: {// wxSizeEvent break; } case 171: {// wxMoveEvent + wxMoveEvent * ev = (wxMoveEvent *) event; evClass = (char*)"wxMoveEvent"; rt.addAtom((char*)"wxMove"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.add(ev->GetPosition()); + rt.add(ev->GetRect()); + rt.addTupleCount(4); break; } case 172: {// wxPaintEvent @@ -474,10 +486,13 @@ case 173: {// wxEraseEvent break; } case 174: {// wxFocusEvent + wxFocusEvent * ev = (wxFocusEvent *) event; + wxWindow * GetWindow = ev->GetWindow(); evClass = (char*)"wxFocusEvent"; rt.addAtom((char*)"wxFocus"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addRef(getRef((void *)GetWindow,memenv), "wxWindow"); + rt.addTupleCount(3); break; } case 175: {// wxChildFocusEvent @@ -488,10 +503,14 @@ case 175: {// wxChildFocusEvent break; } case 176: {// wxMenuEvent + wxMenuEvent * ev = (wxMenuEvent *) event; + wxMenu * GetMenu = ev->GetMenu(); evClass = (char*)"wxMenuEvent"; rt.addAtom((char*)"wxMenu"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addInt(ev->GetMenuId()); + rt.addRef(getRef((void *)GetMenu,memenv), "wxMenu"); + rt.addTupleCount(4); break; } case 177: {// wxCloseEvent @@ -502,17 +521,21 @@ case 177: {// wxCloseEvent break; } case 178: {// wxShowEvent + wxShowEvent * ev = (wxShowEvent *) event; evClass = (char*)"wxShowEvent"; rt.addAtom((char*)"wxShow"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addBool(ev->GetShow()); + rt.addTupleCount(3); break; } case 179: {// wxIconizeEvent + wxIconizeEvent * ev = (wxIconizeEvent *) event; evClass = (char*)"wxIconizeEvent"; rt.addAtom((char*)"wxIconize"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addBool(ev->Iconized()); + rt.addTupleCount(3); break; } case 180: {// wxMaximizeEvent @@ -523,10 +546,16 @@ case 180: {// wxMaximizeEvent break; } case 181: {// wxJoystickEvent + wxJoystickEvent * ev = (wxJoystickEvent *) event; evClass = (char*)"wxJoystickEvent"; rt.addAtom((char*)"wxJoystick"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.add(ev->GetPosition()); + rt.addInt(ev->GetZPosition()); + rt.addInt(ev->GetButtonChange()); + rt.addInt(ev->GetButtonState()); + rt.addInt(ev->GetJoystick()); + rt.addTupleCount(7); break; } case 182: {// wxUpdateUIEvent @@ -603,10 +632,12 @@ case 191: {// wxHelpEvent break; } case 192: {// wxContextMenuEvent + wxContextMenuEvent * ev = (wxContextMenuEvent *) event; evClass = (char*)"wxContextMenuEvent"; rt.addAtom((char*)"wxContextMenu"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.add(ev->GetPosition()); + rt.addTupleCount(3); break; } case 193: {// wxIdleEvent @@ -667,10 +698,13 @@ case 198: {// wxDateEvent break; } case 199: {// wxCalendarEvent + wxCalendarEvent * ev = (wxCalendarEvent *) event; evClass = (char*)"wxCalendarEvent"; rt.addAtom((char*)"wxCalendar"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addInt(ev->GetWeekDay()); + rt.add(ev->GetDate()); + rt.addTupleCount(4); break; } case 200: {// wxFileDirPickerEvent @@ -742,10 +776,13 @@ case 209: {// wxTreeEvent break; } case 210: {// wxNotebookEvent + wxNotebookEvent * ev = (wxNotebookEvent *) event; evClass = (char*)"wxNotebookEvent"; rt.addAtom((char*)"wxNotebook"); rt.addAtom(Etype->eName); - rt.addTupleCount(2); + rt.addInt(ev->GetSelection()); + rt.addInt(ev->GetOldSelection()); + rt.addTupleCount(4); break; } case 216: {// wxClipboardTextEvent diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp index 91ce5d810c..3b11c0642e 100644 --- a/lib/wx/c_src/gen/wxe_funcs.cpp +++ b/lib/wx/c_src/gen/wxe_funcs.cpp @@ -1,7 +1,7 @@ /* * %CopyrightBegin% * - * Copyright Ericsson AB 2008-2014. All Rights Reserved. + * Copyright Ericsson AB 2008-2015. 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 @@ -51,8 +51,8 @@ void WxeApp::wxe_dispatch(wxeCommand& Ecmd) if(recurse_level > 1 && refd->type != 4) { delayed_delete->Append(Ecmd.Save()); } else { - ((WxeApp *) wxTheApp)->clearPtr(This); - delete_object(This, refd); } + delete_object(This, refd); + ((WxeApp *) wxTheApp)->clearPtr(This);} } } break; case WXE_REGISTER_OBJECT: { registerPid(bp, Ecmd.caller, memenv); diff --git a/lib/wx/c_src/wxe_gl.cpp b/lib/wx/c_src/wxe_gl.cpp index a9feb23831..26b45d219e 100644 --- a/lib/wx/c_src/wxe_gl.cpp +++ b/lib/wx/c_src/wxe_gl.cpp @@ -135,8 +135,12 @@ void deleteActiveGL(wxGLCanvas *canvas) void gl_dispatch(int op, char *bp,ErlDrvTermData caller,WXEBinRef *bins[]){ if(caller != gl_active) { wxGLCanvas * current = glc[caller]; - if(current) { gl_active = caller; current->SetCurrent();} - else { + if(current) { + if(current != glc[gl_active]) { + gl_active = caller; + current->SetCurrent(); + } + } else { ErlDrvTermData rt[] = // Error msg {ERL_DRV_ATOM, driver_mk_atom((char *) "_egl_error_"), ERL_DRV_INT, (ErlDrvTermData) op, diff --git a/lib/wx/c_src/wxe_helpers.cpp b/lib/wx/c_src/wxe_helpers.cpp index 15d75080d9..120919e7aa 100644 --- a/lib/wx/c_src/wxe_helpers.cpp +++ b/lib/wx/c_src/wxe_helpers.cpp @@ -24,23 +24,96 @@ * Erlang Commands * ****************************************************************************/ -wxeCommand::wxeCommand(int fc,char * cbuf,int buflen, wxe_data *sd) - : wxObject() +wxeCommand::wxeCommand() { +} + +wxeCommand::~wxeCommand() +{ + Delete(); +} + +void wxeCommand::Delete() +{ + int n = 0; + + if(buffer) { + while(bin[n]) { + if(bin[n]->bin) + driver_free_binary(bin[n]->bin); + driver_free(bin[n++]); + } + if(len > 64) + driver_free(buffer); + buffer = NULL; + op = -1; + } +} + +/* **************************************************************************** + * wxeFifo + * ****************************************************************************/ +wxeFifo::wxeFifo(unsigned int sz) +{ + m_q = (wxeCommand *) driver_alloc(sizeof(wxeCommand) * sz); + m_orig_sz = sz; + m_max = sz; + m_n = 0; + m_first = 0; + m_old = NULL; + for(unsigned int i = 0; i < sz; i++) { + m_q[i].buffer = NULL; + m_q[i].op = -1; + } +} + +wxeFifo::~wxeFifo() { + // dealloc all memory buffers + driver_free(m_q); +} + +wxeCommand * wxeFifo::Get() +{ + unsigned int pos; + if(m_n > 0) { + pos = m_first++; + m_n--; + m_first %= m_max; + return &m_q[pos]; + } + return NULL; +} + +void wxeFifo::Add(int fc, char * cbuf,int buflen, wxe_data *sd) +{ + unsigned int pos; + wxeCommand *curr; + WXEBinRef *temp, *start, *prev; int n = 0; - ref_count = 1; - caller = driver_caller(sd->port_handle); - port = sd->port; - op = fc; - len = buflen; - bin[0] = NULL; - bin[1] = NULL; - bin[2] = NULL; + + if(m_n == (m_max-1)) { // resize + Realloc(); + } + + pos = (m_first + m_n) % m_max; + m_n++; + + curr = &m_q[pos]; + curr->caller = driver_caller(sd->port_handle); + curr->port = sd->port; + curr->op = fc; + curr->len = buflen; + curr->bin[0] = NULL; + curr->bin[1] = NULL; + curr->bin[2] = NULL; if(cbuf) { - buffer = (char *) driver_alloc(len); - memcpy((void *) buffer, (void *) cbuf, len);; + if(buflen > 64) + curr->buffer = (char *) driver_alloc(buflen); + else + curr->buffer = curr->c_buf; + memcpy((void *) curr->buffer, (void *) cbuf, buflen); temp = sd->bin; @@ -48,8 +121,8 @@ wxeCommand::wxeCommand(int fc,char * cbuf,int buflen, wxe_data *sd) start = temp; while(temp) { - if(caller == temp->from) { - bin[n++] = temp; + if(curr->caller == temp->from) { + curr->bin[n++] = temp; if(prev) { prev->next = temp->next; } else { @@ -63,20 +136,68 @@ wxeCommand::wxeCommand(int fc,char * cbuf,int buflen, wxe_data *sd) } sd->bin = start; } else { // No-op only PING currently - buffer = NULL; + curr->buffer = NULL; } } -wxeCommand::~wxeCommand() { - int n = 0; - if(buffer) { - while(bin[n]) { - if(bin[n]->bin) - driver_free_binary(bin[n]->bin); - driver_free(bin[n++]); +void wxeFifo::Append(wxeCommand *orig) +{ + unsigned int pos; + wxeCommand *curr; + if(m_n == (m_max-1)) { // resize + Realloc(); + } + + pos = (m_first + m_n) % m_max; + m_n++; + curr = &m_q[pos]; + curr->caller = orig->caller; + curr->port = orig->port; + curr->op = orig->op; + curr->len = orig->len; + curr->bin[0] = orig->bin[0]; + curr->bin[1] = orig->bin[1]; + curr->bin[2] = orig->bin[2]; + + if(orig->len > 64) + curr->buffer = orig->buffer; + else { + curr->buffer = curr->c_buf; + memcpy((void *) curr->buffer, (void *) orig->buffer, orig->len); + } + orig->op = -1; + orig->buffer = NULL; + orig->bin[0] = NULL; +} + +void wxeFifo::Realloc() +{ + unsigned int i; + unsigned int growth = m_orig_sz / 2; + unsigned int new_sz = growth + m_max; + unsigned int max = m_max; + unsigned int first = m_first; + unsigned int n = m_n; + wxeCommand * old = m_q; + wxeCommand * queue = (wxeCommand *)driver_alloc(new_sz*sizeof(wxeCommand)); + + m_max=new_sz; + m_first = 0; + m_n=0; + m_q = queue; + + for(i=0; i < n; i++) { + unsigned int pos = i+first; + if(old[pos%max].op >= 0) { + Append(&old[pos%max]); } - driver_free(buffer); } + for(i = m_n; i < new_sz; i++) { // Reset the rest + m_q[i].buffer = NULL; + m_q[i].op = -1; + } + // Can not free old queue here it can be used in the wx thread + m_old = old; } /* **************************************************************************** diff --git a/lib/wx/c_src/wxe_helpers.h b/lib/wx/c_src/wxe_helpers.h index 659bc666c6..ec3a5debdb 100644 --- a/lib/wx/c_src/wxe_helpers.h +++ b/lib/wx/c_src/wxe_helpers.h @@ -39,14 +39,14 @@ class wxeMetaCommand : public wxEvent ErlDrvPDL pdl; }; -class wxeCommand : public wxObject +class wxeCommand { public: - wxeCommand(int fc,char * cbuf,int buflen, wxe_data *); + wxeCommand(); virtual ~wxeCommand(); // Use Delete() - wxeCommand * Save() {ref_count++; return this; }; - void Delete() {if(--ref_count < 1) delete this;}; + wxeCommand * Save() { return this; }; + void Delete(); ErlDrvTermData caller; ErlDrvTermData port; @@ -54,7 +54,27 @@ class wxeCommand : public wxObject char * buffer; int len; int op; - int ref_count; + char c_buf[64]; // 64b covers 90% of usage +}; + +class wxeFifo { + public: + wxeFifo(unsigned int size); + virtual ~wxeFifo(); + + void Add(int fc, char * cbuf,int buflen, wxe_data *); + void Append(wxeCommand *Other); + + wxeCommand * Get(); + + void Realloc(); + + unsigned int m_max; + unsigned int m_first; + unsigned int m_n; + unsigned int m_orig_sz; + wxeCommand *m_q; + wxeCommand *m_old; }; class intListElement { diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp index 0ee52e3af2..ef648e008c 100644 --- a/lib/wx/c_src/wxe_impl.cpp +++ b/lib/wx/c_src/wxe_impl.cpp @@ -55,8 +55,9 @@ extern ErlDrvCond * wxe_batch_locker_c; extern ErlDrvTermData init_caller; extern int wxe_status; -wxList * wxe_batch = NULL; -wxList * wxe_batch_cb_saved = NULL; +wxeFifo * wxe_queue = NULL; +wxeFifo * wxe_queue_cb_saved = NULL; + int wxe_batch_caller = 0; // inside batch if larger than 0 /* ************************************************************ @@ -68,30 +69,30 @@ void push_command(int op,char * buf,int len, wxe_data *sd) { /* fprintf(stderr, "Op %d %d [%ld] %d\r\n", op, (int) driver_caller(sd->port_handle), wxe_batch->size(), wxe_batch_caller),fflush(stderr); */ - wxeCommand *Cmd = new wxeCommand(op, buf, len, sd); erl_drv_mutex_lock(wxe_batch_locker_m); - wxe_batch->Append(Cmd); + wxe_queue->Add(op, buf, len, sd); if(wxe_batch_caller > 0) { // wx-thread is waiting on batch end in cond_wait erl_drv_cond_signal(wxe_batch_locker_c); + erl_drv_mutex_unlock(wxe_batch_locker_m); } else { // wx-thread is waiting gui-events if(op == WXE_BATCH_BEGIN) { wxe_batch_caller = 1; } erl_drv_cond_signal(wxe_batch_locker_c); + erl_drv_mutex_unlock(wxe_batch_locker_m); wxWakeUpIdle(); } - erl_drv_mutex_unlock(wxe_batch_locker_m); + } void meta_command(int what, wxe_data *sd) { if(what == PING_PORT) { erl_drv_mutex_lock(wxe_batch_locker_m); if(wxe_batch_caller > 0) { - wxeCommand *Cmd = new wxeCommand(WXE_DEBUG_PING, NULL, 0, sd); - wxe_batch->Append(Cmd); + wxe_queue->Add(WXE_DEBUG_PING, NULL, 0, sd); erl_drv_cond_signal(wxe_batch_locker_c); } wxWakeUpIdle(); @@ -121,12 +122,12 @@ bool WxeApp::OnInit() { global_me = new wxeMemEnv(); - wxe_batch = new wxList; - wxe_batch_cb_saved = new wxList; + wxe_queue = new wxeFifo(1000); + wxe_queue_cb_saved = new wxeFifo(200); cb_buff = NULL; recurse_level = 0; - delayed_cleanup = new wxList; - delayed_delete = new wxList; + delayed_delete = new wxeFifo(10); + delayed_cleanup = new wxList; wxe_ps_init2(); // wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); // Hmm printpreview doesn't work in 2.9 with this @@ -169,6 +170,8 @@ void WxeApp::MacOpenFile(const wxString &filename) { void WxeApp::shutdown(wxeMetaCommand& Ecmd) { ExitMainLoop(); + delete wxe_queue; + delete wxe_queue_cb_saved; } void WxeApp::dummy_close(wxEvent& Ev) { @@ -201,11 +204,11 @@ void handle_event_callback(ErlDrvPort port, ErlDrvTermData process) if(driver_monitor_process(port, process, &monitor) == 0) { // Should we be able to handle commands when recursing? probably erl_drv_mutex_lock(wxe_batch_locker_m); - //fprintf(stderr, "\r\nCB EV Start %lu \r\n", process);fflush(stderr); + // fprintf(stderr, "\r\nCB EV Start %lu \r\n", process);fflush(stderr); app->recurse_level++; - app->dispatch_cb(wxe_batch, wxe_batch_cb_saved, process); + app->dispatch_cb(wxe_queue, wxe_queue_cb_saved, process); app->recurse_level--; - //fprintf(stderr, "CB EV done %lu \r\n", process);fflush(stderr); + // fprintf(stderr, "CB EV done %lu \r\n", process);fflush(stderr); wxe_batch_caller = 0; erl_drv_mutex_unlock(wxe_batch_locker_m); driver_demonitor_process(port, &monitor); @@ -216,22 +219,22 @@ void WxeApp::dispatch_cmds() { erl_drv_mutex_lock(wxe_batch_locker_m); recurse_level++; - int level = dispatch(wxe_batch_cb_saved, 0, WXE_STORED); - dispatch(wxe_batch, level, WXE_NORMAL); + int level = dispatch(wxe_queue_cb_saved, 0, WXE_STORED); + dispatch(wxe_queue, level, WXE_NORMAL); recurse_level--; wxe_batch_caller = 0; + if(wxe_queue->m_old) { + driver_free(wxe_queue->m_old); + wxe_queue->m_old = NULL; + } erl_drv_mutex_unlock(wxe_batch_locker_m); // Cleanup old memenv's and deleted objects if(recurse_level == 0) { - if(delayed_delete->size() > 0) - for( wxList::compatibility_iterator node = delayed_delete->GetFirst(); - node; - node = delayed_delete->GetFirst()) { - wxeCommand *event = (wxeCommand *)node->GetData(); - delayed_delete->Erase(node); - wxe_dispatch(*event); - event->Delete(); - } + wxeCommand *curr; + while((curr = delayed_delete->Get()) != NULL) { + wxe_dispatch(*curr); + curr->Delete(); + } if(delayed_cleanup->size() > 0) for( wxList::compatibility_iterator node = delayed_cleanup->GetFirst(); node; @@ -241,158 +244,145 @@ void WxeApp::dispatch_cmds() destroyMemEnv(*event); delete event; } + if(wxe_queue_cb_saved->m_old) { + driver_free(wxe_queue_cb_saved->m_old); + wxe_queue_cb_saved->m_old = NULL; + } + if(delayed_delete->m_old) { + driver_free(delayed_delete->m_old); + delayed_delete->m_old = NULL; + } } } // Should have erl_drv_mutex_lock(wxe_batch_locker_m); // when entering this function and it should be released // afterwards -int WxeApp::dispatch(wxList * batch, int blevel, int list_type) +int WxeApp::dispatch(wxeFifo * batch, int blevel, int list_type) { int ping = 0; // erl_drv_mutex_lock(wxe_batch_locker_m); must be locked already - while(true) - { - if (batch->size() > 0) { - for( wxList::compatibility_iterator node = batch->GetFirst(); - node; - node = batch->GetFirst()) - { - wxeCommand *event = (wxeCommand *)node->GetData(); - batch->Erase(node); - switch(event->op) { - case WXE_BATCH_END: - {--blevel; } - break; - case WXE_BATCH_BEGIN: - {blevel++; } - break; - case WXE_DEBUG_PING: - // When in debugger we don't want to hang waiting for a BATCH_END - // that never comes, because a breakpoint have hit. - ping++; - if(ping > 2) - blevel = 0; - break; - case WXE_CB_RETURN: - // erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after - // whatever cleaning is necessary - if(event->len > 0) { - cb_buff = (char *) driver_alloc(event->len); - memcpy(cb_buff, event->buffer, event->len); - } - return blevel; - default: - erl_drv_mutex_unlock(wxe_batch_locker_m); - if(event->op < OPENGL_START) { - // fprintf(stderr, " c %d (%d) \r\n", event->op, blevel); - wxe_dispatch(*event); - } else { - gl_dispatch(event->op,event->buffer,event->caller,event->bin); - } - erl_drv_mutex_lock(wxe_batch_locker_m); - break; - } - event->Delete(); - } - } else { - if((list_type == WXE_STORED) || (blevel <= 0 && list_type == WXE_NORMAL)) { - // erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after - // whatever cleaning is necessary - return blevel; + wxeCommand *event; + while(true) { + while((event = batch->Get()) != NULL) { + switch(event->op) { + case -1: + break; + case WXE_BATCH_END: + {--blevel; } + break; + case WXE_BATCH_BEGIN: + {blevel++; } + break; + case WXE_DEBUG_PING: + // When in debugger we don't want to hang waiting for a BATCH_END + // that never comes, because a breakpoint have hit. + ping++; + if(ping > 2) + blevel = 0; + break; + case WXE_CB_RETURN: + // erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after + // whatever cleaning is necessary + if(event->len > 0) { + cb_buff = (char *) driver_alloc(event->len); + memcpy(cb_buff, event->buffer, event->len); } - // sleep until something happens - //fprintf(stderr, "%s:%d sleep %d %d %d %d \r\n", __FILE__, __LINE__, batch->size(), callback_returned, blevel, is_callback);fflush(stderr); - wxe_batch_caller++; - while(batch->size() == 0) { - erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); + event->Delete(); + return blevel; + default: + erl_drv_mutex_unlock(wxe_batch_locker_m); + if(event->op < OPENGL_START) { + // fprintf(stderr, " c %d (%d) \r\n", event->op, blevel); + wxe_dispatch(*event); + } else { + gl_dispatch(event->op,event->buffer,event->caller,event->bin); } + erl_drv_mutex_lock(wxe_batch_locker_m); + break; } + event->Delete(); + } + if((list_type == WXE_STORED) || (blevel <= 0 && list_type == WXE_NORMAL)) { + // erl_drv_mutex_unlock(wxe_batch_locker_m); should be called after + // whatever cleaning is necessary + return blevel; } + // sleep until something happens + //fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__, batch->size(), blevel);fflush(stderr); + wxe_batch_caller++; + while(batch->m_n == 0) { + erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); + } + } } -void WxeApp::dispatch_cb(wxList * batch, wxList * temp, ErlDrvTermData process) { - int callback_returned = 0; +void WxeApp::dispatch_cb(wxeFifo * batch, wxeFifo * temp, ErlDrvTermData process) { + wxeCommand *event; while(true) { - if (batch->size() > 0) { - for( wxList::compatibility_iterator node = batch->GetFirst(); - node; - node = batch->GetFirst()) - { - wxeCommand *event = (wxeCommand *)node->GetData(); - wxeMemEnv *memenv = getMemEnv(event->port); - batch->Erase(node); - // fprintf(stderr, " Ev %d %lu\r\n", event->op, event->caller); - if(event->caller == process || // Callbacks from CB process only - event->op == WXE_CB_START || // Event callback start change process - event->op == WXE_CB_DIED || // Event callback process died - // Allow connect_cb during CB i.e. msg from wxe_server. - (memenv && event->caller == memenv->owner)) - { - switch(event->op) { - case WXE_BATCH_END: - case WXE_BATCH_BEGIN: - case WXE_DEBUG_PING: - break; - case WXE_CB_RETURN: - if(event->len > 0) { - cb_buff = (char *) driver_alloc(event->len); - memcpy(cb_buff, event->buffer, event->len); - } // continue - case WXE_CB_DIED: - callback_returned = 1; - return; - case WXE_CB_START: - // CB start from now accept message from CB process only - process = event->caller; - break; - default: - erl_drv_mutex_unlock(wxe_batch_locker_m); - size_t start=temp->GetCount(); - if(event->op < OPENGL_START) { - // fprintf(stderr, " cb %d \r\n", event->op); - wxe_dispatch(*event); - } else { - gl_dispatch(event->op,event->buffer,event->caller,event->bin); - } - erl_drv_mutex_lock(wxe_batch_locker_m); - if(temp->GetCount() > start) { - // We have recursed dispatch_cb and messages for this - // callback may be saved on temp list move them - // to orig list - for(wxList::compatibility_iterator node = temp->Item(start); - node; - node = node->GetNext()) { - wxeCommand *ev = (wxeCommand *)node->GetData(); - if(ev->caller == process) { - batch->Append(ev); - temp->Erase(node); - } - } - } - if(callback_returned) - return; - break; + while((event = batch->Get()) != NULL) { + wxeMemEnv *memenv = getMemEnv(event->port); + // fprintf(stderr, " Ev %d %lu\r\n", event->op, event->caller); + if(event->caller == process || // Callbacks from CB process only + event->op == WXE_CB_START || // Event callback start change process + event->op == WXE_CB_DIED || // Event callback process died + // Allow connect_cb during CB i.e. msg from wxe_server. + (memenv && event->caller == memenv->owner)) { + switch(event->op) { + case -1: + case WXE_BATCH_END: + case WXE_BATCH_BEGIN: + case WXE_DEBUG_PING: + break; + case WXE_CB_RETURN: + if(event->len > 0) { + cb_buff = (char *) driver_alloc(event->len); + memcpy(cb_buff, event->buffer, event->len); + } // continue + case WXE_CB_DIED: + event->Delete(); + return; + case WXE_CB_START: + // CB start from now accept message from CB process only + process = event->caller; + break; + default: + erl_drv_mutex_unlock(wxe_batch_locker_m); + size_t start=temp->m_n; + if(event->op < OPENGL_START) { + // fprintf(stderr, " cb %d \r\n", event->op); + wxe_dispatch(*event); + } else { + gl_dispatch(event->op,event->buffer,event->caller,event->bin); + } + erl_drv_mutex_lock(wxe_batch_locker_m); + if(temp->m_n > start) { + // We have recursed dispatch_cb and messages for this + // callback may be saved on temp list move them + // to orig list + for(unsigned int i=start; i < temp->m_n; i++) { + wxeCommand *ev = &temp->m_q[(temp->m_first+i) % temp->m_max]; + if(ev->caller == process) { + batch->Append(ev); } - event->Delete(); - } else { - // fprintf(stderr, " save %d \r\n", event->op); - temp->Append(event); + } } + break; } - } else { - if(callback_returned) { - return; - } - // sleep until something happens - //fprintf(stderr, "%s:%d sleep %d %d %d %d \r\n", __FILE__, __LINE__, batch->size(), callback_returned, blevel, is_callback);fflush(stderr); - while(batch->size() == 0) { - erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); + event->Delete(); + } else { + // fprintf(stderr, " save %d %lu\r\n", event->op, event->caller); + temp->Append(event); } } + // sleep until something happens + // fprintf(stderr, "%s:%d sleep %d %d\r\n", __FILE__, __LINE__, + // batch->m_n, temp->m_n);fflush(stderr); + while(batch->m_n == 0) { + erl_drv_cond_wait(wxe_batch_locker_c, wxe_batch_locker_m); + } } } - /* Memory handling */ void WxeApp::newMemEnv(wxeMetaCommand& Ecmd) { diff --git a/lib/wx/c_src/wxe_impl.h b/lib/wx/c_src/wxe_impl.h index 57bf2e2dba..a0a1c84718 100644 --- a/lib/wx/c_src/wxe_impl.h +++ b/lib/wx/c_src/wxe_impl.h @@ -60,8 +60,8 @@ public: #endif void shutdown(wxeMetaCommand& event); - int dispatch(wxList *, int, int); - void dispatch_cb(wxList * batch, wxList * temp, ErlDrvTermData process); + int dispatch(wxeFifo *, int, int); + void dispatch_cb(wxeFifo * batch, wxeFifo * temp, ErlDrvTermData process); void wxe_dispatch(wxeCommand& event); @@ -93,7 +93,7 @@ public: int recurse_level; wxList * delayed_cleanup; - wxList * delayed_delete; + wxeFifo * delayed_delete; // Temp container for callbacks char *cb_buff; int cb_len; diff --git a/lib/wx/include/wx.hrl b/lib/wx/include/wx.hrl index 9b913c7c00..97cb689374 100644 --- a/lib/wx/include/wx.hrl +++ b/lib/wx/include/wx.hrl @@ -54,7 +54,9 @@ -type wxListEventType() :: command_list_begin_drag | command_list_begin_rdrag | command_list_begin_label_edit | command_list_end_label_edit | command_list_delete_item | command_list_delete_all_items | command_list_key_down | command_list_insert_item | command_list_col_click | command_list_col_right_click | command_list_col_begin_drag | command_list_col_dragging | command_list_col_end_drag | command_list_item_selected | command_list_item_deselected | command_list_item_right_click | command_list_item_middle_click | command_list_item_activated | command_list_item_focused | command_list_cache_hint. -type wxList() :: #wxList{}. %% Callback event: {@link wxListEvent} --record(wxNotebook, {type :: wxNotebookEventType()}). %% Callback event: {@link wxNotebookEvent} +-record(wxNotebook,{type :: wxNotebookEventType(), %% Callback event: {@link wxNotebookEvent} + nSel :: integer(), + nOldSel :: integer()}). -type wxNotebookEventType() :: command_notebook_page_changed | command_notebook_page_changing. -type wxNotebook() :: #wxNotebook{}. %% Callback event: {@link wxNotebookEvent} @@ -86,7 +88,9 @@ -type wxWindowDestroyEventType() :: destroy. -type wxWindowDestroy() :: #wxWindowDestroy{}. %% Callback event: {@link wxWindowDestroyEvent} --record(wxCalendar, {type :: wxCalendarEventType()}). %% Callback event: {@link wxCalendarEvent} +-record(wxCalendar,{type :: wxCalendarEventType(), %% Callback event: {@link wxCalendarEvent} + wday :: wx:wx_enum(), + date :: wx:wx_datetime()}). -type wxCalendarEventType() :: calendar_sel_changed | calendar_day_changed | calendar_month_changed | calendar_year_changed | calendar_doubleclicked | calendar_weekday_clicked. -type wxCalendar() :: #wxCalendar{}. %% Callback event: {@link wxCalendarEvent} @@ -100,15 +104,19 @@ -type wxScrollEventType() :: scroll_top | scroll_bottom | scroll_lineup | scroll_linedown | scroll_pageup | scroll_pagedown | scroll_thumbtrack | scroll_thumbrelease | scroll_changed. -type wxScroll() :: #wxScroll{}. %% Callback event: {@link wxScrollEvent} --record(wxMenu, {type :: wxMenuEventType()}). %% Callback event: {@link wxMenuEvent} +-record(wxMenu,{type :: wxMenuEventType(), %% Callback event: {@link wxMenuEvent} + menuId :: integer(), + menu :: wxMenu:wxMenu()}). -type wxMenuEventType() :: menu_open | menu_close | menu_highlight. -type wxMenu() :: #wxMenu{}. %% Callback event: {@link wxMenuEvent} --record(wxContextMenu, {type :: wxContextMenuEventType()}). %% Callback event: {@link wxContextMenuEvent} +-record(wxContextMenu,{type :: wxContextMenuEventType(), %% Callback event: {@link wxContextMenuEvent} + pos :: {X::integer(), Y::integer()}}). -type wxContextMenuEventType() :: context_menu. -type wxContextMenu() :: #wxContextMenu{}. %% Callback event: {@link wxContextMenuEvent} --record(wxShow, {type :: wxShowEventType()}). %% Callback event: {@link wxShowEvent} +-record(wxShow,{type :: wxShowEventType(), %% Callback event: {@link wxShowEvent} + show :: boolean()}). -type wxShowEventType() :: show. -type wxShow() :: #wxShow{}. %% Callback event: {@link wxShowEvent} @@ -117,7 +125,10 @@ -type wxSpinEventType() :: command_spinctrl_updated | spin_up | spin_down | spin. -type wxSpin() :: #wxSpin{}. %% Callback event: {@link wxSpinEvent} --record(wxSetCursor, {type :: wxSetCursorEventType()}). %% Callback event: {@link wxSetCursorEvent} +-record(wxSetCursor,{type :: wxSetCursorEventType(), %% Callback event: {@link wxSetCursorEvent} + x :: integer(), + y :: integer(), + cursor :: wxCursor:wxCursor()}). -type wxSetCursorEventType() :: set_cursor. -type wxSetCursor() :: #wxSetCursor{}. %% Callback event: {@link wxSetCursorEvent} @@ -126,7 +137,9 @@ -type wxFontPickerEventType() :: command_fontpicker_changed. -type wxFontPicker() :: #wxFontPicker{}. %% Callback event: {@link wxFontPickerEvent} --record(wxScrollWin, {type :: wxScrollWinEventType()}). %% Callback event: {@link wxScrollWinEvent} +-record(wxScrollWin,{type :: wxScrollWinEventType(), %% Callback event: {@link wxScrollWinEvent} + commandInt :: integer(), + extraLong :: integer()}). -type wxScrollWinEventType() :: scrollwin_top | scrollwin_bottom | scrollwin_lineup | scrollwin_linedown | scrollwin_pageup | scrollwin_pagedown | scrollwin_thumbtrack | scrollwin_thumbrelease. -type wxScrollWin() :: #wxScrollWin{}. %% Callback event: {@link wxScrollWinEvent} @@ -147,7 +160,8 @@ -type wxFileDirPickerEventType() :: command_filepicker_changed | command_dirpicker_changed. -type wxFileDirPicker() :: #wxFileDirPicker{}. %% Callback event: {@link wxFileDirPickerEvent} --record(wxFocus, {type :: wxFocusEventType()}). %% Callback event: {@link wxFocusEvent} +-record(wxFocus,{type :: wxFocusEventType(), %% Callback event: {@link wxFocusEvent} + win :: wxWindow:wxWindow()}). -type wxFocusEventType() :: set_focus | kill_focus. -type wxFocus() :: #wxFocus{}. %% Callback event: {@link wxFocusEvent} @@ -225,7 +239,8 @@ -type wxSizeEventType() :: size. -type wxSize() :: #wxSize{}. %% Callback event: {@link wxSizeEvent} --record(wxIconize, {type :: wxIconizeEventType()}). %% Callback event: {@link wxIconizeEvent} +-record(wxIconize,{type :: wxIconizeEventType(), %% Callback event: {@link wxIconizeEvent} + iconized :: boolean()}). -type wxIconizeEventType() :: iconize. -type wxIconize() :: #wxIconize{}. %% Callback event: {@link wxIconizeEvent} @@ -289,7 +304,12 @@ -type wxCommandEventType() :: command_button_clicked | command_checkbox_clicked | command_choice_selected | command_listbox_selected | command_listbox_doubleclicked | command_text_updated | command_text_enter | command_menu_selected | command_slider_updated | command_radiobox_selected | command_radiobutton_selected | command_scrollbar_updated | command_vlbox_selected | command_combobox_selected | command_tool_rclicked | command_tool_enter | command_checklistbox_toggled | command_togglebutton_clicked | command_left_click | command_left_dclick | command_right_click | command_set_focus | command_kill_focus | command_enter. -type wxCommand() :: #wxCommand{}. %% Callback event: {@link wxCommandEvent} --record(wxJoystick, {type :: wxJoystickEventType()}). %% Callback event: {@link wxJoystickEvent} +-record(wxJoystick,{type :: wxJoystickEventType(), %% Callback event: {@link wxJoystickEvent} + pos :: {X::integer(), Y::integer()}, + zPosition :: integer(), + buttonChange :: integer(), + buttonState :: integer(), + joyStick :: integer()}). -type wxJoystickEventType() :: joy_button_down | joy_button_up | joy_move | joy_zmove. -type wxJoystick() :: #wxJoystick{}. %% Callback event: {@link wxJoystickEvent} @@ -297,7 +317,9 @@ -type wxQueryNewPaletteEventType() :: query_new_palette. -type wxQueryNewPalette() :: #wxQueryNewPalette{}. %% Callback event: {@link wxQueryNewPaletteEvent} --record(wxMove, {type :: wxMoveEventType()}). %% Callback event: {@link wxMoveEvent} +-record(wxMove,{type :: wxMoveEventType(), %% Callback event: {@link wxMoveEvent} + pos :: {X::integer(), Y::integer()}, + rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). -type wxMoveEventType() :: move. -type wxMove() :: #wxMove{}. %% Callback event: {@link wxMoveEvent} diff --git a/lib/wx/test/wx_class_SUITE.erl b/lib/wx/test/wx_class_SUITE.erl index b127e6b71d..45ab0f3a32 100644 --- a/lib/wx/test/wx_class_SUITE.erl +++ b/lib/wx/test/wx_class_SUITE.erl @@ -231,8 +231,15 @@ staticBoxSizer(Config) -> clipboard(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); -clipboard(_Config) -> - wx:new(), +clipboard(Config) -> + Wx = wx:new(), + Frame = wxFrame:new(Wx, ?wxID_ANY, "Main Frame"), + Ctrl = wxTextCtrl:new(Frame, ?wxID_ANY, [{size, {600,400}}, {style, ?wxTE_MULTILINE}]), + wxTextCtrl:connect(Ctrl, command_text_copy, [{skip, true}]), + wxTextCtrl:connect(Ctrl, command_text_cut, [{skip, true}]), + wxTextCtrl:connect(Ctrl, command_text_paste, [{skip, true}]), + wxWindow:show(Frame), + CB = ?mt(wxClipboard, wxClipboard:get()), wxClipboard:usePrimarySelection(CB), ?m(false, wx:is_null(CB)), @@ -271,7 +278,8 @@ clipboard(_Config) -> ?log("Flushing ~n",[]), wxClipboard:flush(CB), ?log("Stopping ~n",[]), - ok. + wx_test_lib:wx_destroy(Frame,Config). + helpFrame(TestInfo) when is_atom(TestInfo) -> wx_test_lib:tc_info(TestInfo); helpFrame(Config) -> diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl index 3252547c9b..6bcd88e4fb 100644 --- a/lib/wx/test/wx_event_SUITE.erl +++ b/lib/wx/test/wx_event_SUITE.erl @@ -379,25 +379,29 @@ recursive(Config) -> Frame = wxFrame:new(Wx, ?wxID_ANY, "Connect in callback"), Panel = wxPanel:new(Frame, []), Sz = wxBoxSizer:new(?wxVERTICAL), - ListBox = wxListBox:new(Panel, ?wxID_ANY, [{choices, ["foo", "bar", "baz"]}]), - wxSizer:add(Sz, ListBox, [{proportion, 1},{flag, ?wxEXPAND}]), - wxWindow:setSizer(Panel, Sz), - wxListBox:connect(ListBox, command_listbox_selected, - [{callback, - fun(#wx{event=#wxCommand{commandInt=Id}}, _) -> - io:format("Selected ~p~n",[Id]) - end}]), - wxListBox:setSelection(ListBox, 0), - wxListBox:connect(ListBox, size, - [{callback, - fun(#wx{event=#wxSize{}}, _) -> - io:format("Size init ~n",[]), - case wxListBox:getCount(ListBox) > 0 of - true -> wxListBox:delete(ListBox, 0); - false -> ok - end, - io:format("Size done ~n",[]) - end}]), + Ctrl1 = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {300, -1}}]), + Ctrl2 = wxTextCtrl:new(Panel, ?wxID_ANY, [{size, {300, -1}}]), + wxSizer:add(Sz, Ctrl1, [{proportion, 1},{flag, ?wxEXPAND}]), + wxSizer:add(Sz, Ctrl2, [{proportion, 1},{flag, ?wxEXPAND}]), + wxWindow:setSizerAndFit(Panel, Sz), + + CB1 = fun(#wx{event=#wxCommand{cmdString=String}}, _) -> + io:format(" CB1: ~s~n",[String]), + wxTextCtrl:setValue(Ctrl2, io_lib:format("from CB1 ~s", [String])) + end, + CB2 = fun(#wx{event=#wxCommand{cmdString=String}}, _) -> + io:format(" CB2: ~s~n",[String]), + ok + end, + wxTextCtrl:connect(Ctrl1, command_text_updated, [{callback,CB1}]), + wxTextCtrl:connect(Ctrl2, command_text_updated, [{callback,CB2}]), + wxFrame:connect(Frame, size, + [{callback, + fun(#wx{event=#wxSize{size=Size}}, _) -> + io:format("Size init: ~s ~n",[wxTextCtrl:getValue(Ctrl2)]), + wxTextCtrl:setValue(Ctrl1, io_lib:format("Size ~p", [Size])), + io:format("Size done: ~s ~n",[wxTextCtrl:getValue(Ctrl2)]) + end}]), wxFrame:show(Frame), wx_test_lib:flush(), |