diff options
Diffstat (limited to 'lib')
95 files changed, 2595 insertions, 1460 deletions
diff --git a/lib/asn1/src/asn1_db.erl b/lib/asn1/src/asn1_db.erl index 557eca0ffd..869ea310aa 100644 --- a/lib/asn1/src/asn1_db.erl +++ b/lib/asn1/src/asn1_db.erl @@ -106,7 +106,9 @@ loop(#state{parent = Parent, monitor = MRef, table = Table, loop(State); {save, OutFile, Mod} -> [{_,Mtab}] = ets:lookup(Table, Mod), - ok = ets:tab2file(Mtab, OutFile), + TempFile = OutFile ++ ".#temp", + ok = ets:tab2file(Mtab, TempFile), + ok = file:rename(TempFile, OutFile), loop(State); {From, {new, Mod, Erule}} -> [] = ets:lookup(Table, Mod), %Assertion. diff --git a/lib/asn1/src/asn1ct.erl b/lib/asn1/src/asn1ct.erl index dd269f095d..8783b5418d 100644 --- a/lib/asn1/src/asn1ct.erl +++ b/lib/asn1/src/asn1ct.erl @@ -221,9 +221,8 @@ check_pass(#st{code=M,file=File,includes=Includes, {error,St#st{error=Reason}} end. -save_pass(#st{code=M,erule=Erule,dbfile=DbFile}=St) -> +save_pass(#st{code=M,erule=Erule}=St) -> ok = asn1ct_check:storeindb(#state{erule=Erule}, M), - asn1_db:dbsave(DbFile,M#module.name), {ok,St}. parse_listing(#st{code=Code,outfile=OutFile0}=St) -> diff --git a/lib/asn1/test/asn1_test_lib.erl b/lib/asn1/test/asn1_test_lib.erl index 1b4c3b3c77..dc614db4f2 100644 --- a/lib/asn1/test/asn1_test_lib.erl +++ b/lib/asn1/test/asn1_test_lib.erl @@ -34,12 +34,16 @@ run_dialyzer() -> compile(File, Config, Options) -> compile_all([File], Config, Options). -compile_all(Files, Config, Options) -> +compile_all(Files, Config, Options0) -> DataDir = proplists:get_value(data_dir, Config), CaseDir = proplists:get_value(case_dir, Config), - [compile_file(filename:join(DataDir, F), [{outdir, CaseDir}, - debug_info|Options]) - || F <- Files], + Options = [{outdir,CaseDir},debug_info|Options0], + + Comp = fun(F) -> + compile_file(filename:join(DataDir, F), Options) + end, + p_run(Comp, Files), + dialyze(Files, Options), ok. @@ -94,9 +98,9 @@ compile_file(File, Options) -> try ok = asn1ct:compile(File, [warnings_as_errors|Options]) catch - Class:Reason -> - ct:print("Failed to compile ~s\n", [File]), - erlang:error({compile_failed, {File, Options}, {Class, Reason}}) + _:Reason -> + ct:print("Failed to compile ~s\n~p", [File,Reason]), + error end. compile_erlang(Mod, Config, Options) -> @@ -219,3 +223,38 @@ ber_get_len(<<0:1,L:7,T/binary>>) -> ber_get_len(<<1:1,Octets:7,T0/binary>>) -> <<L:Octets/unit:8,T/binary>> = T0, {L,T}. + +%% p_run(fun(Data) -> ok|error, List) -> ok +%% Will fail the test case if there were any errors. + +p_run(Test, List) -> + S = erlang:system_info(schedulers), + N = case test_server:is_cover() of + false -> + S + 1; + true -> + %% Cover is running. Using too many processes + %% could slow us down. + min(S, 4) + end, + %%io:format("p_run: ~p parallel processes\n", [N]), + p_run_loop(Test, List, N, [], 0). + +p_run_loop(_, [], _, [], Errors) -> + case Errors of + 0 -> ok; + N -> ct:fail({N,errors}) + end; +p_run_loop(Test, [H|T], N, Refs, Errors) when length(Refs) < N -> + {_,Ref} = erlang:spawn_monitor(fun() -> exit(Test(H)) end), + p_run_loop(Test, T, N, [Ref|Refs], Errors); +p_run_loop(Test, List, N, Refs0, Errors0) -> + receive + {'DOWN',Ref,process,_,Res} -> + Errors = case Res of + ok -> Errors0; + error -> Errors0+1 + end, + Refs = Refs0 -- [Ref], + p_run_loop(Test, List, N, Refs, Errors) + end. diff --git a/lib/common_test/src/ct_gen_conn.erl b/lib/common_test/src/ct_gen_conn.erl index 23ba1ab981..8b59d3ab23 100644 --- a/lib/common_test/src/ct_gen_conn.erl +++ b/lib/common_test/src/ct_gen_conn.erl @@ -27,7 +27,7 @@ -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]). +-export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]). %%---------------------------------------------------------------------- %% Exported types @@ -175,6 +175,14 @@ cont_log(Format,Args) -> log(cont_log,[Format,Args]). %%%----------------------------------------------------------------- +%%% @spec cont_log_no_timestamp(Format,Args) -> ok +%%% +%%% @doc Log activities on the current connection (tool-internal use only). +%%% @see ct_logs:cont_log/2 +cont_log_no_timestamp(Format,Args) -> + log(cont_log_no_timestamp,[Format,Args]). + +%%%----------------------------------------------------------------- %%% @spec end_log() -> ok %%% %%% @doc Log activities on the current connection (tool-internal use only). diff --git a/lib/common_test/src/ct_logs.erl b/lib/common_test/src/ct_logs.erl index 53245c596a..9282a9f81d 100644 --- a/lib/common_test/src/ct_logs.erl +++ b/lib/common_test/src/ct_logs.erl @@ -32,7 +32,7 @@ -export([init/2, close/2, init_tc/1, end_tc/1]). -export([register_groupleader/2, unregister_groupleader/1]). -export([get_log_dir/0, get_log_dir/1]). --export([log/3, start_log/1, cont_log/2, end_log/0]). +-export([log/3, start_log/1, cont_log/2, cont_log_no_timestamp/2, end_log/0]). -export([set_stylesheet/2, clear_stylesheet/1]). -export([add_external_logs/1, add_link/3]). -export([make_last_run_index/0]). @@ -373,6 +373,20 @@ cont_log(Format,Args) -> ok. %%%----------------------------------------------------------------- +%%% @spec cont_log_no_timestamp(Format,Args) -> ok +%%% +%%% @doc Adds information about an activity (tool-internal use only). +%%% +%%% @see start_log/1 +%%% @see end_log/0 +cont_log_no_timestamp([],[]) -> + ok; +cont_log_no_timestamp(Format,Args) -> + cast({log,sync,self(),group_leader(),ct_internal,?MAX_IMPORTANCE, + [{Format,Args}],true}), + ok. + +%%%----------------------------------------------------------------- %%% @spec end_log() -> ok %%% %%% @doc Ends the logging of an activity (tool-internal use only). @@ -595,7 +609,6 @@ div_header(Class,Printer) -> div_footer() -> "</pre></div>\n<pre>". - maybe_log_timestamp() -> {MS,S,US} = ?now, case get(log_timestamp) of diff --git a/lib/common_test/src/ct_telnet.erl b/lib/common_test/src/ct_telnet.erl index 8fb411ec4f..34d27ed5f4 100644 --- a/lib/common_test/src/ct_telnet.erl +++ b/lib/common_test/src/ct_telnet.erl @@ -954,7 +954,7 @@ log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port}, true -> ok; false -> - ct_gen_conn:cont_log(String,Args) + ct_gen_conn:cont_log_no_timestamp(String,Args) end; ForcePrint == true -> @@ -965,7 +965,7 @@ log(#state{name=Name,teln_pid=TelnPid,host=Host,port=Port}, %% called ct_gen_conn:log(heading(Action,Name1),String,Args); false -> - ct_gen_conn:cont_log(String,Args) + ct_gen_conn:cont_log_no_timestamp(String,Args) end end end. @@ -1224,7 +1224,6 @@ teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO, EOMod = if TotalTO /= infinity -> EO#eo{total_timeout=trunc(TotalTO)}; true -> EO end, - ExpectFun = case EOMod#eo.seq of true -> fun() -> seq_expect(Name,Pid,Data,Pattern,Acc,EOMod) @@ -1247,38 +1246,34 @@ teln_expect1(Name,Pid,Data,Pattern,Acc,EO=#eo{idle_timeout=IdleTO, true -> IdleTO end, + {PatOrPats1,Acc1,Rest1} = case NotFinished of + {nomatch,Rest0} -> + %% one expect + {Pattern,[],Rest0}; + {continue,Pats0,Acc0,Rest0} -> + %% sequence + {Pats0,Acc0,Rest0} + end, case timer:tc(ct_gen_conn, do_within_time, [Fun,BreakAfter]) of - {_,{error,Reason}} -> + {_,{error,Reason}} -> %% A timeout will occur when the telnet connection %% is idle for EO#eo.idle_timeout milliseconds. + if Rest1 /= [] -> + log(name_or_pid(Name,Pid)," ~ts",[Rest1]); + true -> + ok + end, {error,Reason}; {_,{ok,Data1}} when TotalTO == infinity -> - case NotFinished of - {nomatch,Rest} -> - %% One expect - teln_expect1(Name,Pid,Rest++Data1, - Pattern,[],EOMod); - {continue,Patterns1,Acc1,Rest} -> - %% Sequence - teln_expect1(Name,Pid,Rest++Data1, - Patterns1,Acc1,EOMod) - end; + teln_expect1(Name,Pid,Rest1++Data1,PatOrPats1,Acc1,EOMod); {Elapsed,{ok,Data1}} -> TVal = TotalTO - (Elapsed/1000), if TVal =< 0 -> {error,timeout}; true -> EO1 = EO#eo{total_timeout = TVal}, - case NotFinished of - {nomatch,Rest} -> - %% One expect - teln_expect1(Name,Pid,Rest++Data1, - Pattern,[],EO1); - {continue,Patterns1,Acc1,Rest} -> - %% Sequence - teln_expect1(Name,Pid,Rest++Data1, - Patterns1,Acc1,EO1) - end + teln_expect1(Name,Pid,Rest1++Data1, + PatOrPats1,Acc1,EO1) end end end. @@ -1416,14 +1411,14 @@ match_lines(Name,Pid,Data,Patterns,EO) -> case one_line(Data,[]) of {noline,Rest} when FoundPrompt=/=false -> %% This is the line including the prompt - case match_line(Name,Pid,Rest,Patterns,FoundPrompt,EO) of + case match_line(Name,Pid,Rest,Patterns,FoundPrompt,false,EO) of nomatch -> {nomatch,prompt}; {Tag,Match} -> {Tag,Match,[]} end; {noline,Rest} when EO#eo.prompt_check==false -> - case match_line(Name,Pid,Rest,Patterns,false,EO) of + case match_line(Name,Pid,Rest,Patterns,false,false,EO) of nomatch -> {nomatch,Rest}; {Tag,Match} -> @@ -1432,7 +1427,7 @@ match_lines(Name,Pid,Data,Patterns,EO) -> {noline,Rest} -> {nomatch,Rest}; {Line,Rest} -> - case match_line(Name,Pid,Line,Patterns,false,EO) of + case match_line(Name,Pid,Line,Patterns,false,true,EO) of nomatch -> match_lines(Name,Pid,Rest,Patterns,EO); {Tag,Match} -> @@ -1440,45 +1435,50 @@ match_lines(Name,Pid,Data,Patterns,EO) -> end end. - %% For one line, match each pattern -match_line(Name,Pid,Line,Patterns,FoundPrompt,EO) -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,match). +match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO) -> + match_line(Name,Pid,Line,Patterns,FoundPrompt,Terminated,EO,match). -match_line(Name,Pid,Line,[prompt|Patterns],false,EO,RetTag) -> - match_line(Name,Pid,Line,Patterns,false,EO,RetTag); -match_line(Name,Pid,Line,[prompt|_Patterns],FoundPrompt,_EO,RetTag) -> +match_line(Name,Pid,Line,[prompt|Patterns],false,Term,EO,RetTag) -> + match_line(Name,Pid,Line,Patterns,false,Term,EO,RetTag); +match_line(Name,Pid,Line,[prompt|_Patterns],FoundPrompt,_Term,_EO,RetTag) -> log(name_or_pid(Name,Pid)," ~ts",[Line]), log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), {RetTag,{prompt,FoundPrompt}}; -match_line(Name,Pid,Line,[{prompt,PromptType}|_Patterns],FoundPrompt,_EO,RetTag) - when PromptType==FoundPrompt -> +match_line(Name,Pid,Line,[{prompt,PromptType}|_Patterns],FoundPrompt,_Term, + _EO,RetTag) when PromptType==FoundPrompt -> log(name_or_pid(Name,Pid)," ~ts",[Line]), log(name_or_pid(Name,Pid),"PROMPT: ~ts",[FoundPrompt]), {RetTag,{prompt,FoundPrompt}}; -match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,EO,RetTag) +match_line(Name,Pid,Line,[{prompt,PromptType}|Patterns],FoundPrompt,Term, + EO,RetTag) when PromptType=/=FoundPrompt -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); -match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,EO,RetTag) -> + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); +match_line(Name,Pid,Line,[{Tag,Pattern}|Patterns],FoundPrompt,Term,EO,RetTag) -> case re:run(Line,Pattern,[{capture,all,list}]) of nomatch -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); {match,Match} -> log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), {RetTag,{Tag,Match}} end; -match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,EO,RetTag) -> +match_line(Name,Pid,Line,[Pattern|Patterns],FoundPrompt,Term,EO,RetTag) -> case re:run(Line,Pattern,[{capture,all,list}]) of nomatch -> - match_line(Name,Pid,Line,Patterns,FoundPrompt,EO,RetTag); + match_line(Name,Pid,Line,Patterns,FoundPrompt,Term,EO,RetTag); {match,Match} -> log(name_or_pid(Name,Pid),"MATCH: ~ts",[Line]), {RetTag,Match} end; -match_line(Name,Pid,Line,[],FoundPrompt,EO,match) -> - match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,EO,halt); -match_line(Name,Pid,Line,[],_FoundPrompt,_EO,halt) -> +match_line(Name,Pid,Line,[],FoundPrompt,Term,EO,match) -> + match_line(Name,Pid,Line,EO#eo.haltpatterns,FoundPrompt,Term,EO,halt); +%% print any terminated line that can not be matched +match_line(Name,Pid,Line,[],_FoundPrompt,true,_EO,halt) -> log(name_or_pid(Name,Pid)," ~ts",[Line]), + nomatch; +%% if there's no line termination, Line is saved as Rest (above) and will +%% be printed later +match_line(_Name,_Pid,_Line,[],_FoundPrompt,false,_EO,halt) -> nomatch. one_line([$\n|Rest],Line) -> diff --git a/lib/common_test/test/ct_config_SUITE.erl b/lib/common_test/test/ct_config_SUITE.erl index 9879e0f20d..cbbfe408a8 100644 --- a/lib/common_test/test/ct_config_SUITE.erl +++ b/lib/common_test/test/ct_config_SUITE.erl @@ -113,10 +113,14 @@ userconfig_static(Config) when is_list(Config) -> ["config_static_SUITE"]). userconfig_dynamic(Config) when is_list(Config) -> - run_test(config_dynamic_SUITE, - Config, - {userconfig, {config_driver, "config_server"}}, - ["config_dynamic_SUITE"]). + case skip_dynamic() of + true -> {skip,"TimeWarpingOS"}; + false -> + run_test(config_dynamic_SUITE, + Config, + {userconfig, {config_driver, "config_server"}}, + ["config_dynamic_SUITE"]) + end. testspec_legacy(Config) when is_list(Config) -> DataDir = ?config(data_dir, Config), @@ -147,16 +151,20 @@ testspec_static(Config) when is_list(Config) -> file:delete(filename:join(ConfigDir, "spec_static.spec")). testspec_dynamic(Config) when is_list(Config) -> - DataDir = ?config(data_dir, Config), - ConfigDir = ?config(config_dir, Config), - make_spec(DataDir, ConfigDir, "spec_dynamic.spec", - [config_dynamic_SUITE], - [{userconfig, {config_driver, "config_server"}}]), - run_test(config_dynamic_SUITE, - Config, - {spec, filename:join(ConfigDir, "spec_dynamic.spec")}, - []), - file:delete(filename:join(ConfigDir, "spec_dynamic.spec")). + case skip_dynamic() of + true -> {skip,"TimeWarpingOS"}; + false -> + DataDir = ?config(data_dir, Config), + ConfigDir = ?config(config_dir, Config), + make_spec(DataDir, ConfigDir, "spec_dynamic.spec", + [config_dynamic_SUITE], + [{userconfig, {config_driver, "config_server"}}]), + run_test(config_dynamic_SUITE, + Config, + {spec, filename:join(ConfigDir, "spec_dynamic.spec")}, + []), + file:delete(filename:join(ConfigDir, "spec_dynamic.spec")) + end. @@ -198,6 +206,23 @@ setup_env(Test, Config, CTConfig) -> reformat_events(Events, EH) -> ct_test_support:reformat(Events, EH). + +%%%----------------------------------------------------------------- +%%% Test related to 'localtime' will often fail if the test host is +%%% time warping, so let's just skip the 'dynamic' tests then. +skip_dynamic() -> + case os:getenv("TS_EXTRA_PLATFORM_LABEL") of + TSExtraPlatformLabel when is_list(TSExtraPlatformLabel) -> + case string:str(TSExtraPlatformLabel,"TimeWarpingOS") of + 0 -> false; + _ -> true + end; + _ -> + false + end. + + + %%%----------------------------------------------------------------- %%% TEST EVENTS %%%----------------------------------------------------------------- diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl index a65275da43..f2580ad8e9 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/netconfc_remote_SUITE.erl @@ -83,7 +83,9 @@ end_per_suite(Config) -> %% Running the netconf server in a remote node, test that the client %% process terminates if the remote node goes down. remote_crash(Config) -> - {ok,Node} = ct_slave:start(nc_remote_crash), + {ok,Node} = ct_slave:start(nc_remote_crash,[{boot_timeout,15}, + {init_timeout,15}, + {startup_timeout,15}]), Pa = filename:dirname(code:which(?NS)), true = rpc:call(Node,code,add_patha,[Pa]), rpc:call(Node,code,load_file,[crypto]), diff --git a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl index e62bc617fa..2412ea6aba 100644 --- a/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl +++ b/lib/common_test/test/ct_netconfc_SUITE_data/ns.erl @@ -302,10 +302,14 @@ table_trans(Fun,Args) -> S -> apply(Fun,Args); Pid -> + Ref = erlang:monitor(process,Pid), Pid ! {table_trans,Fun,Args,self()}, receive {table_trans_done,Result} -> - Result + erlang:demonitor(Ref,[flush]), + Result; + {'DOWN',Ref,process,Pid,Reason} -> + exit({main_ns_proc_died,Reason}) after 20000 -> exit(table_trans_timeout) end diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml index e25cdc580c..92b3d809fc 100644 --- a/lib/compiler/doc/src/notes.xml +++ b/lib/compiler/doc/src/notes.xml @@ -32,6 +32,23 @@ <p>This document describes the changes made to the Compiler application.</p> +<section><title>Compiler 7.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + A literal binary matching regression was introduced in + 19.0 where a match could fail to resolve to the right + clause. This has now been fixed.</p> + <p> + Own Id: OTP-13738</p> + </item> + </list> + </section> + +</section> + <section><title>Compiler 7.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/compiler/src/beam_block.erl b/lib/compiler/src/beam_block.erl index 85d332c56e..ec41925beb 100644 --- a/lib/compiler/src/beam_block.erl +++ b/lib/compiler/src/beam_block.erl @@ -159,14 +159,43 @@ find_fixpoint(OptFun, Is0) -> end. %% move_allocates(Is0) -> Is -%% Move allocate instructions upwards in the instruction stream, in the -%% hope of getting more possibilities for optimizing away moves later. +%% Move allocate instructions upwards in the instruction stream +%% (within the same block), in the hope of getting more possibilities +%% for optimizing away moves later. %% -%% NOTE: Moving allocation instructions is only safe because it is done -%% immediately after code generation so that we KNOW that if {x,X} is -%% initialized, all x registers with lower numbers are also initialized. -%% That assumption may not be true after other optimizations, such as -%% the beam_utils:live_opt/1 optimization. +%% For example, we can transform the following instructions: +%% +%% get_tuple_element x(1) Element => x(2) +%% allocate_zero StackSize 3 %% x(0), x(1), x(2) are live +%% +%% to the following instructions: +%% +%% allocate_zero StackSize 2 %% x(0) and x(1) are live +%% get_tuple_element x(1) Element => x(2) +%% +%% NOTE: Since the beam_reorder pass has been run, it is no longer +%% safe to assume that if x(N) is initialized, then all lower-numbered +%% x registers are also initialized. +%% +%% For example, in general it is not safe to transform the following +%% instructions: +%% +%% get_tuple_element x(0) Element => x(1) +%% allocate_zero StackSize 3 %x(0), x(1), x(2) are live +%% +%% to the following instructions: +%% +%% allocate_zero StackSize 3 +%% get_tuple_element x(0) Element => x(1) +%% +%% The transformation is safe if and only if x(1) has been +%% initialized previously. Unfortunately, beam_reorder may have moved +%% a get_tuple_element instruction so that x(1) is not always +%% initialized when this code is reached. To find whether or not x(1) +%% is initialized, we would need to analyze all code preceding these +%% two instructions (across branches). Since we currently don't have +%% any practical mechanism for doing that, we will have to +%% conservatively assume that the transformation is unsafe. move_allocates([{block,Bl0}|Is]) -> Bl = move_allocates_1(reverse(Bl0), []), @@ -175,27 +204,19 @@ move_allocates([I|Is]) -> [I|move_allocates(Is)]; move_allocates([]) -> []. -move_allocates_1([{set,[],[],{alloc,_,_}=Alloc}|Is0], Acc0) -> - {Is,Acc} = move_allocates_2(Alloc, Is0, Acc0), - move_allocates_1(Is, Acc); +move_allocates_1([I|Is], [{set,[],[],{alloc,Live0,Info}}|Acc]=Acc0) -> + case {alloc_may_pass(I),alloc_live_regs(I, Live0)} of + {false,_} -> + move_allocates_1(Is, [I|Acc0]); + {true,not_possible} -> + move_allocates_1(Is, [I|Acc0]); + {true,Live} when is_integer(Live) -> + A = {set,[],[],{alloc,Live,Info}}, + move_allocates_1(Is, [A,I|Acc]) + end; move_allocates_1([I|Is], Acc) -> move_allocates_1(Is, [I|Acc]); -move_allocates_1([], Is) -> Is. - -move_allocates_2({alloc,Live,Info}, [{set,[],[],{alloc,Live0,Info0}}|Is], Acc) -> - Live = Live0, % Assertion. - Alloc = {alloc,Live,combine_alloc(Info0, Info)}, - move_allocates_2(Alloc, Is, Acc); -move_allocates_2({alloc,Live,Info}=Alloc0, [I|Is]=Is0, Acc) -> - case alloc_may_pass(I) of - false -> - {Is0,[{set,[],[],Alloc0}|Acc]}; - true -> - Alloc = {alloc,alloc_live_regs(I, Live),Info}, - move_allocates_2(Alloc, Is, [I|Acc]) - end; -move_allocates_2(Alloc, [], Acc) -> - {[],[{set,[],[],Alloc}|Acc]}. +move_allocates_1([], Acc) -> Acc. alloc_may_pass({set,_,_,{alloc,_,_}}) -> false; alloc_may_pass({set,_,_,{set_tuple_element,_}}) -> false; @@ -204,9 +225,6 @@ alloc_may_pass({set,_,_,put_list}) -> false; alloc_may_pass({set,_,_,put}) -> false; alloc_may_pass({set,_,_,_}) -> true. -combine_alloc({_,Ns,Nh1,Init}, {_,nostack,Nh2,[]}) -> - {zero,Ns,beam_utils:combine_heap_needs(Nh1, Nh2),Init}. - %% opt([Instruction]) -> [Instruction] %% Optimize the instruction stream inside a basic block. @@ -393,10 +411,19 @@ eliminate_use_of_from_reg([I]=Is, From, _To, Acc) -> %% opt_alloc(Instructions) -> Instructions' %% Optimises all allocate instructions. +opt_alloc([{set,[],[],{alloc,Live0,Info0}}, + {set,[],[],{alloc,Live,Info}}|Is]) -> + Live = Live0, %Assertion. + Alloc = combine_alloc(Info0, Info), + I = {set,[],[],{alloc,Live,Alloc}}, + opt_alloc([I|Is]); opt_alloc([{set,[],[],{alloc,R,{_,Ns,Nh,[]}}}|Is]) -> [{set,[],[],opt_alloc(Is, Ns, Nh, R)}|Is]; opt_alloc([I|Is]) -> [I|opt_alloc(Is)]; opt_alloc([]) -> []. + +combine_alloc({_,Ns,Nh1,Init}, {_,nostack,Nh2,[]}) -> + {zero,Ns,beam_utils:combine_heap_needs(Nh1, Nh2),Init}. %% opt_alloc(Instructions, FrameSize, HeapNeed, LivingRegs) -> [Instr] %% Generates the optimal sequence of instructions for @@ -445,13 +472,14 @@ count_ones(Bits, Acc) -> alloc_live_regs({set,Ds,Ss,_}, Regs0) -> Rset = x_live(Ss, x_dead(Ds, (1 bsl Regs0)-1)), - live_regs(Rset). + live_regs(0, Rset). -live_regs(Regs) -> - live_regs_1(0, Regs). - -live_regs_1(N, 0) -> N; -live_regs_1(N, Regs) -> live_regs_1(N+1, Regs bsr 1). +live_regs(N, 0) -> + N; +live_regs(N, Regs) when Regs band 1 =:= 1 -> + live_regs(N+1, Regs bsr 1); +live_regs(_, _) -> + not_possible. x_dead([{x,N}|Rs], Regs) -> x_dead(Rs, Regs band (bnot (1 bsl N))); x_dead([_|Rs], Regs) -> x_dead(Rs, Regs); diff --git a/lib/compiler/src/beam_dead.erl b/lib/compiler/src/beam_dead.erl index b01f58f683..6f6d742293 100644 --- a/lib/compiler/src/beam_dead.erl +++ b/lib/compiler/src/beam_dead.erl @@ -272,17 +272,14 @@ backward([{jump,{f,To}}=J|[{bif,Op,_,Ops,Reg}|Is]=Is0], D, Acc) -> catch throw:not_possible -> backward(Is0, D, [J|Acc]) end; -backward([{test,bs_start_match2,F,Live,[R,_]=Args,Ctxt}|Is], D, +backward([{test,bs_start_match2,F,_,[R,_],Ctxt}=I|Is], D, [{test,bs_match_string,F,[Ctxt,Bs]}, {test,bs_test_tail2,F,[Ctxt,0]}|Acc0]=Acc) -> - {f,To0} = F, - To = shortcut_bs_start_match(To0, R, D), case beam_utils:is_killed(Ctxt, Acc0, D) of true -> - Eq = {test,is_eq_exact,{f,To},[R,{literal,Bs}]}, + Eq = {test,is_eq_exact,F,[R,{literal,Bs}]}, backward(Is, D, [Eq|Acc0]); false -> - I = {test,bs_start_match2,{f,To},Live,Args,Ctxt}, backward(Is, D, [I|Acc]) end; backward([{test,bs_start_match2,{f,To0},Live,[Src|_]=Info,Dst}|Is], D, Acc) -> diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 09cd3aa2d4..48b5a32814 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -167,12 +167,18 @@ share_1([{label,L}=Lbl|Is], Dict0, Seq, Acc) -> end; share_1([{func_info,_,_,_}=I|Is], _, [], Acc) -> reverse(Is, [I|Acc]); +share_1([{'catch',_,_}=I|Is], Dict0, Seq, Acc) -> + Dict = clean_non_sharable(Dict0), + share_1(Is, Dict, [I|Seq], Acc); share_1([{'try',_,_}=I|Is], Dict0, Seq, Acc) -> Dict = clean_non_sharable(Dict0), share_1(Is, Dict, [I|Seq], Acc); share_1([{try_case,_}=I|Is], Dict0, Seq, Acc) -> Dict = clean_non_sharable(Dict0), share_1(Is, Dict, [I|Seq], Acc); +share_1([{catch_end,_}=I|Is], Dict0, Seq, Acc) -> + Dict = clean_non_sharable(Dict0), + share_1(Is, Dict, [I|Seq], Acc); share_1([I|Is], Dict, Seq, Acc) -> case is_unreachable_after(I) of false -> @@ -182,18 +188,18 @@ share_1([I|Is], Dict, Seq, Acc) -> end. clean_non_sharable(Dict) -> - %% We are passing in or out of a 'try' block. Remove - %% sequences that should not shared over the boundaries - %% of a 'try' block. Since the end of the sequence must match, - %% the only possible match between a sequence outside and - %% a sequence inside the 'try' block is a sequence that ends - %% with an instruction that causes an exception. Any sequence - %% that causes an exception must contain a line/1 instruction. + %% We are passing in or out of a 'catch' or 'try' block. Remove + %% sequences that should not be shared over the boundaries of the + %% block. Since the end of the sequence must match, the only + %% possible match between a sequence outside and a sequence inside + %% the 'catch'/'try' block is a sequence that ends with an + %% instruction that causes an exception. Any sequence that causes + %% an exception must contain a line/1 instruction. maps:filter(fun(K, _V) -> sharable_with_try(K) end, Dict). sharable_with_try([{line,_}|_]) -> %% This sequence may cause an exception and may potentially - %% match a sequence on the other side of the 'try' block + %% match a sequence on the other side of the 'catch'/'try' block %% boundary. false; sharable_with_try([_|Is]) -> diff --git a/lib/compiler/test/beam_block_SUITE.erl b/lib/compiler/test/beam_block_SUITE.erl index 4bcb252833..9fcb6e497d 100644 --- a/lib/compiler/test/beam_block_SUITE.erl +++ b/lib/compiler/test/beam_block_SUITE.erl @@ -21,7 +21,8 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, - get_map_elements/1,otp_7345/1,move_opt_across_gc_bif/1]). + get_map_elements/1,otp_7345/1,move_opt_across_gc_bif/1, + erl_202/1]). %% The only test for the following functions is that %% the code compiles and is accepted by beam_validator. @@ -37,7 +38,8 @@ groups() -> [{p,[parallel], [get_map_elements, otp_7345, - move_opt_across_gc_bif + move_opt_across_gc_bif, + erl_202 ]}]. init_per_suite(Config) -> @@ -135,6 +137,27 @@ positive(speaking) -> paris([], P) -> P + 1. + +%% See https://bugs.erlang.org/browse/ERL-202. +%% Test that move_allocates/1 in beam_block doesn't move allocate +%% when it would not be safe. + +-record(erl_202_r1, {y}). +-record(erl_202_r2, {x}). + +erl_202(_Config) -> + Ref = make_ref(), + Ref = erl_202({{1,2},Ref}, 42), + + {Ref} = erl_202({7,8}, #erl_202_r1{y=#erl_202_r2{x=Ref}}), + + ok. + +erl_202({{_, _},X}, _) -> + X; +erl_202({_, _}, #erl_202_r1{y=R2}) -> + {R2#erl_202_r2.x}. + %%% %%% The only test of the following code is that it compiles. %%% diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl index 0b13adaff2..088f63606c 100644 --- a/lib/compiler/test/beam_jump_SUITE.erl +++ b/lib/compiler/test/beam_jump_SUITE.erl @@ -21,7 +21,7 @@ -export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, init_per_group/2,end_per_group/2, - undefined_label/1]). + undefined_label/1,ambiguous_catch_try_state/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -32,7 +32,8 @@ all() -> groups() -> [{p,[parallel], - [undefined_label + [undefined_label, + ambiguous_catch_try_state ]}]. init_per_suite(Config) -> @@ -57,3 +58,17 @@ flights(0, [], []) when [], 0; 0.0, [], false -> clark; flights(_, Reproduction, introduction) when false, Reproduction -> responsible. + +%% [ERL-209] beam_jump would share 'catch' blocks, causing an +%% ambiguous_catch_try_state error in beam_validator. + +ambiguous_catch_try_state(_Config) -> + {{'EXIT',{{case_clause,song},_}},{'EXIT',{{case_clause,song},_}}} = + checks(42), + ok. + +river() -> song. + +checks(Wanted) -> + %% Must be one line to cause the unsafe optimization. + {catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}. diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index 31402ac717..127679ba69 100644 --- a/lib/compiler/test/match_SUITE.erl +++ b/lib/compiler/test/match_SUITE.erl @@ -24,7 +24,7 @@ pmatch/1,mixed/1,aliases/1,non_matching_aliases/1, match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1, selectify/1,underscore/1,match_map/1,map_vars_used/1, - coverage/1,grab_bag/1]). + coverage/1,grab_bag/1,literal_binary/1]). -include_lib("common_test/include/ct.hrl"). @@ -40,7 +40,7 @@ groups() -> match_in_call,untuplify, shortcut_boolean,letify_guard,selectify, underscore,match_map,map_vars_used,coverage, - grab_bag]}]. + grab_bag,literal_binary]}]. init_per_suite(Config) -> @@ -574,6 +574,15 @@ grab_bag_remove_failure([{stretch,_,Mi}=Stretch | Specs], Unit, _MaxFailure) -> ok end. +%% Regression in 19.0, reported by Alexei Sholik +literal_binary(_Config) -> + 3 = literal_binary_match(bar,<<"y">>), + ok. + +literal_binary_match(bar, <<"x">>) -> 1; +literal_binary_match(_, <<"x">>) -> 2; +literal_binary_match(_, <<"y">>) -> 3; +literal_binary_match(_, _) -> fail. id(I) -> I. diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk index 23dd4bd4b1..334784657e 100644 --- a/lib/compiler/vsn.mk +++ b/lib/compiler/vsn.mk @@ -1 +1 @@ -COMPILER_VSN = 7.0 +COMPILER_VSN = 7.0.1 diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 7183c395ae..d0044fe723 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -749,7 +749,7 @@ static ERL_NIF_TERM algo_hash[8]; /* increase when extending the list */ static int algo_pubkey_cnt; static ERL_NIF_TERM algo_pubkey[7]; /* increase when extending the list */ static int algo_cipher_cnt; -static ERL_NIF_TERM algo_cipher[20]; /* increase when extending the list */ +static ERL_NIF_TERM algo_cipher[21]; /* increase when extending the list */ static void init_algorithms_types(ErlNifEnv* env) { diff --git a/lib/debugger/doc/src/Makefile b/lib/debugger/doc/src/Makefile index 6c9617ca69..0f724b6f17 100644 --- a/lib/debugger/doc/src/Makefile +++ b/lib/debugger/doc/src/Makefile @@ -114,7 +114,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index bcac8afe64..7f86520c06 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -282,7 +282,7 @@ cl_check_log(none) -> cl_check_log(Output) -> io:format(" Check output file `~s' for details\n", [Output]). --spec format_warning(raw_warning()) -> string(). +-spec format_warning(raw_warning() | dial_warning()) -> string(). format_warning(W) -> format_warning(W, basename). diff --git a/lib/diameter/src/Makefile b/lib/diameter/src/Makefile index 7f61620fc1..6bf748a727 100644 --- a/lib/diameter/src/Makefile +++ b/lib/diameter/src/Makefile @@ -123,7 +123,7 @@ ERL_COMPILE_FLAGS += \ # erl/hrl from dictionary file. gen/diameter_gen_%.erl gen/diameter_gen_%.hrl: dict/%.dia $(dia_verbose) \ - ../bin/diameterc -o gen -i $(EBIN) $< + escript ../bin/diameterc -o gen -i $(EBIN) $< opt: $(TARGET_FILES) diff --git a/lib/gs/doc/src/Makefile b/lib/gs/doc/src/Makefile index b270bc84fe..a24ec77e31 100644 --- a/lib/gs/doc/src/Makefile +++ b/lib/gs/doc/src/Makefile @@ -148,7 +148,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3DIR)/* "$(RELEASE_PATH)/man/man3" diff --git a/lib/ic/doc/src/Makefile b/lib/ic/doc/src/Makefile index c9691df7af..19f12ac6b9 100644 --- a/lib/ic/doc/src/Makefile +++ b/lib/ic/doc/src/Makefile @@ -225,7 +225,7 @@ release_docs_spec: docs $(INSTALL_DATA) $(TOP_PDF_FILE) "$(RELSYSDIR)/doc/pdf" $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" - (/bin/cp -rf $(HTMLDIR) "$(RELSYSDIR)/doc") + ($(CP) -rf $(HTMLDIR) "$(RELSYSDIR)/doc") $(INSTALL_DIR) "$(RELEASE_PATH)/man/man3" $(INSTALL_DATA) $(MAN3_FILES) "$(RELEASE_PATH)/man/man3" diff --git a/lib/inets/doc/src/httpd.xml b/lib/inets/doc/src/httpd.xml index 62b92b8356..b05ae72983 100644 --- a/lib/inets/doc/src/httpd.xml +++ b/lib/inets/doc/src/httpd.xml @@ -455,7 +455,7 @@ text/plain asc txt</pre> directory. Several files can be given, in which case the server returns the first it finds, for example:</p> - <code>{directory_index, ["index.hml", "welcome.html"]}</code> + <code>{directory_index, ["index.html", "welcome.html"]}</code> <p>Access to http://your.server.org/docs/ would return http://your.server.org/docs/index.html or diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index 8279fdc824..006fca1bdf 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -61,7 +61,7 @@ <tag><c>{server_port, integer()}</c></tag> <item><p>Servers port number.</p></item> - <tag><c>{request_method, "GET | "PUT" | "DELETE | "POST" | "PATCH"}</c></tag> + <tag><c>{request_method, "GET | "PUT" | "DELETE" | "POST" | "PATCH"}</c></tag> <item><p>HTTP request method.</p></item> <tag><c>{remote_adress, inet:ip_address()} </c></tag> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index 2f071f049f..caa5a083a3 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,22 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.3.1</title> + <section><title>Inets 6.3.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + PUT and DELETE support has been added to mod_esi</p> + <p> + Own Id: OTP-13688 Aux Id: seq13149 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.3.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_server/httpd_conf.erl b/lib/inets/src/http_server/httpd_conf.erl index e5182ca23c..9e54f2b2c5 100644 --- a/lib/inets/src/http_server/httpd_conf.erl +++ b/lib/inets/src/http_server/httpd_conf.erl @@ -395,7 +395,8 @@ validate_properties(Properties) -> %% That is, if property A depends on property B. %% The only sunch preperty at this time is bind_address that depends %% on ipfamily. -validate_properties2(Properties) -> +validate_properties2(Properties0) -> + Properties = fix_ipfamily(Properties0), case proplists:get_value(bind_address, Properties) of undefined -> case proplists:get_value(sock_type, Properties, ip_comm) of @@ -422,6 +423,15 @@ validate_properties2(Properties) -> end end. +fix_ipfamily(Properties) -> + case proplists:get_value(ipfamily, Properties) of + undefined -> + Properties; + IpFamily -> + NewProps = proplists:delete(ipfamily, Properties), + [{ipfamily, validate_ipfamily(IpFamily)} | NewProps] + end. + add_inet_defaults(Properties) -> case proplists:get_value(ipfamily, Properties) of undefined -> diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 424d269859..c893b10dca 100644 --- a/lib/inets/src/http_server/httpd_example.erl +++ b/lib/inets/src/http_server/httpd_example.erl @@ -20,7 +20,7 @@ %% -module(httpd_example). -export([print/1]). --export([get/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]). +-export([get/2, put/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]). -export([newformat/3]). %% These are used by the inets test-suite @@ -59,6 +59,11 @@ get(_Env,[]) -> get(Env,Input) -> default(Env,Input). +put(Env,{Input,_Body}) -> + default(Env,Input); +put(Env,Input) -> + default(Env,Input). + get_bin(_Env,_Input) -> [list_to_binary(header()), list_to_binary(top("GET Example")), @@ -94,7 +99,7 @@ default(Env,Input) -> io_lib:format("~p",[httpd:parse_query(Input)]),"\n", footer()]. -peer(Env, Input) -> +peer(Env, _Input) -> Header = case proplists:get_value(peer_cert, Env) of undefined -> @@ -161,7 +166,7 @@ sleep(T) -> receive after T -> ok end. %% ------------------------------------------------------ -chunk_timeout(SessionID, _, StrInt) -> +chunk_timeout(SessionID, _, _StrInt) -> mod_esi:deliver(SessionID, "Tranfer-Encoding:chunked/html\r\n\r\n"), mod_esi:deliver(SessionID, top("Test chunk encoding timeout")), timer:sleep(20000), diff --git a/lib/inets/src/http_server/mod_esi.erl b/lib/inets/src/http_server/mod_esi.erl index 2800250727..b21af1418c 100644 --- a/lib/inets/src/http_server/mod_esi.erl +++ b/lib/inets/src/http_server/mod_esi.erl @@ -241,7 +241,7 @@ alias_match_str(Alias, eval_script_alias) -> %%------------------------ Erl mechanism -------------------------------- erl(#mod{method = Method} = ModData, ESIBody, Modules) - when (Method =:= "GET") orelse (Method =:= "HEAD") -> + when (Method =:= "GET") orelse (Method =:= "HEAD") orelse (Method =:= "DELETE") -> ?hdrt("erl", [{method, Method}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok, [ModuleName, FuncAndInput]} -> @@ -264,35 +264,32 @@ erl(#mod{method = Method} = ModData, ESIBody, Modules) {proceed, [{status,{400, none, BadRequest}} | ModData#mod.data]} end; -erl(#mod{request_uri = ReqUri, - method = "PUT", - http_version = Version, - data = Data}, _ESIBody, _Modules) -> - ?hdrt("erl", [{method, put}]), - {proceed, [{status,{501,{"PUT", ReqUri, Version}, - ?NICE("Erl mechanism doesn't support method PUT")}}| - Data]}; - -erl(#mod{request_uri = ReqUri, - method = "DELETE", - http_version = Version, - data = Data}, _ESIBody, _Modules) -> - ?hdrt("erl", [{method, delete}]), - {proceed,[{status,{501,{"DELETE", ReqUri, Version}, - ?NICE("Erl mechanism doesn't support method DELETE")}}| - Data]}; - -erl(#mod{request_uri = ReqUri, - method = "PATCH", - http_version = Version, - data = Data}, _ESIBody, _Modules) -> - ?hdrt("erl", [{method, patch}]), - {proceed, [{status,{501,{"PATCH", ReqUri, Version}, - ?NICE("Erl mechanism doesn't support method PATCH")}}| - Data]}; +erl(#mod{method = "PUT", entity_body = Body} = ModData, + ESIBody, Modules) -> + case httpd_util:split(ESIBody,":|%3A|/",2) of + {ok, [ModuleName, FuncAndInput]} -> + case httpd_util:split(FuncAndInput,"[\?/]",2) of + {ok, [FunctionName, Input]} -> + generate_webpage(ModData, ESIBody, Modules, + list_to_atom(ModuleName), + FunctionName, {Input,Body}, + [{entity_body, Body} | + script_elements(FuncAndInput, Input)]); + {ok, [FunctionName]} -> + generate_webpage(ModData, ESIBody, Modules, + list_to_atom(ModuleName), + FunctionName, {undefined,Body}, + [{entity_body, Body} | + script_elements(FuncAndInput, "")]); + {ok, BadRequest} -> + {proceed,[{status,{400,none, BadRequest}} | + ModData#mod.data]} + end; + {ok, BadRequest} -> + {proceed, [{status,{400, none, BadRequest}} | ModData#mod.data]} + end; -erl(#mod{method = "POST", - entity_body = Body} = ModData, ESIBody, Modules) -> +erl(#mod{method = "POST", entity_body = Body} = ModData, ESIBody, Modules) -> ?hdrt("erl", [{method, post}]), case httpd_util:split(ESIBody,":|%3A|/",2) of {ok,[ModuleName, Function]} -> @@ -301,7 +298,16 @@ erl(#mod{method = "POST", Function, Body, [{entity_body, Body}]); {ok, BadRequest} -> {proceed,[{status, {400, none, BadRequest}} | ModData#mod.data]} - end. + end; + +erl(#mod{request_uri = ReqUri, + method = "PATCH", + http_version = Version, + data = Data}, _ESIBody, _Modules) -> + ?hdrt("erl", [{method, patch}]), + {proceed, [{status,{501,{"PATCH", ReqUri, Version}, + ?NICE("Erl mechanism doesn't support method PATCH")}}| + Data]}. generate_webpage(ModData, ESIBody, [all], Module, FunctionName, Input, ScriptElements) -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 3a31daeb20..d4d21f6774 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,10 +18,16 @@ %% %CopyrightEnd% {"%VSN%", [ + {<<"6.3.1">>, + [{load_module, mod_esi, soft_purge, soft_purge, []} + ]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ], [ + {<<"6.3.1">>, + [{load_module, mod_esi, soft_purge, soft_purge, []} + ]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ] diff --git a/lib/inets/test/httpd_SUITE.erl b/lib/inets/test/httpd_SUITE.erl index 3194b5ad3d..28e77151f2 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2013-2015. All Rights Reserved. +%% Copyright Ericsson AB 2013-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -119,8 +119,10 @@ groups() -> ]}, {htaccess, [], [htaccess_1_1, htaccess_1_0, htaccess_0_9]}, {security, [], [security_1_1, security_1_0]}, %% Skip 0.9 as causes timing issus in test code - {http_1_1, [], [host, chunked, expect, cgi, cgi_chunked_encoding_test, - trace, range, if_modified_since, mod_esi_chunk_timeout] ++ http_head() ++ http_get() ++ load()}, + {http_1_1, [], + [host, chunked, expect, cgi, cgi_chunked_encoding_test, + trace, range, if_modified_since, mod_esi_chunk_timeout, + esi_put] ++ http_head() ++ http_get() ++ load()}, {http_1_0, [], [host, cgi, trace] ++ http_head() ++ http_get() ++ load()}, {http_0_9, [], http_head() ++ http_get() ++ load()} ]. @@ -283,20 +285,50 @@ init_per_testcase(Case, Config) when Case == host; Case == trace -> http_1_1 -> httpd_1_1 end, - [{version_cb, Cb} | proplists:delete(version_cb, Config)]; + dbg( + Case, + [{version_cb, Cb} | proplists:delete(version_cb, Config)], + init); init_per_testcase(range, Config) -> ct:timetrap({seconds, 20}), DocRoot = proplists:get_value(doc_root, Config), create_range_data(DocRoot), - Config; + dbg(range, Config, init); -init_per_testcase(_, Config) -> +init_per_testcase(Case, Config) -> ct:timetrap({seconds, 20}), - Config. - -end_per_testcase(_Case, _Config) -> - ok. + dbg(Case, Config, init). + +end_per_testcase(Case, Config) -> + dbg(Case, Config, 'end'). + + +dbg(Case, Config, Status) -> + Cases = [esi_put], + case lists:member(Case, Cases) of + true -> + case Status of + init -> + dbg:tracer(), + dbg:p(all, c), + dbg:tpl(httpd_example, cx), + dbg:tpl(mod_esi, generate_webpage, cx), + io:format("dbg: started~n"), + Config; + 'end' -> + io:format("dbg: stopped~n"), + dbg:stop_clear(), + ok + end; + false -> + case Status of + init -> + Config; + 'end' -> + ok + end + end. %%------------------------------------------------------------------------- %% Test cases starts here. @@ -765,6 +797,14 @@ esi(Config) when is_list(Config) -> ok = http_status("GET /cgi-bin/erl/httpd_example:peer ", Config, [{statuscode, 200}, {header, "peer-cert-exist", peer(Config)}]). + +%%------------------------------------------------------------------------- +esi_put() -> + [{doc, "Test mod_esi PUT"}]. + +esi_put(Config) when is_list(Config) -> + ok = http_status("PUT /cgi-bin/erl/httpd_example/put/123342234123 ", + Config, [{statuscode, 200}]). %%------------------------------------------------------------------------- mod_esi_chunk_timeout(Config) when is_list(Config) -> diff --git a/lib/inets/test/inets_SUITE.erl b/lib/inets/test/inets_SUITE.erl index 5eaf3a28a0..38b8229389 100644 --- a/lib/inets/test/inets_SUITE.erl +++ b/lib/inets/test/inets_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2015. All Rights Reserved. +%% Copyright Ericsson AB 1997-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -212,11 +212,19 @@ start_httpd(Config) when is_list(Config) -> Pids0 = [ServicePid || {_, ServicePid} <- inets:services()], true = lists:member(Pid0, Pids0), [_|_] = inets:services_info(), - inets:stop(httpd, Pid0), ct:sleep(500), + Pids1 = [ServicePid || {_, ServicePid} <- inets:services()], + false = lists:member(Pid0, Pids1), + {ok, Pid0b} = + inets:start(httpd, [{port, 0}, {ipfamily, inet6fb4} | HttpdConf]), + Pids0b = [ServicePid || {_, ServicePid} <- inets:services()], + true = lists:member(Pid0b, Pids0b), + [_|_] = inets:services_info(), + inets:stop(httpd, Pid0b), + ct:sleep(500), Pids1 = [ServicePid || {_, ServicePid} <- inets:services()], - false = lists:member(Pid0, Pids1), + false = lists:member(Pid0b, Pids1), {ok, Pid1} = inets:start(httpd, [{port, 0}, {ipfamily, inet} | HttpdConf], stand_alone), diff --git a/lib/inets/test/inets_sup_SUITE.erl b/lib/inets/test/inets_sup_SUITE.erl index 5b8b1463c8..1e664337e6 100644 --- a/lib/inets/test/inets_sup_SUITE.erl +++ b/lib/inets/test/inets_sup_SUITE.erl @@ -33,7 +33,7 @@ suite() -> all() -> [default_tree, ftpc_worker, tftpd_worker, - httpd_subtree, httpd_subtree_profile, + httpd_config, httpd_subtree, httpd_subtree_profile, httpc_subtree]. groups() -> @@ -52,9 +52,32 @@ end_per_suite(_) -> inets:stop(), ok. -init_per_testcase(httpd_subtree, Config) -> +init_per_testcase(httpd_config = TC, Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + Dir = filename:join(PrivDir, TC), + ok = file:make_dir(Dir), + + FallbackConfig = [{port, 0}, + {server_name,"www.test"}, + {modules, [mod_get]}, + {server_root, Dir}, + {document_root, Dir}, + {bind_address, any}, + {ipfamily, inet6fb4}], + try + inets:stop(), + inets:start(), + inets:start(httpd, FallbackConfig), + Config + catch + _:Reason -> + inets:stop(), + exit({failed_starting_inets, Reason}) + end; + +init_per_testcase(httpd_subtree = TC, Config) -> PrivDir = proplists:get_value(priv_dir, Config), - Dir = filename:join(PrivDir, "root"), + Dir = filename:join(PrivDir, TC), ok = file:make_dir(Dir), SimpleConfig = [{port, 0}, @@ -75,9 +98,9 @@ init_per_testcase(httpd_subtree, Config) -> exit({failed_starting_inets, Reason}) end; -init_per_testcase(httpd_subtree_profile, Config) -> +init_per_testcase(httpd_subtree_profile = TC, Config) -> PrivDir = proplists:get_value(priv_dir, Config), - Dir = filename:join(PrivDir, "root"), + Dir = filename:join(PrivDir, TC), ok = file:make_dir(Dir), SimpleConfig = [{port, 0}, @@ -193,6 +216,11 @@ tftpd_worker(Config) when is_list(Config) -> [] = supervisor:which_children(tftp_sup), ok. +httpd_config() -> + [{doc, "Makes sure the httpd config works for inet6fb4."}]. +httpd_config(Config) when is_list(Config) -> + do_httpd_subtree(Config, default). + httpd_subtree() -> [{doc, "Makes sure the httpd sub tree is correct."}]. httpd_subtree(Config) when is_list(Config) -> diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 3408c3b128..747724a86b 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.3.1 +INETS_VSN = 6.3.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/jinterface/doc/src/Makefile b/lib/jinterface/doc/src/Makefile index cd1e61a795..508c8e01b5 100644 --- a/lib/jinterface/doc/src/Makefile +++ b/lib/jinterface/doc/src/Makefile @@ -166,7 +166,7 @@ release_docs_spec: docs $(INSTALL_DIR) "$(RELSYSDIR)/doc/html" $(INSTALL_DIR) "$(RELSYSDIR)/doc/html/java/$(JAVA_PKG_PATH)" $(INSTALL_DATA) $(INFO_FILE) "$(RELSYSDIR)" - (/bin/cp -rf ../html "$(RELSYSDIR)/doc") + ($(CP) -rf ../html "$(RELSYSDIR)/doc") # $(INSTALL_DATA) $(GIF_FILES) $(EXTRA_FILES) $(HTML_FILES) \ # "$(RELSYSDIR)/doc/html" diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index c0dce2f50c..8cc21bf3e2 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -151,6 +151,12 @@ fe80::204:acff:fe17:bf38 <name name="socket_address"/> </datatype> <datatype> + <name name="socket_getopt"/> + </datatype> + <datatype> + <name name="socket_setopt"/> + </datatype> + <datatype> <name name="returned_non_ip_address"/> <desc> <p> @@ -327,8 +333,6 @@ fe80::204:acff:fe17:bf38 <func> <name name="getopts" arity="2"/> <fsummary>Get one or more options for a socket.</fsummary> - <type name="socket_getopt"/> - <type name="socket_setopt"/> <desc> <p>Gets one or more options for a socket. For a list of available options, see @@ -580,7 +584,6 @@ get_tcpi_sacked(Sock) -> <func> <name name="setopts" arity="2"/> <fsummary>Set one or more options for a socket.</fsummary> - <type name="socket_setopt"/> <desc> <p>Sets one or more options for a socket.</p> <p>The following options are available:</p> diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml index 9e6fb60bb7..dc0291babe 100644 --- a/lib/kernel/doc/src/kernel_app.xml +++ b/lib/kernel/doc/src/kernel_app.xml @@ -215,12 +215,14 @@ <p>Defines the <c>First..Last</c> port range for the listener socket of a distributed Erlang node.</p> </item> + <marker id="inet_dist_listen_options"></marker> <tag><c>{inet_dist_listen_options, Opts}</c></tag> <item> <p>Defines a list of extra socket options to be used when opening the listening socket for a distributed Erlang node. See <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>.</p> </item> + <marker id="inet_dist_connect_options"></marker> <tag><c>{inet_dist_connect_options, Opts}</c></tag> <item> <p>Defines a list of extra socket options to be used when connecting to diff --git a/lib/kernel/doc/src/net_kernel.xml b/lib/kernel/doc/src/net_kernel.xml index f48a534d4f..3c1b3d5190 100644 --- a/lib/kernel/doc/src/net_kernel.xml +++ b/lib/kernel/doc/src/net_kernel.xml @@ -116,6 +116,21 @@ $ <input>erl -sname foobar</input></pre> </func> <func> + <name name="getopts" arity="2"/> + <fsummary>Get distribution socket options.</fsummary> + <desc> + <p>Get one or more options for the distribution socket + connected to <c><anno>Node</anno></c>.</p> + <p>If <c><anno>Node</anno></c> is a connected node + the return value is the same as from + <seealso marker="inet#getopts/2"><c>inet:getopts(Sock, Options)</c></seealso> + where <c>Sock</c> is the distribution socket for <c><anno>Node</anno></c>.</p> + <p>Returns <c>ignored</c> if the local node is not alive or + <c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not connected.</p> + </desc> + </func> + + <func> <name name="monitor_nodes" arity="1"/> <name name="monitor_nodes" arity="2"/> <fsummary>Subscribe to node status change messages.</fsummary> @@ -289,6 +304,27 @@ $ <input>erl -sname foobar</input></pre> </func> <func> + <name name="setopts" arity="2"/> + <fsummary>Set distribution socket options.</fsummary> + <desc> + <p>Set one or more options for distribution sockets. + Argument <c><anno>Node</anno></c> can be either one node name + or the atom <c>new</c> to affect the distribution sockets of all + future connected nodes.</p> + <p>The return value is the same as from + <seealso marker="inet#setopts/2"><c>inet:setopts/2</c></seealso> + or <c>{error, noconnection}</c> if <c><anno>Node</anno></c> is not + a connected node or <c>new</c>.</p> + <p>If <c><anno>Node</anno></c> is <c>new</c> the <c><anno>Options</anno></c> + will then also be added to kernel configration parameters + <seealso marker="kernel:kernel_app#inet_dist_listen_options">inet_dist_listen_options</seealso> + and + <seealso marker="kernel:kernel_app#inet_dist_connect_options">inet_dist_connect_options</seealso>.</p> + <p>Returns <c>ignored</c> if the local node is not alive.</p> + </desc> + </func> + + <func> <name>start([Name]) -> {ok, pid()} | {error, Reason}</name> <name>start([Name, NameType]) -> {ok, pid()} | {error, Reason}</name> <name>start([Name, NameType, Ticktime]) -> {ok, pid()} | {error, Reason}</name> diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml index f37433110c..3d35f6f57f 100644 --- a/lib/kernel/doc/src/notes.xml +++ b/lib/kernel/doc/src/notes.xml @@ -31,6 +31,41 @@ </header> <p>This document describes the changes made to the Kernel application.</p> +<section><title>Kernel 5.0.2</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + When calling os:cmd from a process that has set trap_exit + to true an 'EXIT' message would be left in the message + queue. This bug was introduced in kernel vsn 5.0.1.</p> + <p> + Own Id: OTP-13813</p> + </item> + </list> + </section> + +</section> + +<section><title>Kernel 5.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fix a os:cmd bug where creating a background job using + & would cause os:cmd to hang until the background job + terminated or closed its stdout and stderr file + descriptors. This bug has existed from kernel 5.0.</p> + <p> + Own Id: OTP-13741</p> + </item> + </list> + </section> + +</section> + <section><title>Kernel 5.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/kernel/include/dist_util.hrl b/lib/kernel/include/dist_util.hrl index 43e50d4325..320e916c04 100644 --- a/lib/kernel/include/dist_util.hrl +++ b/lib/kernel/include/dist_util.hrl @@ -63,7 +63,7 @@ f_getll, %% Get low level port or pid. f_address, %% The address of the "socket", %% generated from Socket,Node - %% These two are used in the tick loop, + %% These three are used in the tick loop, %% so they are not fun's to avoid holding old code. mf_tick, %% Takes the socket as parameters and %% sends a tick, this is no fun, it @@ -74,7 +74,11 @@ %% {ok, RecvCnt, SendCnt, SendPend} for %% a given socket. This is a {M,F}, %% returning {error, Reason on failure} - request_type = normal + request_type = normal, + + %% New in kernel-5.1 (OTP 19.1): + mf_setopts, %% netkernel:setopts on active connection + mf_getopts %% netkernel:getopts on active connection }). diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl index 6174136507..48541ec500 100644 --- a/lib/kernel/src/code_server.erl +++ b/lib/kernel/src/code_server.erl @@ -135,10 +135,14 @@ split_paths([], _S, Path, Paths) -> -spec call(term()) -> term(). call(Req) -> + Ref = erlang:monitor(process, ?MODULE), ?MODULE ! {code_call, self(), Req}, receive {?MODULE, Reply} -> - Reply + erlang:demonitor(Ref,[flush]), + Reply; + {'DOWN',Ref,process,_,_} -> + exit({'DOWN',code_server,Req}) end. reply(Pid, Res) -> @@ -933,14 +937,20 @@ del_ebin(Dir) -> filename:join(del_ebin_1(filename:split(Dir))). del_ebin_1([Parent,App,"ebin"]) -> - Ext = archive_extension(), - case filename:basename(Parent, Ext) of - Parent -> - %% Plain directory. + case filename:basename(Parent) of + [] -> + %% Parent is the root directory [Parent,App]; - Archive -> - %% Archive. - [Archive] + _ -> + Ext = archive_extension(), + case filename:basename(Parent, Ext) of + Parent -> + %% Plain directory. + [Parent,App]; + Archive -> + %% Archive. + [Archive] + end end; del_ebin_1([H|T]) -> [H|del_ebin_1(T)]; diff --git a/lib/kernel/src/dist_util.erl b/lib/kernel/src/dist_util.erl index 47d0c1b861..8d2fc4d4b7 100644 --- a/lib/kernel/src/dist_util.erl +++ b/lib/kernel/src/dist_util.erl @@ -143,7 +143,11 @@ handshake_other_started(#hs_data{request_type=ReqType}=HSData0) -> ChallengeB = recv_challenge_reply(HSData, ChallengeA, MyCookie), send_challenge_ack(HSData, gen_digest(ChallengeB, HisCookie)), ?debug({dist_util, self(), accept_connection, Node}), - connection(HSData). + connection(HSData); + +handshake_other_started(OldHsData) when element(1,OldHsData) =:= hs_data -> + handshake_other_started(convert_old_hsdata(OldHsData)). + %% %% check if connecting node is allowed to connect @@ -330,7 +334,20 @@ handshake_we_started(#hs_data{request_type=ReqType, gen_digest(ChallengeA,HisCookie)), reset_timer(NewHSData#hs_data.timer), recv_challenge_ack(NewHSData, MyChallenge, MyCookie), - connection(NewHSData). + connection(NewHSData); + +handshake_we_started(OldHsData) when element(1,OldHsData) =:= hs_data -> + handshake_we_started(convert_old_hsdata(OldHsData)). + +convert_old_hsdata({hs_data, KP, ON, TN, S, T, TF, A, OV, OF, OS, FS, FR, + FS_PRE, FS_POST, FG, FA, MFT, MFG, RT}) -> + #hs_data{ + kernel_pid = KP, other_node = ON, this_node = TN, socket = S, timer = T, + this_flags = TF, allowed = A, other_version = OV, other_flags = OF, + other_started = OS, f_send = FS, f_recv = FR, f_setopts_pre_nodeup = FS_PRE, + f_setopts_post_nodeup = FS_POST, f_getll = FG, f_address = FA, + mf_tick = MFT, mf_getstat = MFG, request_type = RT}. + %% -------------------------------------------------------------- %% The connection has been established. @@ -350,15 +367,15 @@ connection(#hs_data{other_node = Node, mark_nodeup(HSData,Address), case FPostNodeup(Socket) of ok -> - con_loop(HSData#hs_data.kernel_pid, - Node, - Socket, - Address, - HSData#hs_data.this_node, - PType, - #tick{}, - HSData#hs_data.mf_tick, - HSData#hs_data.mf_getstat); + con_loop({HSData#hs_data.kernel_pid, + Node, + Socket, + PType, + HSData#hs_data.mf_tick, + HSData#hs_data.mf_getstat, + HSData#hs_data.mf_setopts, + HSData#hs_data.mf_getopts}, + #tick{}); _ -> ?shutdown2(Node, connection_setup_failed) end; @@ -454,8 +471,8 @@ mark_nodeup(#hs_data{kernel_pid = Kernel, ?shutdown(Node) end. -con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, Type, Tick, MFTick, MFGetstat) -> +con_loop({Kernel, Node, Socket, Type, MFTick, MFGetstat, MFSetOpts, MFGetOpts}=ConData, + Tick) -> receive {tcp_closed, Socket} -> ?shutdown2(Node, connection_closed); @@ -468,15 +485,12 @@ con_loop(Kernel, Node, Socket, TcpAddress, _ -> ignore_it end, - con_loop(Kernel, Node, Socket, TcpAddress, MyNode, Type, - Tick, MFTick, MFGetstat); + con_loop(ConData, Tick); {Kernel, tick} -> case send_tick(Socket, Tick, Type, MFTick, MFGetstat) of {ok, NewTick} -> - con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, Type, NewTick, MFTick, - MFGetstat); + con_loop(ConData, NewTick); {error, not_responding} -> error_msg("** Node ~p not responding **~n" "** Removing (timedout) connection **~n", @@ -489,13 +503,24 @@ con_loop(Kernel, Node, Socket, TcpAddress, case MFGetstat(Socket) of {ok, Read, Write, _} -> From ! {self(), get_status, {ok, Read, Write}}, - con_loop(Kernel, Node, Socket, TcpAddress, - MyNode, - Type, Tick, - MFTick, MFGetstat); + con_loop(ConData, Tick); _ -> ?shutdown2(Node, get_status_failed) - end + end; + {From, Ref, {setopts, Opts}} -> + Ret = case MFSetOpts of + undefined -> {error, enotsup}; + _ -> MFSetOpts(Socket, Opts) + end, + From ! {Ref, Ret}, + con_loop(ConData, Tick); + {From, Ref, {getopts, Opts}} -> + Ret = case MFGetOpts of + undefined -> {error, enotsup}; + _ -> MFGetOpts(Socket, Opts) + end, + From ! {Ref, Ret}, + con_loop(ConData, Tick) end. diff --git a/lib/kernel/src/erl_epmd.erl b/lib/kernel/src/erl_epmd.erl index f8ef4a475d..7bc9e2ede3 100644 --- a/lib/kernel/src/erl_epmd.erl +++ b/lib/kernel/src/erl_epmd.erl @@ -103,6 +103,10 @@ names(EpmdAddr) -> register_node(Name, PortNo) -> register_node(Name, PortNo, inet). +register_node(Name, PortNo, inet_tcp) -> + register_node(Name, PortNo, inet); +register_node(Name, PortNo, inet6_tcp) -> + register_node(Name, PortNo, inet6); register_node(Name, PortNo, Family) -> gen_server:call(erl_epmd, {register, Name, PortNo, Family}, infinity). @@ -403,8 +407,6 @@ select_best_version(L1, _H1, _L2, H2) when L1 > H2 -> 0; select_best_version(_L1, H1, L2, _H2) when L2 > H1 -> 0; -select_best_version(_L1, H1, L2, _H2) when L2 > H1 -> - 0; select_best_version(_L1, H1, _L2, H2) -> erlang:min(H1, H2). diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index a91a6ed517..75dd800c6b 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -75,6 +75,7 @@ -export_type([address_family/0, hostent/0, hostname/0, ip4_address/0, ip6_address/0, ip_address/0, port_number/0, local_address/0, socket_address/0, returned_non_ip_address/0, + socket_setopt/0, socket_getopt/0, posix/0, socket/0, stat_option/0]). %% imports -import(lists, [append/1, duplicate/2, filter/2, foldl/3]). diff --git a/lib/kernel/src/inet6_tcp_dist.erl b/lib/kernel/src/inet6_tcp_dist.erl index 3aa61973af..9b6c2745d5 100644 --- a/lib/kernel/src/inet6_tcp_dist.erl +++ b/lib/kernel/src/inet6_tcp_dist.erl @@ -24,6 +24,7 @@ -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +-export([setopts/2, getopts/2]). %% ------------------------------------------------------------ %% Select this protocol based on node name @@ -72,3 +73,9 @@ close(Socket) -> is_node_name(Node) when is_atom(Node) -> inet_tcp_dist:is_node_name(Node). + +setopts(S, Opts) -> + inet_tcp_dist:setopts(S, Opts). + +getopts(S, Opts) -> + inet_tcp_dist:getopts(S, Opts). diff --git a/lib/kernel/src/inet_tcp_dist.erl b/lib/kernel/src/inet_tcp_dist.erl index f91d7ef7c3..3084bd599a 100644 --- a/lib/kernel/src/inet_tcp_dist.erl +++ b/lib/kernel/src/inet_tcp_dist.erl @@ -24,13 +24,16 @@ -export([listen/1, accept/1, accept_connection/5, setup/5, close/1, select/1, is_node_name/1]). +%% Optional +-export([setopts/2, getopts/2]). + %% Generalized dist API -export([gen_listen/2, gen_accept/2, gen_accept_connection/6, gen_setup/6, gen_select/2]). %% internal exports --export([accept_loop/3,do_accept/7,do_setup/7,getstat/1]). +-export([accept_loop/3,do_accept/7,do_setup/7,getstat/1,tick/2]). -import(error_logger,[error_msg/2]). @@ -74,7 +77,7 @@ gen_listen(Driver, Name) -> TcpAddress = get_tcp_address(Driver, Socket), {_,Port} = TcpAddress#net_address.address, ErlEpmd = net_kernel:epmd_module(), - case ErlEpmd:register_node(Name, Port) of + case ErlEpmd:register_node(Name, Port, Driver) of {ok, Creation} -> {ok, {Socket, TcpAddress, Creation}}; Error -> @@ -215,8 +218,10 @@ do_accept(Driver, Kernel, AcceptPid, Socket, MyNode, Allowed, SetupTime) -> inet:getll(S) end, f_address = fun(S, Node) -> get_remote_id(Driver, S, Node) end, - mf_tick = fun(S) -> tick(Driver, S) end, - mf_getstat = fun ?MODULE:getstat/1 + mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end, + mf_getstat = fun ?MODULE:getstat/1, + mf_setopts = fun ?MODULE:setopts/2, + mf_getopts = fun ?MODULE:getopts/2 }, dist_util:handshake_other_started(HSData); {false,IP} -> @@ -320,6 +325,7 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> {packet, 4}, nodelay()]) end, + f_getll = fun inet:getll/1, f_address = fun(_,_) -> @@ -329,9 +335,11 @@ do_setup(Driver, Kernel, Node, Type, MyNode, LongOrShortNames, SetupTime) -> protocol = tcp, family = AddressFamily} end, - mf_tick = fun(S) -> tick(Driver, S) end, + mf_tick = fun(S) -> ?MODULE:tick(Driver, S) end, mf_getstat = fun ?MODULE:getstat/1, - request_type = Type + request_type = Type, + mf_setopts = fun ?MODULE:setopts/2, + mf_getopts = fun ?MODULE:getopts/2 }, dist_util:handshake_we_started(HSData); _ -> @@ -492,3 +500,12 @@ split_stat([], R, W, P) -> {ok, R, W, P}. +setopts(S, Opts) -> + case [Opt || {K,_}=Opt <- Opts, + K =:= active orelse K =:= deliver orelse K =:= packet] of + [] -> inet:setopts(S,Opts); + Opts1 -> {error, {badopts,Opts1}} + end. + +getopts(S, Opts) -> + inet:getopts(S, Opts). diff --git a/lib/kernel/src/net_kernel.erl b/lib/kernel/src/net_kernel.erl index ac19f4935b..0c679e7349 100644 --- a/lib/kernel/src/net_kernel.erl +++ b/lib/kernel/src/net_kernel.erl @@ -59,6 +59,8 @@ connect_node/1, monitor_nodes/1, monitor_nodes/2, + setopts/2, + getopts/2, start/1, stop/0]). @@ -111,7 +113,7 @@ }). -record(listen, { - listen, %% listen pid + listen, %% listen socket accept, %% accepting pid address, %% #net_address module %% proto module @@ -384,7 +386,7 @@ init({Name, LongOrShortNames, TickT, CleanHalt}) -> connections = ets:new(sys_dist,[named_table, protected, - {keypos, 2}]), + {keypos, #connection.node}]), listen = Listeners, allowed = [], verbose = 0 @@ -554,6 +556,38 @@ handle_call({new_ticktime,_T,_TP}, #state{tick = #tick_change{time = T}} = State) -> async_reply({reply, {ongoing_change_to, T}, State}, From); +handle_call({setopts, new, Opts}, From, State) -> + Ret = setopts_new(Opts, State), + async_reply({reply, Ret, State}, From); + +handle_call({setopts, Node, Opts}, From, State) -> + Return = + case ets:lookup(sys_dist, Node) of + [Conn] when Conn#connection.state =:= up -> + case call_owner(Conn#connection.owner, {setopts, Opts}) of + {ok, Ret} -> Ret; + _ -> {error, noconnection} + end; + + _ -> + {error, noconnection} + end, + async_reply({reply, Return, State}, From); + +handle_call({getopts, Node, Opts}, From, State) -> + Return = + case ets:lookup(sys_dist, Node) of + [Conn] when Conn#connection.state =:= up -> + case call_owner(Conn#connection.owner, {getopts, Opts}) of + {ok, Ret} -> Ret; + _ -> {error, noconnection} + end; + + _ -> + {error, noconnection} + end, + async_reply({reply, Return, State}, From); + handle_call(_Msg, _From, State) -> {noreply, State}. @@ -1608,3 +1642,93 @@ async_gen_server_reply(From, Msg) -> {'EXIT', _} -> ok end. + +call_owner(Owner, Msg) -> + Mref = monitor(process, Owner), + Owner ! {self(), Mref, Msg}, + receive + {Mref, Reply} -> + erlang:demonitor(Mref, [flush]), + {ok, Reply}; + {'DOWN', Mref, _, _, _} -> + error + end. + + +-spec setopts(Node, Options) -> ok | {error, Reason} | ignored when + Node :: node() | new, + Options :: [inet:socket_setopt()], + Reason :: inet:posix() | noconnection. + +setopts(Node, Opts) when is_atom(Node), is_list(Opts) -> + request({setopts, Node, Opts}). + +setopts_new(Opts, State) -> + %% First try setopts on listening socket(s) + %% Bail out on failure. + %% If successful, we are pretty sure Opts are ok + %% and we continue with config params and pending connections. + case setopts_on_listen(Opts, State#state.listen) of + ok -> + setopts_new_1(Opts); + Fail -> Fail + end. + +setopts_on_listen(_, []) -> ok; +setopts_on_listen(Opts, [#listen {listen = LSocket, module = Mod} | T]) -> + try Mod:setopts(LSocket, Opts) of + ok -> + setopts_on_listen(Opts, T); + Fail -> Fail + catch + error:undef -> {error, enotsup} + end. + +setopts_new_1(Opts) -> + ConnectOpts = case application:get_env(kernel, inet_dist_connect_options) of + {ok, CO} -> CO; + _ -> [] + end, + application:set_env(kernel, inet_dist_connect_options, + merge_opts(Opts,ConnectOpts)), + ListenOpts = case application:get_env(kernel, inet_dist_listen_options) of + {ok, LO} -> LO; + _ -> [] + end, + application:set_env(kernel, inet_dist_listen_options, + merge_opts(Opts, ListenOpts)), + case lists:keyfind(nodelay, 1, Opts) of + {nodelay, ND} when is_boolean(ND) -> + application:set_env(kernel, dist_nodelay, ND); + _ -> ignore + end, + + %% Update any pending connections + PendingConns = ets:select(sys_dist, [{'_', + [{'=/=',{element,#connection.state,'$_'},up}], + ['$_']}]), + lists:foreach(fun(#connection{state = pending, owner = Owner}) -> + call_owner(Owner, {setopts, Opts}); + (#connection{state = up_pending, pending_owner = Owner}) -> + call_owner(Owner, {setopts, Opts}); + (_) -> ignore + end, PendingConns), + ok. + +merge_opts([], B) -> + B; +merge_opts([H|T], B0) -> + {Key, _} = H, + B1 = lists:filter(fun({K,_}) -> K =/= Key end, B0), + merge_opts(T, [H | B1]). + +-spec getopts(Node, Options) -> + {'ok', OptionValues} | {'error', Reason} | ignored when + Node :: node(), + Options :: [inet:socket_getopt()], + OptionValues :: [inet:socket_setopt()], + Reason :: inet:posix() | noconnection. + +getopts(Node, Opts) when is_atom(Node), is_list(Opts) -> + request({getopts, Node, Opts}). + diff --git a/lib/kernel/src/os.erl b/lib/kernel/src/os.erl index f0ad26b1f2..05bbf1069e 100644 --- a/lib/kernel/src/os.erl +++ b/lib/kernel/src/os.erl @@ -226,11 +226,13 @@ extensions() -> Command :: atom() | io_lib:chars(). cmd(Cmd) -> validate(Cmd), - {SpawnCmd, SpawnOpts, SpawnInput} = mk_cmd(os:type(), Cmd), + {SpawnCmd, SpawnOpts, SpawnInput, Eot} = mk_cmd(os:type(), Cmd), Port = open_port({spawn, SpawnCmd}, [binary, stderr_to_stdout, - stream, in, eof, hide | SpawnOpts]), + stream, in, hide | SpawnOpts]), + MonRef = erlang:monitor(port, Port), true = port_command(Port, SpawnInput), - Bytes = get_data(Port, []), + Bytes = get_data(Port, MonRef, Eot, []), + demonitor(MonRef, [flush]), String = unicode:characters_to_list(Bytes), if %% Convert to unicode list if possible otherwise return bytes is_list(String) -> String; @@ -243,7 +245,7 @@ mk_cmd({win32,Wtype}, Cmd) -> {false,_} -> lists:concat(["cmd /c", Cmd]); {Cspec,_} -> lists:concat([Cspec," /c",Cmd]) end, - {Command, [], []}; + {Command, [], [], <<>>}; mk_cmd(OsType,Cmd) when is_atom(Cmd) -> mk_cmd(OsType, atom_to_list(Cmd)); mk_cmd(_,Cmd) -> @@ -252,7 +254,8 @@ mk_cmd(_,Cmd) -> {"/bin/sh -s unix:cmd", [out], %% We insert a new line after the command, in case the command %% contains a comment character. - ["(", unicode:characters_to_binary(Cmd), "\n); exit\n"]}. + ["(", unicode:characters_to_binary(Cmd), "\n); echo \"\^D\"\n"], + <<$\^D>>}. validate(Atom) when is_atom(Atom) -> ok; @@ -267,21 +270,44 @@ validate1([List|Rest]) when is_list(List) -> validate1([]) -> ok. -get_data(Port, Sofar) -> +get_data(Port, MonRef, Eot, Sofar) -> receive {Port, {data, Bytes}} -> - get_data(Port, [Sofar,Bytes]); - {Port, eof} -> - Port ! {self(), close}, - receive - {Port, closed} -> - true - end, - receive - {'EXIT', Port, _} -> - ok - after 1 -> % force context switch - ok - end, + case eot(Bytes, Eot) of + more -> + get_data(Port, MonRef, Eot, [Sofar,Bytes]); + Last -> + Port ! {self(), close}, + flush_until_closed(Port), + flush_exit(Port), + iolist_to_binary([Sofar, Last]) + end; + {'DOWN', MonRef, _, _ , _} -> + flush_exit(Port), iolist_to_binary(Sofar) end. + +eot(_Bs, <<>>) -> + more; +eot(Bs, Eot) -> + case binary:match(Bs, Eot) of + nomatch -> more; + {Pos, _} -> + binary:part(Bs,{0, Pos}) + end. + +flush_until_closed(Port) -> + receive + {Port, {data, _Bytes}} -> + flush_until_closed(Port); + {Port, closed} -> + true + end. + +flush_exit(Port) -> + receive + {'EXIT', Port, _} -> + ok + after 1 -> % force context switch + ok + end. diff --git a/lib/kernel/test/erl_distribution_SUITE.erl b/lib/kernel/test/erl_distribution_SUITE.erl index eb58e92224..e43be77428 100644 --- a/lib/kernel/test/erl_distribution_SUITE.erl +++ b/lib/kernel/test/erl_distribution_SUITE.erl @@ -25,6 +25,7 @@ init_per_group/2,end_per_group/2]). -export([tick/1, tick_change/1, illegal_nodenames/1, hidden_node/1, + setopts/1, table_waste/1, net_setuptime/1, inet_dist_options_options/1, @@ -42,6 +43,8 @@ -export([get_socket_priorities/0, tick_cli_test/1, tick_cli_test1/1, tick_serv_test/2, tick_serv_test1/1, + run_remote_test/1, + setopts_do/2, keep_conn/1, time_ping/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -66,6 +69,7 @@ suite() -> all() -> [tick, tick_change, illegal_nodenames, hidden_node, + setopts, table_waste, net_setuptime, inet_dist_options_options, {group, monitor_nodes}]. @@ -282,6 +286,165 @@ tick_cli_test1(Node) -> end end. +setopts(Config) when is_list(Config) -> + register(setopts_regname, self()), + [N1,N2,N3,N4] = get_nodenames(4, setopts), + + {_N1F,Port1} = start_node_unconnected(N1, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), "1", "ping"]), + 0 = wait_for_port_exit(Port1), + + {_N2F,Port2} = start_node_unconnected(N2, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), "2", "ping"]), + 0 = wait_for_port_exit(Port2), + + {ok, LSock} = gen_tcp:listen(0, [{packet,2}, {active,false}]), + {ok, LTcpPort} = inet:port(LSock), + + {N3F,Port3} = start_node_unconnected(N3, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), + "1", integer_to_list(LTcpPort)]), + wait_and_connect(LSock, N3F, Port3), + 0 = wait_for_port_exit(Port3), + + {N4F,Port4} = start_node_unconnected(N4, ?MODULE, run_remote_test, + ["setopts_do", atom_to_list(node()), + "2", integer_to_list(LTcpPort)]), + wait_and_connect(LSock, N4F, Port4), + 0 = wait_for_port_exit(Port4), + + ok. + +wait_and_connect(LSock, NodeName, NodePort) -> + {ok, Sock} = gen_tcp:accept(LSock), + {ok, "Connect please"} = gen_tcp:recv(Sock, 0), + flush_from_port(NodePort), + pong = net_adm:ping(NodeName), + gen_tcp:send(Sock, "Connect done"), + gen_tcp:close(Sock). + + +flush_from_port(Port) -> + flush_from_port(Port, 10). + +flush_from_port(Port, Timeout) -> + receive + {Port,{data,String}} -> + io:format("~p: ~s\n", [Port, String]), + flush_from_port(Port, Timeout) + after Timeout -> + timeout + end. + +wait_for_port_exit(Port) -> + case (receive M -> M end) of + {Port,{exit_status,Status}} -> + Status; + {Port,{data,String}} -> + io:format("~p: ~s\n", [Port, String]), + wait_for_port_exit(Port) + end. + +run_remote_test([FuncStr, TestNodeStr | Args]) -> + Status = try + io:format("Node ~p started~n", [node()]), + TestNode = list_to_atom(TestNodeStr), + io:format("Node ~p spawning function ~p~n", [node(), FuncStr]), + {Pid,Ref} = spawn_monitor(?MODULE, list_to_atom(FuncStr), [TestNode, Args]), + io:format("Node ~p waiting for function ~p~n", [node(), FuncStr]), + receive + {'DOWN', Ref, process, Pid, normal} -> + 0; + Other -> + io:format("Node ~p got unexpected msg: ~p\n",[node(), Other]), + 1 + end + catch + C:E -> + io:format("Node ~p got EXCEPTION ~p:~p\nat ~p\n", + [node(), C, E, erlang:get_stacktrace()]), + 2 + end, + io:format("Node ~p doing halt(~p).\n",[node(), Status]), + erlang:halt(Status). + +% Do the actual test on the remote node +setopts_do(TestNode, [OptNr, ConnectData]) -> + [] = nodes(), + {Opt, Val} = opt_from_nr(OptNr), + ok = net_kernel:setopts(new, [{Opt, Val}]), + + [] = nodes(), + {error, noconnection} = net_kernel:getopts(TestNode, [Opt]), + + case ConnectData of + "ping" -> % We connect + net_adm:ping(TestNode); + TcpPort -> % Other connect + {ok, Sock} = gen_tcp:connect("localhost", list_to_integer(TcpPort), + [{active,false},{packet,2}]), + ok = gen_tcp:send(Sock, "Connect please"), + {ok, "Connect done"} = gen_tcp:recv(Sock, 0), + gen_tcp:close(Sock) + end, + [TestNode] = nodes(), + {ok, [{Opt,Val}]} = net_kernel:getopts(TestNode, [Opt]), + {error, noconnection} = net_kernel:getopts('pixie@fairyland', [Opt]), + + NewVal = change_val(Val), + ok = net_kernel:setopts(TestNode, [{Opt, NewVal}]), + {ok, [{Opt,NewVal}]} = net_kernel:getopts(TestNode, [Opt]), + + ok = net_kernel:setopts(TestNode, [{Opt, Val}]), + {ok, [{Opt,Val}]} = net_kernel:getopts(TestNode, [Opt]), + + ok. + +opt_from_nr("1") -> {nodelay, true}; +opt_from_nr("2") -> {nodelay, false}. + +change_val(true) -> false; +change_val(false) -> true. + +start_node_unconnected(Name, Mod, Func, Args) -> + FullName = full_node_name(Name), + CmdLine = mk_node_cmdline(Name,Mod,Func,Args), + io:format("Starting node ~p: ~s~n", [FullName, CmdLine]), + case open_port({spawn, CmdLine}, [exit_status]) of + Port when is_port(Port) -> + {FullName, Port}; + Error -> + exit({failed_to_start_node, FullName, Error}) + end. + +full_node_name(PreName) -> + HostSuffix = lists:dropwhile(fun ($@) -> false; (_) -> true end, + atom_to_list(node())), + list_to_atom(atom_to_list(PreName) ++ HostSuffix). + +mk_node_cmdline(Name,Mod,Func,Args) -> + Static = "-noinput", + Pa = filename:dirname(code:which(?MODULE)), + Prog = case catch init:get_argument(progname) of + {ok,[[P]]} -> P; + _ -> exit(no_progname_argument_found) + end, + NameSw = case net_kernel:longnames() of + false -> "-sname "; + true -> "-name "; + _ -> exit(not_distributed_node) + end, + {ok, Pwd} = file:get_cwd(), + NameStr = atom_to_list(Name), + Prog ++ " " + ++ Static ++ " " + ++ NameSw ++ " " ++ NameStr + ++ " -pa " ++ Pa + ++ " -env ERL_CRASH_DUMP " ++ Pwd ++ "/erl_crash_dump." ++ NameStr + ++ " -setcookie " ++ atom_to_list(erlang:get_cookie()) + ++ " -run " ++ atom_to_list(Mod) ++ " " ++ atom_to_list(Func) + ++ " " ++ string:join(Args, " "). + %% OTP-4255. tick_change(Config) when is_list(Config) -> diff --git a/lib/kernel/test/gen_sctp_SUITE.erl b/lib/kernel/test/gen_sctp_SUITE.erl index f836b2aa94..620ab235a0 100644 --- a/lib/kernel/test/gen_sctp_SUITE.erl +++ b/lib/kernel/test/gen_sctp_SUITE.erl @@ -117,7 +117,11 @@ xfer_min(Config) when is_list(Config) -> Stream = 0, Data = <<"The quick brown fox jumps over a lazy dog 0123456789">>, Loopback = {127,0,0,1}, + StatOpts = + [recv_avg,recv_cnt,recv_max,recv_oct, + send_avg,send_cnt,send_max,send_oct], {ok,Sb} = gen_sctp:open([{type,seqpacket}]), + {ok,SbStat1} = inet:getstat(Sb, StatOpts), {ok,Pb} = inet:port(Sb), ok = gen_sctp:listen(Sb, true), @@ -212,6 +216,8 @@ xfer_min(Config) when is_list(Config) -> assoc_id=SbAssocId}} = recv_event(log_ok(gen_sctp:recv(Sb, infinity))), ok = gen_sctp:close(Sa), + {ok,SbStat2} = inet:getstat(Sb, StatOpts), + [] = filter_stat_eq(SbStat1, SbStat2), ok = gen_sctp:close(Sb), receive @@ -220,6 +226,18 @@ xfer_min(Config) when is_list(Config) -> end, ok. +filter_stat_eq([], []) -> + []; +filter_stat_eq([{Tag,Val1}=Stat|SbStat1], [{Tag,Val2}|SbStat2]) -> + if + Val1 == Val2 -> + [Stat|filter_stat_eq(SbStat1, SbStat2)]; + true -> + filter_stat_eq(SbStat1, SbStat2) + end. + + + %% Minimal data transfer in active mode. xfer_active(Config) when is_list(Config) -> Timeout = 2000, @@ -383,26 +401,28 @@ def_sndrcvinfo(Config) when is_list(Config) -> assoc_id=S2AssocId} = S2AssocChange = log_ok(gen_sctp:connect(S2, Loopback, P1, [])), ?LOGVAR(S2AssocChange), - case recv_event(log_ok(gen_sctp:recv(S1))) of - {Loopback,P2, - #sctp_assoc_change{ - state=comm_up, - error=0, - assoc_id=S1AssocId}} -> - ?LOGVAR(S1AssocId); - {Loopback,P2, - #sctp_paddr_change{ - state=addr_confirmed, - error=0, - assoc_id=S1AssocId}} -> - ?LOGVAR(S1AssocId), + S1AssocId = + case recv_event(log_ok(gen_sctp:recv(S1))) of {Loopback,P2, #sctp_assoc_change{ state=comm_up, error=0, - assoc_id=S1AssocId}} = - recv_event(log_ok(gen_sctp:recv(S1))) - end, + assoc_id=AssocId}} -> + AssocId; + {Loopback,P2, + #sctp_paddr_change{ + state=addr_confirmed, + error=0, + assoc_id=AssocId}} -> + {Loopback,P2, + #sctp_assoc_change{ + state=comm_up, + error=0, + assoc_id=AssocId}} = + recv_event(log_ok(gen_sctp:recv(S1))), + AssocId + end, + ?LOGVAR(S1AssocId), #sctp_sndrcvinfo{ ppid=17, context=0, timetolive=0} = %, assoc_id=S1AssocId} = @@ -1055,6 +1075,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> Addr = {127,0,0,1}, Stream = 0, Timeout = 333, + StartTime = timestamp(), S1 = socket_open([{ifaddr,Addr}|SockOpts], Timeout), ?LOGVAR(S1), P1 = socket_call(S1, get_port), @@ -1077,7 +1098,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> state=comm_up, assoc_id=AssocId2}}} -> AssocId2 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, ?LOGVAR(S2Ai), S1Ai = @@ -1087,7 +1108,7 @@ peeloff(Config, SockOpts) when is_list(Config) -> state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, ?LOGVAR(S1Ai), %% @@ -1095,13 +1116,13 @@ peeloff(Config, SockOpts) when is_list(Config) -> receive {S1,{Addr,P2,S1Ai,Stream,<<"Number one">>}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, socket_call(S2, {send,Socket1,S1Ai,Stream,<<"Number two">>}), receive {S2,{Addr,P1,S2Ai,Stream,<<"Number two">>}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% S3 = socket_peeloff(Socket1, S1Ai, SockOpts, Timeout), @@ -1120,31 +1141,31 @@ peeloff(Config, SockOpts) when is_list(Config) -> receive {S2,{Addr,P3,S2Ai,Stream,<<"Number three">>}} -> ok after Timeout -> - socket_bailout([S1,S2,S3]) + socket_bailout([S1,S2,S3], StartTime) end, socket_call(S3, {send,Socket2,S2Ai,Stream,<<"Number four">>}), receive {S3,{Addr,P2,S3Ai,Stream,<<"Number four">>}} -> ok after Timeout -> - socket_bailout([S1,S2,S3]) + socket_bailout([S1,S2,S3], StartTime) end, %% inet:i(sctp), - socket_close_verbose(S1), - socket_close_verbose(S2), + socket_close_verbose(S1, StartTime), + socket_close_verbose(S2, StartTime), receive {S3,{Addr,P2,#sctp_shutdown_event{assoc_id=S3Ai_X}}} -> match_unless_solaris(S3Ai, S3Ai_X) after Timeout -> - socket_bailout([S3]) + socket_bailout([S3], StartTime) end, receive {S3,{Addr,P2,#sctp_assoc_change{state=shutdown_comp, assoc_id=S3Ai}}} -> ok after Timeout -> - socket_bailout([S3]) + socket_bailout([S3], StartTime) end, - socket_close_verbose(S3), + socket_close_verbose(S3, StartTime), [] = flush(), ok. @@ -1156,6 +1177,7 @@ buffers(Config) when is_list(Config) -> Addr = {127,0,0,1}, Stream = 1, Timeout = 3333, + StartTime = timestamp(), S1 = socket_open([{ip,Addr}], Timeout), ?LOGVAR(S1), P1 = socket_call(S1, get_port), @@ -1174,7 +1196,7 @@ buffers(Config) when is_list(Config) -> state=comm_up, assoc_id=AssocId2}}} -> AssocId2 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, S1Ai = receive @@ -1183,7 +1205,7 @@ buffers(Config) when is_list(Config) -> state=comm_up, assoc_id=AssocId1}}} -> AssocId1 after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% socket_call(S1, {setopts,[{recbuf,Limit}]}), @@ -1197,22 +1219,22 @@ buffers(Config) when is_list(Config) -> receive {S1,{Addr,P2,S1Ai,Stream,Data}} -> ok after Timeout -> - socket_bailout([S1,S2]) + socket_bailout([S1,S2], StartTime) end, %% - socket_close_verbose(S1), + socket_close_verbose(S1, StartTime), receive {S2,{Addr,P1,#sctp_shutdown_event{assoc_id=S2Ai}}} -> ok after Timeout -> - socket_bailout([S2]) + socket_bailout([S2], StartTime) end, receive {S2,{Addr,P1,#sctp_assoc_change{state=shutdown_comp, assoc_id=S2Ai}}} -> ok after Timeout -> - socket_bailout([S2]) + socket_bailout([S2], StartTime) end, - socket_close_verbose(S2), + socket_close_verbose(S2, StartTime), [] = flush(), ok. @@ -1521,8 +1543,8 @@ socket_peeloff(Socket, AssocId, SocketOpts, Timeout) -> end, s_start(Starter, Timeout). -socket_close_verbose(S) -> - History = socket_history(socket_close(S)), +socket_close_verbose(S, StartTime) -> + History = socket_history(socket_close(S), StartTime), io:format("socket_close ~p:~n ~p.~n", [S,History]), History. @@ -1535,19 +1557,19 @@ socket_call(S, Request) -> %% socket_get(S, Key) -> %% s_req(S, {get,Key}). -socket_bailout([S|Ss]) -> - History = socket_history(socket_close(S)), +socket_bailout([S|Ss], StartTime) -> + History = socket_history(socket_close(S), StartTime), io:format("bailout ~p:~n ~p.~n", [S,History]), - socket_bailout(Ss); -socket_bailout([]) -> + socket_bailout(Ss, StartTime); +socket_bailout([], _) -> io:format("flush: ~p.~n", [flush()]), ct:fail(socket_bailout). -socket_history({State,Flush}) -> +socket_history({State,Flush}, StartTime) -> {lists:keysort( 2, lists:flatten( - [[{Key,Val} || Val <- Vals] + [[{Key,{TS-StartTime,Val}} || {TS,Val} <- Vals] || {Key,Vals} <- gb_trees:to_list(State)])), Flush}. @@ -1610,14 +1632,12 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> {Parent,Ref,exit} -> ok = gen_sctp:close(Socket), Key = exit, - Val = {now(),Socket}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, Socket, State), Parent ! {self(),Ref,{NewState,flush()}}; {Parent,Ref,{Msg}} -> Result = Handler(Msg), Key = req, - Val = {now(),{Msg,Result}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Msg,Result}, State), Parent ! {self(),Ref,Result}, s_loop(Socket, Timeout, Parent, Handler, NewState); %% {Parent,Ref,{get,Key}} -> @@ -1627,16 +1647,15 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> {[#sctp_sndrcvinfo{stream=Stream,assoc_id=AssocId}=SRI],Data}} when not is_tuple(Data) -> case gb_get({assoc_change,AssocId}, State) of - [{_,{Addr,Port, - #sctp_assoc_change{ - state=comm_up, - inbound_streams=Is}}}|_] + [{Addr,Port, + #sctp_assoc_change{ + state=comm_up, + inbound_streams=Is}}|_] when 0 =< Stream, Stream < Is-> ok; [] -> ok end, Key = {msg,AssocId,Stream}, - Val = {now(),{Addr,Port,SRI,Data}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SRI,Data}, State), Parent ! {self(),{Addr,Port,AssocId,Stream,Data}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1647,13 +1666,12 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, Key = {assoc_change,AssocId}, - Val = {now(),{Addr,Port,SAC}}, case {gb_get(Key, State),St} of {[],_} -> ok; - {[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_} + {[{Addr,Port,#sctp_assoc_change{state=comm_up}}|_],_} when St =:= comm_lost; St =:= shutdown_comp -> ok end, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SAC}, State), Parent ! {self(),{Addr,Port,SAC}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1667,14 +1685,13 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, case {gb_get({assoc_change,AssocId}, State),St} of - {[{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_],_} + {[{Addr,Port,#sctp_assoc_change{state=comm_up}}|_],_} when St =:= addr_available; St =:= addr_confirmed -> ok; {[],addr_confirmed} -> ok end, Key = {paddr_change,AssocId}, - Val = {now(),{Addr,Port,SPC}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port,SPC}, State), again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); {sctp,Socket,Addr,Port, @@ -1684,12 +1701,11 @@ s_loop(Socket, Timeout, Parent, Handler, State) -> [] -> ok end, case gb_get({assoc_change,AssocId}, State) of - [{_,{Addr,Port,#sctp_assoc_change{state=comm_up}}}|_] -> ok; + [{Addr,Port,#sctp_assoc_change{state=comm_up}}|_] -> ok; [] -> ok end, Key = {shutdown_event,AssocId}, - Val = {now(),{Addr,Port}}, - NewState = gb_push(Key, Val, State), + NewState = gb_push(Key, {Addr,Port}, State), Parent ! {self(), {Addr,Port,SSE}}, again(Socket), s_loop(Socket, Timeout, Parent, Handler, NewState); @@ -1707,11 +1723,12 @@ again(Socket) -> end. gb_push(Key, Val, GBT) -> + TS = timestamp(), case gb_trees:lookup(Key, GBT) of none -> - gb_trees:insert(Key, [Val], GBT); + gb_trees:insert(Key, [{TS,Val}], GBT); {value,V} -> - gb_trees:update(Key, [Val|V], GBT) + gb_trees:update(Key, [{TS,Val}|V], GBT) end. gb_get(Key, GBT) -> @@ -1719,7 +1736,7 @@ gb_get(Key, GBT) -> none -> []; {value,V} -> - V + [Val || {_TS,Val} <- V] end. match_unless_solaris(A, B) -> @@ -1727,3 +1744,6 @@ match_unless_solaris(A, B) -> {unix,sunos} -> B; _ -> A = B end. + +timestamp() -> + erlang:monotonic_time(). diff --git a/lib/kernel/test/os_SUITE.erl b/lib/kernel/test/os_SUITE.erl index 2a1e5016ec..19ab3713a1 100644 --- a/lib/kernel/test/os_SUITE.erl +++ b/lib/kernel/test/os_SUITE.erl @@ -24,7 +24,8 @@ init_per_testcase/2,end_per_testcase/2]). -export([space_in_cwd/1, quoting/1, cmd_unicode/1, space_in_name/1, bad_command/1, find_executable/1, unix_comment_in_command/1, deep_list_command/1, - large_output_command/1, perf_counter_api/1]). + large_output_command/1, background_command/0, background_command/1, + message_leak/1, perf_counter_api/1]). -include_lib("common_test/include/ct.hrl"). @@ -35,7 +36,8 @@ suite() -> all() -> [space_in_cwd, quoting, cmd_unicode, space_in_name, bad_command, find_executable, unix_comment_in_command, deep_list_command, - large_output_command, perf_counter_api]. + large_output_command, background_command, message_leak, + perf_counter_api]. groups() -> []. @@ -52,6 +54,13 @@ init_per_group(_GroupName, Config) -> end_per_group(_GroupName, Config) -> Config. +init_per_testcase(background_command, Config) -> + case os:type() of + {win32, _} -> + {skip,"Should not work on windows"}; + _ -> + Config + end; init_per_testcase(_TC,Config) -> Config. @@ -261,13 +270,38 @@ deep_list_command(Config) when is_list(Config) -> %% FYI: [$e, $c, "ho"] =:= io_lib:format("ec~s", ["ho"]) ok. -%% Test to take sure that the correct data is +%% Test to make sure that the correct data is %% received when doing large commands. large_output_command(Config) when is_list(Config) -> %% Maximum allowed on windows is 8192, so we test well below that AAA = lists:duplicate(7000, $a), comp(AAA,os:cmd("echo " ++ AAA)). +%% Test that it is possible on unix to start a background task using os:cmd. +background_command() -> + [{timetrap, {seconds, 5}}]. +background_command(_Config) -> + %% This testcase fails when the os:cmd takes + %% longer then the 5 second timeout + os:cmd("sleep 10&"). + +%% Test that message does not leak to the calling process +message_leak(_Config) -> + process_flag(trap_exit, true), + + os:cmd("echo hello"), + [] = receive_all(), + + case os:type() of + {unix, _} -> + os:cmd("while true; do echo hello; done&"), + [] = receive_all(); + _ -> + ok % Cannot background on non-unix + end, + + process_flag(trap_exit, false). + %% Test that the os:perf_counter api works as expected perf_counter_api(_Config) -> diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk index e7d422d03c..cdd200a234 100644 --- a/lib/kernel/vsn.mk +++ b/lib/kernel/vsn.mk @@ -1 +1 @@ -KERNEL_VSN = 5.0 +KERNEL_VSN = 5.0.2 diff --git a/lib/odbc/configure.in b/lib/odbc/configure.in index cb2b23d534..d26daa5eda 100644 --- a/lib/odbc/configure.in +++ b/lib/odbc/configure.in @@ -147,7 +147,7 @@ AC_SUBST(THR_LIBS) odbc_lib_link_success=no AC_SUBST(TARGET_FLAGS) case $host_os in - darwin1[[0-4]].*|darwin[[0-9]].*) + darwin1[[0-5]].*|darwin[[0-9]].*) TARGET_FLAGS="-DUNIX" if test ! -d "$with_odbc" || test "$with_odbc" = "yes"; then ODBC_LIB= -L"/usr/lib" diff --git a/lib/odbc/test/odbc_connect_SUITE.erl b/lib/odbc/test/odbc_connect_SUITE.erl index 5727c1ca50..261dfc6f20 100644 --- a/lib/odbc/test/odbc_connect_SUITE.erl +++ b/lib/odbc/test/odbc_connect_SUITE.erl @@ -89,6 +89,7 @@ init_per_suite(Config) when is_list(Config) -> [{auto_commit, off}] ++ odbc_test_lib:platform_options()) of {ok, Ref} -> odbc:disconnect(Ref), + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()} | Config]; _ -> {skip, "ODBC is not properly setup"} @@ -129,11 +130,8 @@ init_per_testcase(_TestCase, Config) -> init_per_testcase_common(Config). init_per_testcase_common(Config) -> - test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), - Dog = test_server:timetrap(?default_timeout), - Temp = lists:keydelete(connection_ref, 1, Config), - NewConfig = lists:keydelete(watchdog, 1, Temp), - [{watchdog, Dog} | NewConfig]. + ct:pal("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), + lists:keydelete(connection_ref, 1, Config). %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ @@ -153,25 +151,22 @@ end_per_testcase(_TestCase, Config) -> end_per_testcase_common(Config). end_per_testcase_common(Config) -> - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), Result = odbc:sql_query(Ref, "DROP TABLE " ++ Table), io:format("Drop table: ~p ~p~n", [Table, Result]), - odbc:disconnect(Ref), - Dog = ?config(watchdog, Config), - test_server:timetrap_cancel(Dog). + odbc:disconnect(Ref). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -commit(doc)-> - ["Test the use of explicit commit"]; -commit(suite) -> []; +commit()-> + [{doc,"Test the use of explicit commit"}]. commit(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -205,14 +200,13 @@ commit(Config) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -rollback(doc)-> - ["Test the use of explicit rollback"]; -rollback(suite) -> []; +rollback()-> + [{doc,"Test the use of explicit rollback"}]. rollback(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), @@ -245,9 +239,8 @@ rollback(Config) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -not_explicit_commit(doc) -> - ["Test what happens if you try using commit on a auto_commit connection."]; -not_explicit_commit(suite) -> []; +not_explicit_commit() -> + [{doc,"Test what happens if you try using commit on a auto_commit connection."}]. not_explicit_commit(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, on}] ++ @@ -256,19 +249,17 @@ not_explicit_commit(_Config) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -not_exist_db(doc) -> - ["Tests valid data format but invalid data in the connection parameters."]; -not_exist_db(suite) -> []; +not_exist_db() -> + [{doc,"Tests valid data format but invalid data in the connection parameters."}]. not_exist_db(_Config) -> {error, _} = odbc:connect("DSN=foo;UID=bar;PWD=foobar", odbc_test_lib:platform_options()), %% So that the odbc control server can be stoped "in the correct way" - test_server:sleep(100). + ct:sleep(100). %%------------------------------------------------------------------------- -no_c_executable(doc) -> - "Test what happens if the port-program can not be found"; -no_c_executable(suite) -> []; +no_c_executable() -> + [{doc,"Test what happens if the port-program can not be found"}]. no_c_executable(_Config) -> process_flag(trap_exit, true), Dir = filename:nativename(filename:join(code:priv_dir(odbc), @@ -293,9 +284,8 @@ no_c_executable(_Config) -> end. %%------------------------------------------------------------------------ -port_dies(doc) -> - "Tests what happens if the port program dies"; -port_dies(suite) -> []; +port_dies() -> + [{doc,"Tests what happens if the port program dies"}]. port_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {status, _} = process_info(Ref, status), @@ -307,7 +297,7 @@ port_dies(_Config) -> %% Wait for exit_status from port 5000 ms (will not get a exit %% status in this case), then wait a little longer to make sure %% the port and the controlprocess has had time to terminate. - test_server:sleep(10000), + ct:sleep(10000), undefined = process_info(Ref, status); [] -> ct:fail([erlang:port_info(P, name) || P <- erlang:ports()]) @@ -315,9 +305,8 @@ port_dies(_Config) -> %%------------------------------------------------------------------------- -control_process_dies(doc) -> - "Tests what happens if the Erlang control process dies"; -control_process_dies(suite) -> []; +control_process_dies() -> + [{doc,"Tests what happens if the Erlang control process dies"}]. control_process_dies(_Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), process_flag(trap_exit, true), @@ -326,7 +315,7 @@ control_process_dies(_Config) -> [Port] -> {connected, Ref} = erlang:port_info(Port, connected), exit(Ref, kill), - test_server:sleep(500), + ct:sleep(500), undefined = erlang:port_info(Port, connected); %% Check for c-program still running, how? [] -> @@ -334,9 +323,8 @@ control_process_dies(_Config) -> end. %%------------------------------------------------------------------------- -client_dies_normal(doc) -> - ["Client dies with reason normal."]; -client_dies_normal(suite) -> []; +client_dies_normal() -> + [{doc,"Client dies with reason normal."}]. client_dies_normal(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_normal, [self()]), @@ -352,7 +340,7 @@ client_dies_normal(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_normal(Pid) -> @@ -366,9 +354,8 @@ client_normal(Pid) -> %%------------------------------------------------------------------------- -client_dies_timeout(doc) -> - ["Client dies with reason timeout."]; -client_dies_timeout(suite) -> []; +client_dies_timeout() -> + [{doc,"Client dies with reason timeout."}]. client_dies_timeout(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_timeout, [self()]), @@ -384,7 +371,7 @@ client_dies_timeout(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_timeout(Pid) -> @@ -398,9 +385,8 @@ client_timeout(Pid) -> %%------------------------------------------------------------------------- -client_dies_error(doc) -> - ["Client dies with reason error."]; -client_dies_error(suite) -> []; +client_dies_error() -> + [{doc,"Client dies with reason error."}]. client_dies_error(Config) when is_list(Config) -> Pid = spawn(?MODULE, client_error, [self()]), @@ -416,7 +402,7 @@ client_dies_error(Config) when is_list(Config) -> {'DOWN', MonitorReference, _Type, _Object, _Info} -> ok after 5000 -> - test_server:fail(control_process_not_stopped) + ct:fail(control_process_not_stopped) end. client_error(Pid) -> @@ -430,9 +416,8 @@ client_error(Pid) -> %%------------------------------------------------------------------------- -connect_timeout(doc) -> - ["Test the timeout for the connect function."]; -connect_timeout(suite) -> []; +connect_timeout() -> + [{doc,"Test the timeout for the connect function."}]. connect_timeout(Config) when is_list(Config) -> {'EXIT',timeout} = (catch odbc:connect(?RDBMS:connection_string(), [{timeout, 0}] ++ @@ -442,10 +427,9 @@ connect_timeout(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -connect_port_timeout(doc) -> - ["Test the timeout for the port program to connect back to the odbc " - "application within the connect function."]; -connect_port_timeout(suite) -> []; +connect_port_timeout() -> + [{"Test the timeout for the port program to connect back to the odbc " + "application within the connect function."}]. connect_port_timeout(Config) when is_list(Config) -> %% Application environment var 'port_timeout' has been set to 0 by %% init_per_testcase/2. @@ -453,15 +437,14 @@ connect_port_timeout(Config) when is_list(Config) -> odbc_test_lib:platform_options()). %%------------------------------------------------------------------------- -timeout(doc) -> - ["Test that timeouts don't cause unwanted behavior sush as receiving" - " an anwser to a previously tiemed out query."]; -timeout(suite) -> []; +timeout() -> + [{"Test that timeouts don't cause unwanted behavior sush as receiving" + " an anwser to a previously tiemed out query."}]. timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}]), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), @@ -512,7 +495,7 @@ update_table_timeout(Table, TimeOut, Pid) -> {'EXIT', timeout} -> Pid ! timout_occurred; {updated, 1} -> - test_server:fail(database_locker_failed) + ct:fail(database_locker_failed) end, receive @@ -537,15 +520,14 @@ update_table_timeout(Table, TimeOut, Pid) -> ok = odbc:disconnect(Ref). %%------------------------------------------------------------------------- -many_timeouts(doc) -> - ["Tests that many consecutive timeouts lead to that the connection " - "is shutdown."]; -many_timeouts(suite) -> []; +many_timeouts() -> + [{doc, "Tests that many consecutive timeouts lead to that the connection " + "is shutdown."}]. many_timeouts(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -592,19 +574,18 @@ loop_many_timouts(Ref, UpdateQuery, TimeOut) -> {'EXIT',timeout} -> loop_many_timouts(Ref, UpdateQuery, TimeOut); {updated, 1} -> - test_server:fail(database_locker_failed); + ct:fail(database_locker_failed); {error, connection_closed} -> ok end. %%------------------------------------------------------------------------- -timeout_reset(doc) -> - ["Check that the number of consecutive timouts is reset to 0 when " - "a successful call to the database is made."]; -timeout_reset(suite) -> []; +timeout_reset() -> + [{doc, "Check that the number of consecutive timouts is reset to 0 when " + "a successful call to the database is made."}]. timeout_reset(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -688,21 +669,20 @@ loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts) -> loop_timout_reset(Ref, UpdateQuery, TimeOut, NumTimeouts - 1); {updated, 1} -> - test_server:fail(database_locker_failed); + ct:fail(database_locker_failed); {error, connection_closed} -> - test_server:fail(connection_closed_premature) + ct:fail(connection_closed_premature) end. %%------------------------------------------------------------------------- -disconnect_on_timeout(doc) -> - ["Check that disconnect after a time out works properly"]; -disconnect_on_timeout(suite) -> []; +disconnect_on_timeout() -> + [{doc,"Check that disconnect after a time out works properly"}]. disconnect_on_timeout(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{auto_commit, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), TransStr = transaction_support_str(?RDBMS), {updated, _} = @@ -726,7 +706,7 @@ disconnect_on_timeout(Config) when is_list(Config) -> ok -> ok = odbc:commit(Ref, commit); nok -> - test_server:fail(database_locker_failed) + ct:fail(database_locker_failed) end. update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> @@ -744,14 +724,13 @@ update_table_disconnect_on_timeout(Table, TimeOut, Pid) -> end. %%------------------------------------------------------------------------- -connection_closed(doc) -> - ["Checks that you get an appropriate error message if you try to" - " use a connection that has been closed"]; -connection_closed(suite) -> []; +connection_closed() -> + [{doc, "Checks that you get an appropriate error message if you try to" + " use a connection that has been closed"}]. connection_closed(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -771,14 +750,13 @@ connection_closed(Config) when is_list(Config) -> {error, connection_closed} = odbc:commit(Ref, commit). %%------------------------------------------------------------------------- -disable_scrollable_cursors(doc) -> - ["Test disabling of scrollable cursors."]; -disable_scrollable_cursors(suite) -> []; +disable_scrollable_cursors() -> + [{doc,"Test disabling of scrollable cursors."}]. disable_scrollable_cursors(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{scrollable_cursors, off}]), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -792,10 +770,10 @@ disable_scrollable_cursors(Config) when is_list(Config) -> NextResult = ?RDBMS:selected_ID(1, next), - test_server:format("Expected: ~p~n", [NextResult]), + ct:pal("Expected: ~p~n", [NextResult]), Result = odbc:next(Ref), - test_server:format("Got: ~p~n", [Result]), + ct:pal("Got: ~p~n", [Result]), NextResult = Result, {error, scrollable_cursors_disabled} = odbc:first(Ref), @@ -809,15 +787,14 @@ disable_scrollable_cursors(Config) when is_list(Config) -> {selected, _ColNames,[]} = odbc:select(Ref, next, 1). %%------------------------------------------------------------------------- -return_rows_as_lists(doc)-> - ["Test the option that a row may be returned as a list instead " - "of a tuple. Too be somewhat backward compatible."]; -return_rows_as_lists(suite) -> []; +return_rows_as_lists()-> + [{doc,"Test the option that a row may be returned as a list instead " + "of a tuple. Too be somewhat backward compatible."}]. return_rows_as_lists(Config) when is_list(Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), [{tuple_row, off}] ++ odbc_test_lib:platform_options()), - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -854,29 +831,28 @@ return_rows_as_lists(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -api_missuse(doc)-> - ["Test that behaviour of the control process if the api is abused"]; -api_missuse(suite) -> []; +api_missuse()-> + [{doc,"Test that behaviour of the control process if the api is abused"}]. api_missuse(Config) when is_list(Config)-> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:call(Ref, {self(), foobar, 10}, infinity), - test_server:sleep(10), + ct:sleep(10), undefined = process_info(Ref, status), {ok, Ref2} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Serious programming fault, connetion will be shut down gen_server:cast(Ref2, {self(), foobar, 10}), - test_server:sleep(10), + ct:sleep(10), undefined = process_info(Ref2, status), {ok, Ref3} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), %% Could be an innocent misstake the connection lives. Ref3 ! foobar, - test_server:sleep(10), + ct:sleep(10), {status, _} = process_info(Ref3, status). transaction_support_str(mysql) -> @@ -886,13 +862,13 @@ transaction_support_str(_) -> %%------------------------------------------------------------------------- -extended_errors(doc)-> - ["Test the extended errors connection option: When off; the old behaviour of just an error " - "string is returned on error. When on, the error string is replaced by a 3 element tuple " - "that also exposes underlying ODBC provider error codes."]; -extended_errors(suite) -> []; +extended_errors()-> + [{doc, + "Test the extended errors connection option: When off; the old behaviour of just an error " + "string is returned on error. When on, the error string is replaced by a 3 element tuple " + "that also exposes underlying ODBC provider error codes."}]. extended_errors(Config) when is_list(Config)-> - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), {updated, _} = odbc:sql_query(Ref, "create table " ++ Table ++" ( id integer, data varchar(10))"), diff --git a/lib/odbc/test/odbc_data_type_SUITE.erl b/lib/odbc/test/odbc_data_type_SUITE.erl index c88c00725e..a3a4bc78eb 100644 --- a/lib/odbc/test/odbc_data_type_SUITE.erl +++ b/lib/odbc/test/odbc_data_type_SUITE.erl @@ -120,6 +120,7 @@ init_per_suite(Config) when is_list(Config) -> false -> case (catch odbc:start()) of ok -> + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()}| Config]; _ -> {skip, "ODBC not startable"} @@ -191,23 +192,22 @@ init_per_testcase(Case, Config) -> common_init_per_testcase(Case, Config) -> PlatformOptions = odbc_test_lib:platform_options(), - case atom_to_list(Case) of - "binary" ++ _ -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{binary_strings, on}] ++ PlatformOptions); - LCase when LCase == "utf8"; - LCase == "nchar"; - LCase == "nvarchar" -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), - [{binary_strings, on}] ++ PlatformOptions); - _ -> - {ok, Ref} = odbc:connect(?RDBMS:connection_string(), PlatformOptions) - end, + {ok, Ref} = + case atom_to_list(Case) of + "binary" ++ _ -> + odbc:connect(?RDBMS:connection_string(), + [{binary_strings, on}] ++ PlatformOptions); + LCase when LCase == "utf8"; + LCase == "nchar"; + LCase == "nvarchar" -> + odbc:connect(?RDBMS:connection_string(), + [{binary_strings, on}] ++ PlatformOptions); + _ -> + odbc:connect(?RDBMS:connection_string(), PlatformOptions) + end, odbc_test_lib:strict(Ref, ?RDBMS), - Dog = test_server:timetrap(?default_timeout), - Temp = lists:keydelete(connection_ref, 1, Config), - NewConfig = lists:keydelete(watchdog, 1, Temp), - [{watchdog, Dog}, {connection_ref, Ref} | NewConfig]. + NewConfig = lists:keydelete(connection_ref, 1, Config), + [{connection_ref, Ref} | NewConfig]. is_fixed_upper_limit(mysql) -> false; @@ -231,28 +231,23 @@ is_supported_bit(_) -> %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_TestCase, Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), ok = odbc:disconnect(Ref), %% Clean up if needed - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), odbc:sql_query(NewRef, "DROP TABLE " ++ Table), - odbc:disconnect(NewRef), - Dog = ?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. + odbc:disconnect(NewRef). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -char_fixed_lower_limit(doc) -> - ["Tests fixed length char data type lower boundaries."]; -char_fixed_lower_limit(suite) -> - []; +char_fixed_lower_limit() -> + [{doc,"Tests fixed length char data type lower boundaries."}]. char_fixed_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -287,18 +282,16 @@ char_fixed_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -char_fixed_upper_limit(doc) -> - ["Tests fixed length char data type upper boundaries."]; -char_fixed_upper_limit(suite) -> - []; +char_fixed_upper_limit() -> + [{doc,"Tests fixed length char data type upper boundaries."}]. char_fixed_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Limit unknown"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Upper limit {updated, _} = % Value == 0 || -1 driver dependent! @@ -337,14 +330,12 @@ char_fixed_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -char_fixed_padding(doc) -> - ["Tests that data that is shorter than the given size is padded " - "with blanks."]; -char_fixed_padding(suite) -> - []; +char_fixed_padding() -> + [{doc, "Tests that data that is shorter than the given size is padded " + "with blanks."}]. char_fixed_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -365,13 +356,11 @@ char_fixed_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -varchar_lower_limit(doc) -> - ["Tests variable length char data type lower boundaries."]; -varchar_lower_limit(suite) -> - []; +varchar_lower_limit() -> + [{doc,"Tests variable length char data type lower boundaries."}]. varchar_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -405,13 +394,11 @@ varchar_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -varchar_upper_limit(doc) -> - ["Tests variable length char data type upper boundaries."]; -varchar_upper_limit(suite) -> - []; +varchar_upper_limit() -> + [{doc,"Tests variable length char data type upper boundaries."}]. varchar_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -455,14 +442,12 @@ varchar_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -varchar_no_padding(doc) -> - ["Tests that data that is shorter than the given max size is not padded " - "with blanks."]; -varchar_no_padding(suite) -> - []; +varchar_no_padding() -> + [{doc, "Tests that data that is shorter than the given max size is not padded " + "with blanks."}]. varchar_no_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should NOT be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -481,13 +466,11 @@ varchar_no_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -text_lower_limit(doc) -> - ["Tests 'long' char data type lower boundaries."]; -text_lower_limit(suite) -> - []; +text_lower_limit() -> + [{doc,"Tests 'long' char data type lower boundaries."}]. text_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -504,15 +487,13 @@ text_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -text_upper_limit(doc) -> - []; -text_upper_limit(suite) -> - []; +text_upper_limit() -> + [{doc,"Tests 'text' char data type upper boundaries."}]. text_upper_limit(Config) when is_list(Config) -> {skip,"Consumes too much resources" }. -%% Ref = ?config(connection_ref, Config), -%% Table = ?config(tableName, Config), +%% Ref = proplists:get_value(connection_ref, Config), +%% Table = proplists:get_value(tableName, Config), %% {updated, _} = % Value == 0 || -1 driver dependent! %% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -534,13 +515,11 @@ text_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_char_fixed_lower_limit(doc) -> - ["Tests fixed length char data type lower boundaries."]; -binary_char_fixed_lower_limit(suite) -> - []; +binary_char_fixed_lower_limit() -> + [{doc,"Tests fixed length char data type lower boundaries."}]. binary_char_fixed_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -579,18 +558,16 @@ binary_char_fixed_lower_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -binary_char_fixed_upper_limit(doc) -> - ["Tests fixed length char data type upper boundaries."]; -binary_char_fixed_upper_limit(suite) -> - []; +binary_char_fixed_upper_limit() -> + [{doc,"Tests fixed length char data type upper boundaries."}]. binary_char_fixed_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Limit unknown"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Upper limit {updated, _} = % Value == 0 || -1 driver dependent! @@ -630,14 +607,12 @@ binary_char_fixed_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_char_fixed_padding(doc) -> - ["Tests that data that is shorter than the given size is padded " - "with blanks."]; -binary_char_fixed_padding(suite) -> - []; +binary_char_fixed_padding() -> + [{doc, "Tests that data that is shorter than the given size is padded " + "with blanks."}]. binary_char_fixed_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -658,13 +633,11 @@ binary_char_fixed_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_varchar_lower_limit(doc) -> - ["Tests variable length char data type lower boundaries."]; -binary_varchar_lower_limit(suite) -> - []; +binary_varchar_lower_limit() -> + [{doc,"Tests variable length char data type lower boundaries."}]. binary_varchar_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Below limit {error, _} = @@ -701,13 +674,11 @@ binary_varchar_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_varchar_upper_limit(doc) -> - ["Tests variable length char data type upper boundaries."]; -binary_varchar_upper_limit(suite) -> - []; +binary_varchar_upper_limit() -> + [{doc,"Tests variable length char data type upper boundaries."}]. binary_varchar_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -750,14 +721,12 @@ binary_varchar_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -binary_varchar_no_padding(doc) -> - ["Tests that data that is shorter than the given max size is not padded " - "with blanks."]; -binary_varchar_no_padding(suite) -> - []; +binary_varchar_no_padding() -> + [{doc,"Tests that data that is shorter than the given max size is not padded " + "with blanks."}]. binary_varchar_no_padding(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), %% Data should NOT be padded with blanks {updated, _} = % Value == 0 || -1 driver dependent! @@ -776,13 +745,11 @@ binary_varchar_no_padding(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_text_lower_limit(doc) -> - ["Tests 'long' char data type lower boundaries."]; -binary_text_lower_limit(suite) -> - []; +binary_text_lower_limit() -> + [{doc,"Tests 'long' char data type lower boundaries."}]. binary_text_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -799,15 +766,13 @@ binary_text_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -binary_text_upper_limit(doc) -> - []; -binary_text_upper_limit(suite) -> - []; +binary_text_upper_limit() -> + [{doc,"Tests text char data type upper boundaries."}]. binary_text_upper_limit(Config) when is_list(Config) -> {skip,"Consumes too much resources" }. -%% Ref = ?config(connection_ref, Config), -%% Table = ?config(tableName, Config), +%% Ref = proplists:get_value(connection_ref, Config), +%% Table = proplists:get_value(tableName, Config), %% {updated, _} = % Value == 0 || -1 driver dependent! %% odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -830,17 +795,15 @@ binary_text_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -tiny_int_lower_limit(doc) -> - ["Tests integer of type tinyint."]; -tiny_int_lower_limit(suite) -> - []; +tiny_int_lower_limit() -> + [{doc,"Tests integer of type tinyint."}]. tiny_int_lower_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Type tiniyint not supported"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -864,17 +827,15 @@ tiny_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -tiny_int_upper_limit(doc) -> - ["Tests integer of type tinyint."]; -tiny_int_upper_limit(suite) -> - []; +tiny_int_upper_limit() -> + [{doc,"Tests integer of type tinyint."}]. tiny_int_upper_limit(Config) when is_list(Config) -> case ?RDBMS of postgres -> {skip, "Type tiniyint not supported"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -898,13 +859,11 @@ tiny_int_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -small_int_lower_limit(doc) -> - ["Tests integer of type smallint."]; -small_int_lower_limit(suite) -> - []; +small_int_lower_limit() -> + [{doc,"Tests integer of type smallint."}]. small_int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -927,13 +886,11 @@ small_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -small_int_upper_limit(doc) -> - ["Tests integer of type smallint."]; -small_int_upper_limit(suite) -> - []; +small_int_upper_limit() -> + [{doc,"Tests integer of type smallint."}]. small_int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -955,13 +912,11 @@ small_int_upper_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -int_lower_limit(doc) -> - ["Tests integer of type int."]; -int_lower_limit(suite) -> - []; +int_lower_limit() -> + [{doc,"Tests integer of type int."}]. int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -983,13 +938,11 @@ int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -int_upper_limit(doc) -> - ["Tests integer of type int."]; -int_upper_limit(suite) -> - []; +int_upper_limit() -> + [{doc,"Tests integer of type int."}]. int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1011,13 +964,11 @@ int_upper_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -big_int_lower_limit(doc) -> - ["Tests integer of type bigint"]; -big_int_lower_limit(suite) -> - []; +big_int_lower_limit() -> + [{doc,"Tests integer of type bigint"}]. big_int_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1040,13 +991,11 @@ big_int_lower_limit(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -big_int_upper_limit(doc) -> - ["Tests integer of type bigint."]; -big_int_upper_limit(suite) -> - []; +big_int_upper_limit() -> + [{doc,"Tests integer of type bigint."}]. big_int_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1068,17 +1017,13 @@ big_int_upper_limit(Config) when is_list(Config) -> ++ "')"). %%------------------------------------------------------------------------- -bit_false(doc) -> - [""]; -bit_false(suite) -> - []; bit_false(Config) when is_list(Config) -> case ?RDBMS of oracle -> {skip, "Not supported by driver"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1102,17 +1047,13 @@ bit_false(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -bit_true(doc) -> - [""]; -bit_true(suite) -> - []; bit_true(Config) when is_list(Config) -> case ?RDBMS of oracle -> {skip, "Not supported by driver"}; _ -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! @@ -1136,14 +1077,11 @@ bit_true(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_lower_limit(doc) -> - [""]; -float_lower_limit(suite) -> - []; + float_lower_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of mysql -> @@ -1186,13 +1124,10 @@ float_lower_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_upper_limit(doc) -> - [""]; -float_upper_limit(suite) -> - []; + float_upper_limit(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of mysql -> @@ -1218,13 +1153,11 @@ float_upper_limit(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -float_zero(doc) -> - ["Test the float value zero."]; -float_zero(suite) -> - []; +float_zero() -> + [{doc,"Test the float value zero."}]. float_zero(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1237,13 +1170,11 @@ float_zero(Config) when is_list(Config) -> SelectResult = odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table). %%------------------------------------------------------------------------- -real_zero(doc) -> - ["Test the real value zero."]; -real_zero(suite) -> - []; +real_zero() -> + [{doc,"Test the real value zero."}]. real_zero(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), case ?RDBMS of oracle -> @@ -1262,13 +1193,11 @@ real_zero(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table) end. %%------------------------------------------------------------------------ -dec_long(doc) -> - [""]; dec_long(suit) -> []; dec_long(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1281,13 +1210,11 @@ dec_long(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ -dec_double(doc) -> - [""]; dec_double(suit) -> []; dec_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1329,13 +1256,11 @@ dec_double(Config) when is_list(Config) -> ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ -dec_bignum(doc) -> - [""]; dec_bignum(suit) -> []; dec_bignum(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1361,13 +1286,11 @@ dec_bignum(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ -num_long(doc) -> - [""]; num_long(suit) -> []; num_long(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1380,13 +1303,11 @@ num_long(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields). %%------------------------------------------------------------------------ -num_double(doc) -> - [""]; num_double(suit) -> []; num_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1426,13 +1347,11 @@ num_double(Config) when is_list(Config) -> odbc:sql_query(Ref,"SELECT FIELD FROM " ++ Table), ["FIELD"] = odbc_test_lib:to_upper(Fields2). %%------------------------------------------------------------------------ -num_bignum(doc) -> - [""]; num_bignum(suit) -> []; num_bignum(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1459,13 +1378,13 @@ num_bignum(Config) when is_list(Config) -> ["FIELD"] = odbc_test_lib:to_upper(Fields1). %%------------------------------------------------------------------------ -utf8(doc) -> - ["Test unicode support"]; +utf8() -> + [{doc,"Test unicode support"}]. utf8(suit) -> []; utf8(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ "(FIELD text)"), @@ -1487,30 +1406,30 @@ utf8(Config) when is_list(Config) -> end, Latin1Data), - test_server:format("UnicodeIn: ~p ~n",[UnicodeIn]), + ct:pal("UnicodeIn: ~p ~n",[UnicodeIn]), {updated, _} = odbc:param_query(Ref,"INSERT INTO " ++ Table ++ "(FIELD) values(?)", [{{sql_varchar,50}, UnicodeIn}]), {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table), - test_server:format("UnicodeOut: ~p~n", [UnicodeOut]), + ct:pal("UnicodeOut: ~p~n", [UnicodeOut]), Result = lists:map(fun({Char}) -> unicode:characters_to_list(Char,utf8) end, UnicodeOut), - test_server:format("Result: ~p ~n", [Result]), + ct:pal("Result: ~p ~n", [Result]), Latin1Data = Result. %%------------------------------------------------------------------------ -nchar(doc) -> - ["Test unicode nchar support in sqlserver"]; +nchar() -> + [{doc,"Test unicode nchar support in sqlserver"}]. nchar(suit) -> []; nchar(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1520,13 +1439,13 @@ nchar(Config) when is_list(Config) -> %%------------------------------------------------------------------------ -nvarchar(doc) -> - ["Test 'unicode' nvarchar support"]; +nvarchar() -> + [{doc,"Test 'unicode' nvarchar support"}]. nvarchar(suit) -> []; nvarchar(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1535,13 +1454,11 @@ nvarchar(Config) when is_list(Config) -> w_char_support(Ref, Table, sql_wlongvarchar, 50). %%------------------------------------------------------------------------ -timestamp(doc) -> - [""]; timestamp(suit) -> []; timestamp(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1582,21 +1499,21 @@ w_char_support(Ref, Table, CharType, Size) -> end, Latin1Data), - test_server:format("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]), + ct:pal("UnicodeIn (utf 16): ~p ~n",[UnicodeIn]), {updated, _} = odbc:param_query(Ref, "INSERT INTO " ++ Table ++ "(FIELD) values(?)", [{{CharType, Size},UnicodeIn}]), {selected,_,UnicodeOut} = odbc:sql_query(Ref,"SELECT * FROM " ++ Table), - test_server:format("UnicodeOut: ~p~n", [UnicodeOut]), + ct:pal("UnicodeOut: ~p~n", [UnicodeOut]), PadResult = lists:map(fun({Unicode}) -> unicode:characters_to_list(Unicode,{utf16,little}) end, UnicodeOut), - test_server:format("Result: ~p~n", [PadResult]), + ct:pal("Result: ~p~n", [PadResult]), Result = lists:map(fun(Str) -> string:strip(Str) end, PadResult), diff --git a/lib/odbc/test/odbc_query_SUITE.erl b/lib/odbc/test/odbc_query_SUITE.erl index 5f719b7287..c283872965 100644 --- a/lib/odbc/test/odbc_query_SUITE.erl +++ b/lib/odbc/test/odbc_query_SUITE.erl @@ -113,6 +113,7 @@ init_per_suite(Config) when is_list(Config) -> false -> case (catch odbc:start()) of ok -> + ct:timetrap(?default_timeout), [{tableName, odbc_test_lib:unique_table_name()}| Config]; _ -> {skip, "ODBC not startable"} @@ -144,10 +145,10 @@ end_per_suite(_Config) -> init_per_testcase(_Case, Config) -> {ok, Ref} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), odbc_test_lib:strict(Ref, ?RDBMS), - Dog = test_server:timetrap(?default_timeout), - Temp = lists:keydelete(connection_ref, 1, Config), - NewConfig = lists:keydelete(watchdog, 1, Temp), - [{watchdog, Dog}, {connection_ref, Ref} | NewConfig]. + + NewConfig = lists:keydelete(connection_ref, 1, Config), + + [{connection_ref, Ref} | NewConfig]. %%-------------------------------------------------------------------- %% Function: end_per_testcase(Case, Config) -> _ @@ -158,27 +159,23 @@ init_per_testcase(_Case, Config) -> %% Description: Cleanup after each test case %%-------------------------------------------------------------------- end_per_testcase(_Case, Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), ok = odbc:disconnect(Ref), %% Clean up if needed - Table = ?config(tableName, Config), + Table = proplists:get_value(tableName, Config), {ok, NewRef} = odbc:connect(?RDBMS:connection_string(), odbc_test_lib:platform_options()), odbc:sql_query(NewRef, "DROP TABLE " ++ Table), - odbc:disconnect(NewRef), - Dog = ?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. + odbc:disconnect(NewRef). %%------------------------------------------------------------------------- %% Test cases starts here. %%------------------------------------------------------------------------- -stored_proc(doc)-> - ["Test stored proc with OUT param"]; -stored_proc(suite) -> []; +stored_proc()-> + [{doc, "Test stored proc with OUT param"}]. stored_proc(Config) when is_list(Config) -> case ?RDBMS of X when X == oracle; X == postgres-> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), {updated, _} = odbc:sql_query(Ref, ?RDBMS:stored_proc_integer_out()), @@ -192,12 +189,11 @@ stored_proc(Config) when is_list(Config) -> {skip, "stored proc not yet supported"} end. -sql_query(doc)-> - ["Test the common cases"]; -sql_query(suite) -> []; +sql_query()-> + [{doc, "Test the common cases"}]. sql_query(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -235,14 +231,14 @@ sql_query(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_count(doc) -> - ["Tests select_count/[2,3]'s timeout, " - " select_count's functionality will be better tested by other tests " - " such as first."]; +select_count() -> + [{doc, "Tests select_count/[2,3]'s timeout, " + " select_count's functionality will be better tested by other tests " + " such as first."}]. select_count(sute) -> []; select_count(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -257,12 +253,11 @@ select_count(Config) when is_list(Config) -> (catch odbc:select_count(Ref, "SELECT * FROM ", -1)), ok. %%------------------------------------------------------------------------- -first(doc) -> - ["Tests first/[1,2]"]; -first(suite) -> []; +first() -> + [doc, {"Tests first/[1,2]"}]. first(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -284,12 +279,11 @@ first(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -last(doc) -> - ["Tests last/[1,2]"]; -last(suite) -> []; +last() -> + [{doc, "Tests last/[1,2]"}]. last(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -311,12 +305,11 @@ last(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -next(doc) -> - ["Tests next/[1,2]"]; -next(suite) -> []; +next() -> + [{doc, "Tests next/[1,2]"}]. next(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -337,12 +330,11 @@ next(Config) when is_list(Config) -> {'EXIT', {function_clause, _}} = (catch odbc:next(Ref, -1)), ok. %%------------------------------------------------------------------------- -prev(doc) -> - ["Tests prev/[1,2]"]; -prev(suite) -> []; +prev() -> + [{doc, "Tests prev/[1,2]"}]. prev(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -366,12 +358,12 @@ prev(Config) when is_list(Config) -> {'EXIT', {function_clause, _}} = (catch odbc:prev(Ref, -1)), ok. %%------------------------------------------------------------------------- -select_next(doc) -> - ["Tests select/[4,5] with CursorRelation = next "]; +select_next() -> + [{doc, "Tests select/[4,5] with CursorRelation = next "}]. select_next(suit) -> []; select_next(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -407,12 +399,12 @@ select_next(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_relative(doc) -> - ["Tests select/[4,5] with CursorRelation = relative "]; +select_relative() -> + [{doc, "Tests select/[4,5] with CursorRelation = relative "}]. select_relative(suit) -> []; select_relative(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -448,12 +440,12 @@ select_relative(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -select_absolute(doc) -> - ["Tests select/[4,5] with CursorRelation = absolute "]; +select_absolute() -> + [{doc, "Tests select/[4,5] with CursorRelation = absolute "}]. select_absolute(suit) -> []; select_absolute(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -482,12 +474,11 @@ select_absolute(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -create_table_twice(doc) -> - ["Test what happens if you try to create the same table twice."]; -create_table_twice(suite) -> []; +create_table_twice() -> + [{doc, "Test what happens if you try to create the same table twice."}]. create_table_twice(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -501,12 +492,11 @@ create_table_twice(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -delete_table_twice(doc) -> - ["Test what happens if you try to delete the same table twice."]; -delete_table_twice(suite) -> []; +delete_table_twice() -> + [{doc, "Test what happens if you try to delete the same table twice."}]. delete_table_twice(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -518,12 +508,12 @@ delete_table_twice(Config) when is_list(Config) -> ok. %------------------------------------------------------------------------- -duplicate_key(doc) -> - ["Test what happens if you try to use the same key twice"]; +duplicate_key() -> + [{doc, "Test what happens if you try to use the same key twice"}]. duplicate_key(suit) -> []; duplicate_key(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -539,13 +529,12 @@ duplicate_key(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -not_connection_owner(doc) -> - ["Test what happens if a process that did not start the connection" - " tries to acess it."]; -not_connection_owner(suite) -> []; +not_connection_owner() -> + [{doc, "Test what happens if a process that did not start the connection" + " tries to acess it."}]. not_connection_owner(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), spawn_link(?MODULE, not_owner, [self(), Ref, Table]), @@ -564,12 +553,11 @@ not_owner(Pid, Ref, Table) -> Pid ! continue. %%------------------------------------------------------------------------- -no_result_set(doc) -> - ["Tests what happens if you try to use a function that needs an " - "associated result set when there is none."]; -no_result_set(suite) -> []; +no_result_set() -> + [{doc, "Tests what happens if you try to use a function that needs an " + "associated result set when there is none."}]. no_result_set(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), + Ref = proplists:get_value(connection_ref, Config), {error, result_set_does_not_exist} = odbc:first(Ref), {error, result_set_does_not_exist} = odbc:last(Ref), @@ -582,13 +570,11 @@ no_result_set(Config) when is_list(Config) -> odbc:select(Ref, {relative, 2}, 1), ok. %%------------------------------------------------------------------------- -query_error(doc) -> - ["Test what happens if there is an error in the query."]; -query_error(suite) -> - []; +query_error() -> + [{doc, "Test what happens if there is an error in the query."}]. query_error(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -605,15 +591,13 @@ query_error(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -multiple_select_result_sets(doc) -> - ["Test what happens if you have a batch of select queries."]; -multiple_select_result_sets(suite) -> - []; +multiple_select_result_sets() -> + [{doc, "Test what happens if you have a batch of select queries."}]. multiple_select_result_sets(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -640,16 +624,14 @@ multiple_select_result_sets(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -multiple_mix_result_sets(doc) -> - ["Test what happens if you have a batch of select and other type of" - " queries."]; -multiple_mix_result_sets(suite) -> - []; +multiple_mix_result_sets() -> + [{doc, "Test what happens if you have a batch of select and other type of" + " queries."}]. multiple_mix_result_sets(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -674,15 +656,13 @@ multiple_mix_result_sets(Config) when is_list(Config) -> {skip, "multiple result_set not supported"} end. %%------------------------------------------------------------------------- -multiple_result_sets_error(doc) -> - ["Test what happens if one of the batched queries fails."]; -multiple_result_sets_error(suite) -> - []; +multiple_result_sets_error() -> + [{doc, "Test what happens if one of the batched queries fails."}]. multiple_result_sets_error(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -709,15 +689,13 @@ multiple_result_sets_error(Config) when is_list(Config) -> end. %%------------------------------------------------------------------------- -param_insert_tiny_int(doc)-> - ["Test insertion of tiny ints by parameterized queries."]; -param_insert_tiny_int(suite) -> - []; +param_insert_tiny_int()-> + [{doc,"Test insertion of tiny ints by parameterized queries."}]. param_insert_tiny_int(Config) when is_list(Config) -> case ?RDBMS of sqlserver -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -746,13 +724,11 @@ param_insert_tiny_int(Config) when is_list(Config) -> {skip, "Type tiniyint not supported"} end. %%------------------------------------------------------------------------- -param_insert_small_int(doc)-> - ["Test insertion of small ints by parameterized queries."]; -param_insert_small_int(suite) -> - []; +param_insert_small_int()-> + [{doc,"Test insertion of small ints by parameterized queries."}]. param_insert_small_int(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -778,13 +754,11 @@ param_insert_small_int(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_int(doc)-> - ["Test insertion of ints by parameterized queries."]; -param_insert_int(suite) -> - []; +param_insert_int()-> + [{doc,"Test insertion of ints by parameterized queries."}]. param_insert_int(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -810,13 +784,11 @@ param_insert_int(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_integer(doc)-> - ["Test insertion of integers by parameterized queries."]; -param_insert_integer(suite) -> - []; +param_insert_integer()-> + [{doc,"Test insertion of integers by parameterized queries."}]. param_insert_integer(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -842,13 +814,11 @@ param_insert_integer(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_decimal(doc)-> - ["Test insertion of decimal numbers by parameterized queries."]; -param_insert_decimal(suite) -> - []; +param_insert_decimal()-> + [{doc,"Test insertion of decimal numbers by parameterized queries."}]. param_insert_decimal(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -893,13 +863,11 @@ param_insert_decimal(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_numeric(doc)-> - ["Test insertion of numeric numbers by parameterized queries."]; -param_insert_numeric(suite) -> - []; +param_insert_numeric()-> + [{doc,"Test insertion of numeric numbers by parameterized queries."}]. param_insert_numeric(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -944,13 +912,11 @@ param_insert_numeric(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_char(doc)-> - ["Test insertion of fixed length string by parameterized queries."]; -param_insert_char(suite) -> - []; +param_insert_char()-> + [{doc,"Test insertion of fixed length string by parameterized queries."}]. param_insert_char(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -980,13 +946,11 @@ param_insert_char(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_character(doc)-> - ["Test insertion of fixed length string by parameterized queries."]; -param_insert_character(suite) -> - []; +param_insert_character()-> + [{doc,"Test insertion of fixed length string by parameterized queries."}]. param_insert_character(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1017,13 +981,11 @@ param_insert_character(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------ -param_insert_char_varying(doc)-> - ["Test insertion of variable length strings by parameterized queries."]; -param_insert_char_varying(suite) -> - []; +param_insert_char_varying()-> + [{doc,"Test insertion of variable length strings by parameterized queries."}]. param_insert_char_varying(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1054,13 +1016,11 @@ param_insert_char_varying(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_character_varying(doc)-> - ["Test insertion of variable length strings by parameterized queries."]; -param_insert_character_varying(suite) -> - []; +param_insert_character_varying()-> + [{doc,"Test insertion of variable length strings by parameterized queries."}]. param_insert_character_varying(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1091,13 +1051,11 @@ param_insert_character_varying(Config) when is_list(Config) -> [{{sql_varchar, 10}, ["1", 2]}])), ok. %%------------------------------------------------------------------------- -param_insert_float(doc)-> - ["Test insertion of floats by parameterized queries."]; -param_insert_float(suite) -> - []; +param_insert_float()-> + [{doc,"Test insertion of floats by parameterized queries."}]. param_insert_float(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1120,7 +1078,7 @@ param_insert_float(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(float_numbers_do_not_match) + ct:fail(float_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1130,13 +1088,11 @@ param_insert_float(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_real(doc)-> - ["Test insertion of real numbers by parameterized queries."]; -param_insert_real(suite) -> - []; +param_insert_real()-> + [{doc,"Test insertion of real numbers by parameterized queries."}]. param_insert_real(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1161,7 +1117,7 @@ param_insert_real(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(real_numbers_do_not_match) + ct:fail(real_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1171,13 +1127,11 @@ param_insert_real(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_double(doc)-> - ["Test insertion of doubles by parameterized queries."]; -param_insert_double(suite) -> - []; +param_insert_double()-> + [{doc,"Test insertion of doubles by parameterized queries."}]. param_insert_double(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1200,7 +1154,7 @@ param_insert_double(Config) when is_list(Config) -> true -> ok; false -> - test_server:fail(double_numbers_do_not_match) + ct:fail(double_numbers_do_not_match) end, {'EXIT',{badarg,odbc,param_query,'Params'}} = @@ -1210,13 +1164,11 @@ param_insert_double(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_insert_mix(doc)-> - ["Test insertion of a mixture of datatypes by parameterized queries."]; -param_insert_mix(suite) -> - []; +param_insert_mix()-> + [{doc,"Test insertion of a mixture of datatypes by parameterized queries."}]. param_insert_mix(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1237,13 +1189,11 @@ param_insert_mix(Config) when is_list(Config) -> odbc:sql_query(Ref, "SELECT * FROM " ++ Table), ok. %%------------------------------------------------------------------------- -param_update(doc)-> - ["Test parameterized update query."]; -param_update(suite) -> - []; +param_update()-> + [{doc,"Test parameterized update query."}]. param_update(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1272,12 +1222,12 @@ param_update(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -delete_nonexisting_row(doc) -> % OTP-5759 - ["Make a delete...where with false conditions (0 rows deleted). ", - "This used to give an error message (see ticket OTP-5759)."]; +delete_nonexisting_row() -> % OTP-5759 + [{doc, "Make a delete...where with false conditions (0 rows deleted). ", + "This used to give an error message (see ticket OTP-5759)."}]. delete_nonexisting_row(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, "CREATE TABLE " ++ Table @@ -1301,13 +1251,11 @@ delete_nonexisting_row(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_delete(doc) -> - ["Test parameterized delete query."]; -param_delete(suite) -> - []; +param_delete() -> + [{doc,"Test parameterized delete query."}]. param_delete(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1336,13 +1284,11 @@ param_delete(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -param_select(doc) -> - ["Test parameterized select query."]; -param_select(suite) -> - []; +param_select() -> + [{doc,"Test parameterized select query."}]. param_select(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1366,13 +1312,11 @@ param_select(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_select_empty_params(doc) -> - ["Test parameterized select query with no parameters."]; -param_select_empty_params(suite) -> - []; +param_select_empty_params() -> + [{doc,"Test parameterized select query with no parameters."}]. param_select_empty_params(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1396,13 +1340,11 @@ param_select_empty_params(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -param_delete_empty_params(doc) -> - ["Test parameterized delete query with no parameters."]; -param_delete_empty_params(suite) -> - []; +param_delete_empty_params() -> + [{doc,"Test parameterized delete query with no parameters."}]. param_delete_empty_params(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1430,13 +1372,11 @@ param_delete_empty_params(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_integer(doc) -> - ["Test describe_table/[2,3] for integer columns."]; -describe_integer(suite) -> - []; +describe_integer() -> + [{doc,"Test describe_table/[2,3] for integer columns."}]. describe_integer(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1449,13 +1389,11 @@ describe_integer(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_string(doc) -> - ["Test describe_table/[2,3] for string columns."]; -describe_string(suite) -> - []; +describe_string() -> + [{doc,"Test describe_table/[2,3] for string columns."}]. describe_string(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1470,13 +1408,11 @@ describe_string(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_floating(doc) -> - ["Test describe_table/[2,3] for floting columns."]; -describe_floating(suite) -> - []; +describe_floating() -> + [{doc,"Test describe_table/[2,3] for floting columns."}]. describe_floating(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1490,14 +1426,12 @@ describe_floating(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_dec_num(doc) -> - ["Test describe_table/[2,3] for decimal and numerical columns"]; -describe_dec_num(suite) -> - []; +describe_dec_num() -> + [{doc,"Test describe_table/[2,3] for decimal and numerical columns"}]. describe_dec_num(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = odbc:sql_query(Ref, @@ -1511,14 +1445,12 @@ describe_dec_num(Config) when is_list(Config) -> %%------------------------------------------------------------------------- -describe_timestamp(doc) -> - ["Test describe_table/[2,3] for tinmestap columns"]; -describe_timestamp(suite) -> - []; +describe_timestamp() -> + [{doc,"Test describe_table/[2,3] for tinmestap columns"}]. describe_timestamp(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {updated, _} = % Value == 0 || -1 driver dependent! odbc:sql_query(Ref, "CREATE TABLE " ++ Table ++ @@ -1530,14 +1462,12 @@ describe_timestamp(Config) when is_list(Config) -> ok. %%------------------------------------------------------------------------- -describe_no_such_table(doc) -> - ["Test what happens if you try to describe a table that does not exist."]; -describe_no_such_table(suite) -> - []; +describe_no_such_table() -> + [{doc,"Test what happens if you try to describe a table that does not exist."}]. describe_no_such_table(Config) when is_list(Config) -> - Ref = ?config(connection_ref, Config), - Table = ?config(tableName, Config), + Ref = proplists:get_value(connection_ref, Config), + Table = proplists:get_value(tableName, Config), {error, _ } = odbc:describe_table(Ref, Table), ok. @@ -1549,10 +1479,10 @@ describe_no_such_table(Config) when is_list(Config) -> is_driver_error(Error) -> case is_list(Error) of true -> - test_server:format("Driver error ~p~n", [Error]), + ct:pal("Driver error ~p~n", [Error]), ok; false -> - test_server:fail(Error) + ct:fail(Error) end. is_supported_multiple_resultsets(sqlserver) -> true; diff --git a/lib/odbc/test/odbc_start_SUITE.erl b/lib/odbc/test/odbc_start_SUITE.erl index f055eeb60e..310b92ca29 100644 --- a/lib/odbc/test/odbc_start_SUITE.erl +++ b/lib/odbc/test/odbc_start_SUITE.erl @@ -49,6 +49,7 @@ init_per_suite(Config) -> _ -> %% Make sure odbc is not already started odbc:stop(), + ct:timetrap(?TIMEOUT), [{tableName, odbc_test_lib:unique_table_name()} | Config] end end. @@ -74,11 +75,9 @@ end_per_suite(_Config) -> %% variable, but should NOT alter/remove any existing entries. %% Description: Initialization before each test case %%-------------------------------------------------------------------- -init_per_testcase(_TestCase, Config0) -> - test_server:format("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), - Config = lists:keydelete(watchdog, 1, Config0), - Dog = test_server:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. +init_per_testcase(_TestCase, Config) -> + ct:pal("ODBCINI = ~p~n", [os:getenv("ODBCINI")]), + Config. %%-------------------------------------------------------------------- %% Function: end_per_testcase(TestCase, Config) -> _ @@ -88,15 +87,8 @@ init_per_testcase(_TestCase, Config0) -> %% A list of key/value pairs, holding the test case configuration. %% Description: Cleanup after each test case %%-------------------------------------------------------------------- -end_per_testcase(_TestCase, Config) -> - Dog = ?config(watchdog, Config), - case Dog of - undefined -> - ok; - _ -> - test_server:timetrap_cancel(Dog) - end. - +end_per_testcase(_TestCase, _Config) -> + ok. %%-------------------------------------------------------------------- %% Function: all(Clause) -> TestCases %% Clause - atom() - suite | doc @@ -135,10 +127,8 @@ app(Config) when is_list(Config) -> appup(Config) when is_list(Config) -> ok = ?t:appup_test(odbc). -start(doc) -> - ["Test start/stop of odbc"]; -start(suite) -> - []; +start() -> + [{doc,"Test start/stop of odbc"}]. start(Config) when is_list(Config) -> PlatformOptions = odbc_test_lib:platform_options(), {error,odbc_not_started} = odbc:connect(?RDBMS:connection_string(), @@ -153,9 +143,9 @@ start(Config) when is_list(Config) -> start_odbc(transient), start_odbc(permanent); {error, odbc_not_started} -> - test_server:fail(start_failed); + ct:fail(start_failed); Error -> - test_server:format("Connection failed: ~p~n", [Error]), + ct:pal("Connection failed: ~p~n", [Error]), {skip, "ODBC is not properly setup"} end. @@ -166,13 +156,12 @@ start_odbc(Type) -> ok = odbc:disconnect(Ref), odbc:stop(); {error, odbc_not_started} -> - test_server:fail(start_failed) + ct:fail(start_failed) end. -long_connection_line(doc)-> - ["Test a connection line longer than 127 characters"]; -long_connection_line(suite) -> []; +long_connection_line()-> + [{doc,"Test a connection line longer than 127 characters"}]. long_connection_line(_Config) -> odbc:start(), String133 = "unknown_odbc_parameter=01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789", diff --git a/lib/odbc/test/odbc_test_lib.erl b/lib/odbc/test/odbc_test_lib.erl index 37c2249303..cf82d4d32a 100644 --- a/lib/odbc/test/odbc_test_lib.erl +++ b/lib/odbc/test/odbc_test_lib.erl @@ -61,13 +61,13 @@ odbc_check() -> end. check_row_count(Count, Count) -> - test_server:format("Correct row count Count: ~p~n", [Count]), + ct:pal("Correct row count Count: ~p~n", [Count]), true; check_row_count(_, undefined) -> - test_server:format("Undefined row count ~n", []), + ct:pal("Undefined row count ~n", []), true; check_row_count(Expected, Count) -> - test_server:format("Incorrect row count Expected ~p Got ~p~n", + ct:pal("Incorrect row count Expected ~p Got ~p~n", [Expected, Count]), false. diff --git a/lib/reltool/src/reltool_server.erl b/lib/reltool/src/reltool_server.erl index 21a4485f94..89e90670cf 100644 --- a/lib/reltool/src/reltool_server.erl +++ b/lib/reltool/src/reltool_server.erl @@ -530,9 +530,14 @@ analyse(#state{sys=Sys} = S, Apps, Status) -> %% Write all #app to app_tab and all #mod to mod_tab. Status2 = apps_init_is_included(S, Apps, RelApps, Status), + %% For each application that is not (directly or indirectly) part + %% of a release, but still has #app.is_included==true, propagate + %% is_included to the dependencies specified in the .app files. + app_propagate_is_included(S), + %% For each module that has #mod.is_included==true, propagate %% is_included to the modules it uses. - propagate_is_included(S), + mod_propagate_is_included(S), %% Insert reverse dependencies - i.e. for each %% #mod{name=Mod, uses_mods=[UsedMod]}, @@ -565,31 +570,34 @@ apps_in_rels(Rels, Apps) -> apps_in_rel(#rel{name = RelName, rel_apps = RelApps}, Apps) -> Mandatory = [{RelName, kernel}, {RelName, stdlib}], - Other = + Explicit0 = [{RelName, AppName} || #rel_app{name=AppName} <- RelApps], + Explicit = Mandatory ++ Explicit0, + Deps = [{RelName, AppName} || RA <- RelApps, - AppName <- [RA#rel_app.name | + AppName <- + case lists:keyfind(RA#rel_app.name, + #app.name, + Apps) of + App=#app{info = #app_info{applications = AA}} -> %% Included applications in rel shall overwrite included %% applications in .app. I.e. included applications in %% .app shall only be used if it is not defined in rel. - case RA#rel_app.incl_apps of - undefined -> - case lists:keyfind(RA#rel_app.name, - #app.name, - Apps) of - #app{info = #app_info{incl_apps = IA}} -> - IA; - false -> - reltool_utils:throw_error( - "Release ~tp uses non existing " - "application ~w", - [RelName,RA#rel_app.name]) - end; - IA -> - IA - end], - not lists:keymember(AppName, 2, Mandatory)], - more_apps_in_rels(Mandatory ++ Other, Apps, []). + IA = case RA#rel_app.incl_apps of + undefined -> + (App#app.info)#app_info.incl_apps; + RelIA -> + RelIA + end, + AA ++ IA; + false -> + reltool_utils:throw_error( + "Release ~tp uses non existing " + "application ~w", + [RelName,RA#rel_app.name]) + end, + not lists:keymember(AppName, 2, Explicit)], + more_apps_in_rels(Deps, Apps, Explicit). more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> case lists:member(RA, Acc) of @@ -597,8 +605,8 @@ more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> more_apps_in_rels(RelApps, Apps, Acc); false -> case lists:keyfind(AppName, #app.name, Apps) of - #app{info = #app_info{applications = InfoApps}} -> - Extra = [{RelName, N} || N <- InfoApps], + #app{info = #app_info{applications = AA, incl_apps=IA}} -> + Extra = [{RelName, N} || N <- AA++IA], Acc2 = more_apps_in_rels(Extra, Apps, [RA | Acc]), more_apps_in_rels(RelApps, Apps, Acc2); false -> @@ -610,7 +618,6 @@ more_apps_in_rels([{RelName, AppName} = RA | RelApps], Apps, Acc) -> more_apps_in_rels([], _Apps, Acc) -> Acc. - apps_init_is_included(S, Apps, RelApps, Status) -> lists:foldl(fun(App, AccStatus) -> app_init_is_included(S, App, RelApps, AccStatus) @@ -745,6 +752,100 @@ false_to_undefined(Bool) -> _ -> Bool end. +get_no_rel_apps_and_dependencies(S) -> + ets:select(S#state.app_tab, [{#app{name='$1', + is_included=true, + info=#app_info{applications='$2', + incl_apps='$3', + _='_'}, + rels=[], + _='_'}, + [], + [{{'$1','$2','$3'}}]}]). + +app_propagate_is_included(S) -> + lists:foreach( + fun({AppName,DepNames1,DepNames2}) -> + app_mark_is_included(S,AppName,DepNames1++DepNames2) + end, + get_no_rel_apps_and_dependencies(S)). + +app_mark_is_included(#state{app_tab=AppTab, mod_tab=ModTab, sys=Sys}=S,UsedByName,[AppName|AppNames]) -> + case ets:lookup(AppTab, AppName) of + [A] -> + case A#app.is_included of + undefined -> + %% Not yet marked => mark and propagate + A2 = + case A#app.incl_cond of + include -> + A#app{is_pre_included = true, + is_included = true}; + exclude -> + A#app{is_pre_included = false, + is_included = false}; + AppInclCond when AppInclCond==undefined; + AppInclCond==derived -> + A#app{is_included = true} + end, + ets:insert(AppTab, A2), + + ModCond = + case A#app.mod_cond of + undefined -> Sys#sys.mod_cond; + _ -> A#app.mod_cond + end, + Filter = + fun(M) -> + case ModCond of + all -> true; + app -> M#mod.is_app_mod; + ebin -> M#mod.is_ebin_mod; + derived -> false; + none -> false + end + end, + Mods = lists:filter(Filter, A#app.mods), + %% Mark the modules of this app, but no need to go + %% recursive on modules since this is done in + %% mod_mark_is_included. + [case M#mod.is_included of + undefined -> + M2 = + case M#mod.incl_cond of + include -> + M#mod{is_pre_included = true, + is_included = true}; + exclude -> + M#mod{is_pre_included = false, + is_included = false}; + ModInclCond when ModInclCond==undefined; + ModInclCond==derived -> + M#mod{is_included = true} + end, + ets:insert(ModTab, M2); + _ -> + ok + end || M <- Mods], + + %% Go recursive on dependencies + #app{info=#app_info{applications=DepNames1, + incl_apps=DepNames2}} = A, + app_mark_is_included(S,AppName,DepNames1++DepNames2); + _ -> + %% Already marked + ok + end; + [] -> + %% Missing app + reltool_utils:throw_error( + "Application ~tp uses non existing application ~w", + [UsedByName,AppName]) + end, + app_mark_is_included(S, UsedByName, AppNames); +app_mark_is_included(_S, _UsedByName, []) -> + ok. + %% Return the list for {ModName, UsesModNames} for all modules where %% #mod.is_included==true. get_all_mods_and_dependencies(S) -> @@ -755,7 +856,7 @@ get_all_mods_and_dependencies(S) -> [], [{{'$1','$2'}}]}]). -propagate_is_included(S) -> +mod_propagate_is_included(S) -> case lists:flatmap( fun({ModName,UsesModNames}) -> mod_mark_is_included(S,ModName,UsesModNames,[]) diff --git a/lib/reltool/test/reltool_server_SUITE.erl b/lib/reltool/test/reltool_server_SUITE.erl index e9bd46fb27..e8dfea94da 100644 --- a/lib/reltool/test/reltool_server_SUITE.erl +++ b/lib/reltool/test/reltool_server_SUITE.erl @@ -142,6 +142,7 @@ all() -> save_config, dependencies, mod_incl_cond_derived, + dep_in_app_not_xref, use_selected_vsn, use_selected_vsn_relative_path, non_standard_vsn_id, @@ -408,7 +409,6 @@ create_release_sort(Config) -> {app,tools,[{mod_cond,app},{incl_cond,include}]} ]}, %% Generate release - ?msym({ok, {release, {RelName1, RelVsn}, {erts, _}, [{kernel, _}, @@ -2304,6 +2304,7 @@ dependencies(Config) -> ok. +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Test that incl_cond on mod level overwrites mod_cond on app level %% Uses same test applications as dependencies/1 above mod_incl_cond_derived(Config) -> @@ -2346,6 +2347,40 @@ mod_incl_cond_derived(Config) -> ok. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% ERL-167, OTP-11993: For applications that are not included in a +%% release spec ('rel'), dependencies in the .app files are not +%% considered - only those found with xref. +dep_in_app_not_xref(Config) -> + RelName = "Just testing...", + RelVsn = "1.0", + Sys = + {sys, + [ + {lib_dirs,[filename:join(datadir(Config),"dep_in_app_not_xref")]}, + {incl_cond,exclude}, + {incl_archive_filters,[]}, + {erts,[{incl_cond,exclude}]}, + {boot_rel, RelName}, + {rel, RelName, RelVsn, [kernel, stdlib]}, + {app,kernel,[{incl_cond,include}]}, + {app,stdlib,[{incl_cond,include}]}, + {app,x,[{incl_cond,include}]}, + {app,y,[{incl_cond,derived}]}, + {app,z,[{incl_cond,derived}]} + ]}, + + TargetDir = filename:join([?WORK_DIR, "target_dep_in_app_not_xref"]), + ?m(ok, reltool_utils:recursive_delete(TargetDir)), + ?m(ok, file:make_dir(TargetDir)), + ?log("SPEC: ~p\n", [reltool:get_target_spec([{config, Sys}])]), + ok = ?m(ok, reltool:create_target([{config, Sys}], TargetDir)), + ?log("~p~n",[file:list_dir(filename:join([TargetDir,"lib"]))]), + + ?m(true, filelib:is_dir(filename:join([TargetDir,"lib","y-1.0"]))), + ?m(true, filelib:is_dir(filename:join([TargetDir,"lib","z-1.0"]))), + ok. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% use_selected_vsn(Config) -> LibDir1 = filename:join(datadir(Config),"use_selected_vsn"), B1Dir = filename:join(LibDir1,"b-1.0"), diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app new file mode 100644 index 0000000000..fe922e3a41 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/ebin/x.app @@ -0,0 +1,7 @@ +% -*-erlang-*- +{application, x, + [{description, "Main application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [x1]}, + {registered, []}, + {applications, [kernel, stdlib, y]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl new file mode 100644 index 0000000000..bf1e7f9279 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/x-1.0/src/x1.erl @@ -0,0 +1,5 @@ +-module(x1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app new file mode 100644 index 0000000000..a21cfe6c21 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/ebin/y.app @@ -0,0 +1,8 @@ +% -*-erlang-*- +{application, y, + [{description, "Library application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [y1]}, + {registered, []}, + {applications, [kernel, stdlib]}, + {included_applications, [z]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl new file mode 100644 index 0000000000..902a7e21f3 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/y-1.0/src/y1.erl @@ -0,0 +1,5 @@ +-module(y1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app new file mode 100644 index 0000000000..437a0968e9 --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/ebin/z.app @@ -0,0 +1,7 @@ +% -*-erlang-*- +{application, z, + [{description, "Library application in reltool dependency test"}, + {vsn, "1.0"}, + {modules, [z1]}, + {registered, []}, + {applications, [kernel, stdlib]}]}. diff --git a/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl new file mode 100644 index 0000000000..97ef90b87f --- /dev/null +++ b/lib/reltool/test/reltool_server_SUITE_data/dep_in_app_not_xref/z-1.0/src/z1.erl @@ -0,0 +1,5 @@ +-module(z1). +-compile(export_all). + +f() -> + ok. diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index ad58bb6b7b..10d2539b7f 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -1063,6 +1063,12 @@ otp_9395_check_and_purge(cleanup,_Conf) -> %% OTP-9395 - performance problems when there are MANY processes %% Upgrade which updates many modules (brutal_purge) otp_9395_update_many_mods(Conf) when is_list(Conf) -> + + %% "nain" is very slow - it fails this test quite often due to a + %% long sys call + %% /proc/cpuinfo: "clock: 1249MHz" + inet:gethostname() == {ok,"nain"} andalso throw({skip,"slow test host"}), + %% Set some paths PrivDir = priv_dir(Conf), Dir = filename:join(PrivDir,"otp_9395_update_many_mods"), @@ -1162,6 +1168,12 @@ otp_9395_update_many_mods(cleanup,_Conf) -> %% OTP-9395 - performance problems when there are MANY processes %% Upgrade which removes many modules (brutal_purge) otp_9395_rm_many_mods(Conf) when is_list(Conf) -> + + %% "nain" is very slow - it fails this test quite often due to a + %% long sys call + %% /proc/cpuinfo: "clock: 1249MHz" + inet:gethostname() == {ok,"nain"} andalso throw({skip,"slow test host"}), + %% Set some paths PrivDir = priv_dir(Conf), Dir = filename:join(PrivDir,"otp_9395_rm_many_mods"), @@ -1920,7 +1932,7 @@ wait_nodes_up(Nodes, Tag) -> wait_nodes_up(Nodes0, Tag, Apps) -> ?t:format("wait_nodes_up(~p, ~p, ~p):",[Nodes0, Tag, Apps]), Nodes = fix_nodes(Nodes0), - wait_nodes_up(Nodes, Tag, lists:umerge(Apps,[kernel,stdlib,sasl]), 30). + wait_nodes_up(Nodes, Tag, lists:umerge(Apps,[kernel,stdlib,sasl]), 60). fix_nodes([{Node,InitPid}|Nodes]) -> [{Node,InitPid} | fix_nodes(Nodes)]; @@ -1962,7 +1974,7 @@ wait_nodes_up(Nodes, Tag, Apps, N) -> ?t:format("",[]), ok; _ -> - timer:sleep(1000), + timer:sleep(2000), wait_nodes_up(Pang, Tag, Apps, N-1) end. diff --git a/lib/sasl/test/rh_test_lib.erl b/lib/sasl/test/rh_test_lib.erl index 11935496d8..dacd8b6b9f 100644 --- a/lib/sasl/test/rh_test_lib.erl +++ b/lib/sasl/test/rh_test_lib.erl @@ -18,7 +18,7 @@ cmd(Cmd,Args,Env) -> case open_port({spawn_executable, Cmd}, [{args,Args},{env,Env}]) of Port when is_port(Port) -> unlink(Port), - erlang:port_close(Port), + catch erlang:port_close(Port), % migth already be closed, so catching ok; Error -> Error diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 3b6f988a2d..9d68ee0eee 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -28,6 +28,26 @@ <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 8.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + The TLS/SSL protocol version selection for the SSL server + has been corrected to follow RFC 5246 Appendix E.1 + especially in case where the list of supported versions + has gaps. Now the server selects the highest protocol + version it supports that is not higher than what the + client supports.</p> + <p> + Own Id: OTP-13753 Aux Id: seq13150 </p> + </item> + </list> + </section> + +</section> + <section><title>SSL 8.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 11728128c4..fadc67ef80 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,14 +1,20 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"^8[.]0$">>, + [{load_module, ssl_handshake, soft_purge, soft_purge, []} + ]}, + {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"7\\..*">>, [{restart_application, ssl}]}, + {<<"^8[.]0$">>, + [{load_module, ssl_handshake, soft_purge, soft_purge, []} + ]}, + {<<"^7[.][^.].*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, {<<"4\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 53282998d0..adee59393e 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -528,13 +528,12 @@ certify(internal, #server_key_exchange{exchange_keys = Keys}, end end; -certify(internal, #certificate_request{hashsign_algorithms = HashSigns}, +certify(internal, #certificate_request{} = CertRequest, #state{session = #session{own_certificate = Cert}, - key_algorithm = KeyExAlg, + role = client, ssl_options = #ssl_options{signature_algs = SupportedHashSigns}, negotiated_version = Version} = State0, Connection) -> - - case ssl_handshake:select_hashsign(HashSigns, Cert, KeyExAlg, SupportedHashSigns, Version) of + case ssl_handshake:select_hashsign(CertRequest, Cert, SupportedHashSigns, Version) of #alert {} = Alert -> Connection:handle_own_alert(Alert, Version, certify, State0); NegotiatedHashSign -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 9c3fe9d73b..081efda768 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -74,7 +74,7 @@ ]). %% MISC --export([select_version/3, prf/6, select_hashsign/5, +-export([select_version/3, prf/6, select_hashsign/4, select_hashsign/5, select_hashsign_algs/3, premaster_secret/2, premaster_secret/3, premaster_secret/4]). @@ -581,7 +581,7 @@ prf({3,_N}, PRFAlgo, Secret, Label, Seed, WantedLength) -> {atom(), atom()} | undefined | #alert{}. %% -%% Description: Handles signature_algorithms extension +%% Description: Handles signature_algorithms hello extension (server) %%-------------------------------------------------------------------- select_hashsign(_, undefined, _, _, _Version) -> {null, anon}; @@ -593,14 +593,17 @@ select_hashsign(HashSigns, Cert, KeyExAlgo, select_hashsign(#hash_sign_algos{hash_sign_algos = HashSigns}, Cert, KeyExAlgo, SupportedHashSigns, {Major, Minor}) when Major >= 3 andalso Minor >= 3 -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), - #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, - Sign = cert_sign(Algo), - case lists:filter(fun({sha, dsa = S}) when S == Sign -> - true; - ({_, dsa}) -> - false; - ({_, _} = Algos) -> - is_acceptable_hash_sign(Algos, Sign, KeyExAlgo, SupportedHashSigns); + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSing = sign_algo(SubjAlgo), + + case lists:filter(fun({_, S} = Algos) when S == Sign -> + is_acceptable_hash_sign(Algos, Sign, + SubSing, KeyExAlgo, SupportedHashSigns); (_) -> false end, HashSigns) of @@ -613,6 +616,49 @@ select_hashsign(_, Cert, _, _, Version) -> #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), #'OTPSubjectPublicKeyInfo'{algorithm = {_,Algo, _}} = TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, select_hashsign_algs(undefined, Algo, Version). +%%-------------------------------------------------------------------- +-spec select_hashsign(#certificate_request{}, binary(), + [atom()], ssl_record:ssl_version()) -> + {atom(), atom()} | #alert{}. + +%% +%% Description: Handles signature algorithms selection for certificate requests (client) +%%-------------------------------------------------------------------- +select_hashsign(#certificate_request{}, undefined, _, {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + %% There client does not have a certificate and will send an empty reply, the server may fail + %% or accept the connection by its own preference. No signature algorihms needed as there is + %% no certificate to verify. + {undefined, undefined}; + +select_hashsign(#certificate_request{hashsign_algorithms = #hash_sign_algos{hash_sign_algos = HashSigns}, + certificate_types = Types}, Cert, SupportedHashSigns, + {Major, Minor}) when Major >= 3 andalso Minor >= 3-> + #'OTPCertificate'{tbsCertificate = TBSCert} = public_key:pkix_decode_cert(Cert, otp), + #'OTPCertificate'{tbsCertificate = TBSCert, + signatureAlgorithm = {_,SignAlgo, _}} = public_key:pkix_decode_cert(Cert, otp), + #'OTPSubjectPublicKeyInfo'{algorithm = {_, SubjAlgo, _}} = + TBSCert#'OTPTBSCertificate'.subjectPublicKeyInfo, + + Sign = sign_algo(SignAlgo), + SubSign = sign_algo(SubjAlgo), + + case is_acceptable_cert_type(SubSign, HashSigns, Types) andalso is_supported_sign(Sign, HashSigns) of + true -> + case lists:filter(fun({_, S} = Algos) when S == SubSign -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); + (_) -> + false + end, HashSigns) of + [] -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm); + [HashSign | _] -> + HashSign + end; + false -> + ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_signature_algorithm) + end; +select_hashsign(#certificate_request{}, Cert, _, Version) -> + select_hashsign(undefined, Cert, undefined, [], Version). %%-------------------------------------------------------------------- -spec select_hashsign_algs({atom(), atom()}| undefined, oid(), ssl_record:ssl_version()) -> @@ -648,6 +694,7 @@ select_hashsign_algs(undefined, ?rsaEncryption, _) -> select_hashsign_algs(undefined, ?'id-dsa', _) -> {sha, dsa}. + %%-------------------------------------------------------------------- -spec master_secret(atom(), ssl_record:ssl_version(), #session{} | binary(), #connection_states{}, client | server) -> {binary(), #connection_states{}} | #alert{}. @@ -1143,11 +1190,13 @@ certificate_types(_, {N, M}) when N >= 3 andalso M >= 3 -> end; certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == rsa; + KeyExchange == dh_rsa; KeyExchange == dhe_rsa; KeyExchange == ecdhe_rsa -> <<?BYTE(?RSA_SIGN)>>; -certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dhe_dss; +certificate_types({KeyExchange, _, _, _}, _) when KeyExchange == dh_dss; + KeyExchange == dhe_dss; KeyExchange == srp_dss -> <<?BYTE(?DSS_SIGN)>>; @@ -1256,8 +1305,40 @@ handle_server_hello_extensions(RecordCB, Random, CipherSuite, Compression, end. select_version(RecordCB, ClientVersion, Versions) -> - ServerVersion = RecordCB:highest_protocol_version(Versions), - RecordCB:lowest_protocol_version(ClientVersion, ServerVersion). + do_select_version(RecordCB, ClientVersion, Versions). + +do_select_version(_, ClientVersion, []) -> + ClientVersion; +do_select_version(RecordCB, ClientVersion, [Version | Versions]) -> + case RecordCB:is_higher(Version, ClientVersion) of + true -> + %% Version too high for client - keep looking + do_select_version(RecordCB, ClientVersion, Versions); + false -> + %% Version ok for client - look for a higher + do_select_version(RecordCB, ClientVersion, Versions, Version) + end. +%% +do_select_version(_, _, [], GoodVersion) -> + GoodVersion; +do_select_version( + RecordCB, ClientVersion, [Version | Versions], GoodVersion) -> + BetterVersion = + case RecordCB:is_higher(Version, ClientVersion) of + true -> + %% Version too high for client + GoodVersion; + false -> + %% Version ok for client + case RecordCB:is_higher(Version, GoodVersion) of + true -> + %% Use higher version + Version; + false -> + GoodVersion + end + end, + do_select_version(RecordCB, ClientVersion, Versions, BetterVersion). renegotiation_info(_, client, _, false) -> #renegotiation_info{renegotiated_connection = undefined}; @@ -2164,27 +2245,73 @@ distpoints_lookup([DistPoint | Rest], Issuer, Callback, CRLDbHandle) -> [{DistPoint, {CRL, public_key:der_decode('CertificateList', CRL)}} || CRL <- CRLs] end. -cert_sign(?rsaEncryption) -> +sign_algo(?rsaEncryption) -> rsa; -cert_sign(?'id-ecPublicKey') -> +sign_algo(?'id-ecPublicKey') -> ecdsa; -cert_sign(?'id-dsa') -> +sign_algo(?'id-dsa') -> dsa; -cert_sign(Alg) -> +sign_algo(Alg) -> {_, Sign} =public_key:pkix_sign_types(Alg), Sign. -is_acceptable_hash_sign({_, Sign} = Algos, Sign, _, SupportedHashSigns) -> - is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(Algos,_, KeyExAlgo, SupportedHashSigns) when KeyExAlgo == dh_ecdsa; - KeyExAlgo == ecdh_rsa; - KeyExAlgo == ecdh_ecdsa -> +is_acceptable_hash_sign(Algos, _, _, KeyExAlgo, SupportedHashSigns) when + KeyExAlgo == dh_dss; + KeyExAlgo == dh_rsa; + KeyExAlgo == dh_ecdsa -> + %% dh_* could be called only dh in TLS-1.2 + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(Algos, rsa, ecdsa, ecdh_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, dhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, ecdhe_rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, rsa, rsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, srp_rsa, SupportedHashSigns) -> is_acceptable_hash_sign(Algos, SupportedHashSigns); -is_acceptable_hash_sign(_,_,_,_) -> - false. +is_acceptable_hash_sign({_, rsa} = Algos, rsa, _, rsa_psk, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, dhe_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, dsa} = Algos, dsa, _, srp_dss, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, _, dhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign({_, ecdsa} = Algos, ecdsa, ecdsa, ecdhe_ecdsa, SupportedHashSigns) -> + is_acceptable_hash_sign(Algos, SupportedHashSigns); +is_acceptable_hash_sign(_, _, _, KeyExAlgo, _) when + KeyExAlgo == psk; + KeyExAlgo == dhe_psk; + KeyExAlgo == srp_anon; + KeyExAlgo == dh_anon; + KeyExAlgo == ecdhe_anon + -> + true; +is_acceptable_hash_sign(_,_, _,_,_) -> + false. + is_acceptable_hash_sign(Algos, SupportedHashSigns) -> lists:member(Algos, SupportedHashSigns). +is_acceptable_cert_type(Sign, _HashSigns, Types) -> + lists:member(sign_type(Sign), binary_to_list(Types)). + +is_supported_sign(Sign, HashSigns) -> + [] =/= lists:dropwhile(fun({_, S}) when S =/= Sign -> + true; + (_)-> + false + end, HashSigns). +sign_type(rsa) -> + ?RSA_SIGN; +sign_type(dsa) -> + ?DSS_SIGN; +sign_type(ecdsa) -> + ?ECDSA_SIGN. + + bad_key(#'DSAPrivateKey'{}) -> unacceptable_dsa_key; bad_key(#'RSAPrivateKey'{}) -> diff --git a/lib/ssl/src/ssl_tls_dist_proxy.erl b/lib/ssl/src/ssl_tls_dist_proxy.erl index a920f54ed2..08947f24dd 100644 --- a/lib/ssl/src/ssl_tls_dist_proxy.erl +++ b/lib/ssl/src/ssl_tls_dist_proxy.erl @@ -117,7 +117,7 @@ handle_call({listen, Driver, Name}, _From, State) -> {ok, WorldTcpAddress} = get_tcp_address(World), {_,Port} = WorldTcpAddress#net_address.address, ErlEpmd = net_kernel:epmd_module(), - case ErlEpmd:register_node(Name, Port) of + case ErlEpmd:register_node(Name, Port, Driver) of {ok, Creation} -> {reply, {ok, {Socket, TcpAddress, Creation}}, State#state{listen={Socket, World}}}; diff --git a/lib/ssl/test/ssl_ECC_SUITE.erl b/lib/ssl/test/ssl_ECC_SUITE.erl index b8a03f578d..69ac9908fa 100644 --- a/lib/ssl/test/ssl_ECC_SUITE.erl +++ b/lib/ssl/test/ssl_ECC_SUITE.erl @@ -159,42 +159,42 @@ end_per_testcase(_TestCase, Config) -> client_ecdh_server_ecdh(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdh_server_rsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdh(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdh_rsa_opts, Config), - SOpts = proplists:get_value(server_ecdh_rsa_verify_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdh_rsa_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_rsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_verification_opts, Config), - SOpts = proplists:get_value(server_verification_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_ecdsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_rsa(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_opts, Config), basic_test(COpts, SOpts, Config). client_rsa_server_ecdsa(Config) when is_list(Config) -> - COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + COpts = proplists:get_value(client_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), basic_test(COpts, SOpts, Config). client_ecdsa_server_ecdsa_with_raw_key(Config) when is_list(Config) -> COpts = proplists:get_value(client_ecdsa_opts, Config), - SOpts = proplists:get_value(server_ecdsa_verify_opts, Config), + SOpts = proplists:get_value(server_ecdsa_opts, Config), ServerCert = proplists:get_value(certfile, SOpts), ServerKeyFile = proplists:get_value(keyfile, SOpts), {ok, PemBin} = file:read_file(ServerKeyFile), @@ -244,20 +244,20 @@ basic_test(ClientCert, ClientKey, ClientCA, ServerCert, ServerKey, ServerCA, Con check_result(Server, SType, Client, CType), close(Server, Client). -start_client(openssl, Port, CA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), +start_client(openssl, Port, PeerCA, OwnCa, Cert, Key, _Config) -> + CA = new_openssl_ca("openssl_client_ca", PeerCA, OwnCa), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-cert", Cert, "-CAfile", NewCA, + "-cert", Cert, "-CAfile", CA, "-key", Key, "-host","localhost", "-msg", "-debug"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), OpenSslPort; -start_client(erlang, Port, CA, _, Cert, Key, Config) -> +start_client(erlang, Port, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_client_ca", PeerCA, OwnCa), {ClientNode, _, Hostname} = ssl_test_lib:run_where(Config), ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, {host, Hostname}, @@ -267,20 +267,19 @@ start_client(erlang, Port, CA, _, Cert, Key, Config) -> {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]). -start_server(openssl, CA, OwnCa, Cert, Key, Config) -> - PrivDir = proplists:get_value(priv_dir, Config), - NewCA = new_ca(filename:join(PrivDir, "new_ca.pem"), CA, OwnCa), - +start_server(openssl, PeerCA, OwnCa, Cert, Key, _Config) -> + CA = new_openssl_ca("openssl_server_ca", PeerCA, OwnCa), Port = ssl_test_lib:inet_port(node()), Version = tls_record:protocol_version(tls_record:highest_protocol_version([])), Exe = "openssl", Args = ["s_server", "-accept", integer_to_list(Port), ssl_test_lib:version_flag(Version), - "-verify", "2", "-cert", Cert, "-CAfile", NewCA, + "-verify", "2", "-cert", Cert, "-CAfile", CA, "-key", Key, "-msg", "-debug"], OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), {OpenSslPort, Port}; -start_server(erlang, CA, _, Cert, Key, Config) -> +start_server(erlang, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -291,7 +290,8 @@ start_server(erlang, CA, _, Cert, Key, Config) -> [{verify, verify_peer}, {cacertfile, CA}, {certfile, Cert}, {keyfile, Key}]}]), {Server, ssl_test_lib:inet_port(Server)}. -start_server_with_raw_key(erlang, CA, _, Cert, Key, Config) -> +start_server_with_raw_key(erlang, PeerCA, OwnCa, Cert, Key, Config) -> + CA = new_ca("erlang_server_ca", PeerCA, OwnCa), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, {from, self()}, @@ -336,17 +336,27 @@ close(Client, Server) -> ssl_test_lib:close(Server), ssl_test_lib:close(Client). -%% Work around OpenSSL bug, apparently the same bug as we had fixed in -%% 11629690ba61f8e0c93ef9b2b6102fd279825977 new_ca(FileName, CA, OwnCa) -> {ok, P1} = file:read_file(CA), E1 = public_key:pem_decode(P1), {ok, P2} = file:read_file(OwnCa), E2 = public_key:pem_decode(P2), + Pem = public_key:pem_encode(E1 ++E2), + file:write_file(FileName, Pem), + FileName. + +new_openssl_ca(FileName, CA, OwnCa) -> + {ok, P1} = file:read_file(CA), + E1 = public_key:pem_decode(P1), + {ok, P2} = file:read_file(OwnCa), + E2 = public_key:pem_decode(P2), case os:cmd("openssl version") of "OpenSSL 1.0.1p-freebsd" ++ _ -> Pem = public_key:pem_encode(E1 ++E2), file:write_file(FileName, Pem); + "LibreSSL" ++ _ -> + Pem = public_key:pem_encode(E1 ++E2), + file:write_file(FileName, Pem); _ -> Pem = public_key:pem_encode(E2 ++E1), file:write_file(FileName, Pem) diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index efa5faa218..665dbb1df3 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -408,8 +408,13 @@ init_per_testcase(TestCase, Config) when TestCase == tls_ssl_accept_timeout; ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 15}), Config; -init_per_testcase(clear_pem_cache, Config) -> +init_per_testcase(TestCase, Config) when TestCase == clear_pem_cache; + TestCase == der_input; + TestCase == defaults -> ssl_test_lib:ct_log_supported_protocol_versions(Config), + %% White box test need clean start + ssl:stop(), + ssl:start(), ct:timetrap({seconds, 20}), Config; init_per_testcase(raw_ssl_option, Config) -> @@ -567,8 +572,8 @@ prf(Config) when is_list(Config) -> connection_info() -> [{doc,"Test the API function ssl:connection_information/1"}]. connection_info(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1144,8 +1149,8 @@ cipher_suites_mix() -> cipher_suites_mix(Config) when is_list(Config) -> CipherSuites = [{ecdh_rsa,aes_128_cbc,sha256,sha256}, {rsa,aes_128_cbc,sha}], - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -4409,14 +4414,14 @@ run_suites(Ciphers, Version, Config, Type) -> {ClientOpts, ServerOpts} = case Type of rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), - ssl_test_lib:ssl_options(server_opts, Config)}; + {ssl_test_lib:ssl_options(client_verification_opts, Config), + ssl_test_lib:ssl_options(server_verification_opts, Config)}; dsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_dsa_opts, Config)}; anonymous -> %% No certs in opts! - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_anon, Config)}; psk -> {ssl_test_lib:ssl_options(client_psk, Config), @@ -4440,31 +4445,31 @@ run_suites(Ciphers, Version, Config, Type) -> {ssl_test_lib:ssl_options(client_srp_dsa, Config), ssl_test_lib:ssl_options(server_srp_dsa, Config)}; ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_ecdsa_opts, Config)}; ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)}; rc4_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]}; + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; rc4_ecdh_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdh_rsa_opts, Config)]}; rc4_ecdsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | ssl_test_lib:ssl_options(server_ecdsa_opts, Config)]}; des_dhe_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]}; + ssl_test_lib:ssl_options(server_verification_opts, Config)]}; des_rsa -> - {ssl_test_lib:ssl_options(client_opts, Config), + {ssl_test_lib:ssl_options(client_verification_opts, Config), [{ciphers, Ciphers} | - ssl_test_lib:ssl_options(server_opts, Config)]} + ssl_test_lib:ssl_options(server_verification_opts, Config)]} end, Result = lists:map(fun(Cipher) -> diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl index 20165c70f0..c83c513eb3 100644 --- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl +++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl @@ -65,9 +65,10 @@ tests() -> cert_expired, invalid_signature_client, invalid_signature_server, - extended_key_usage_verify_peer, - extended_key_usage_verify_none, - critical_extension_verify_peer, + extended_key_usage_verify_client, + extended_key_usage_verify_server, + critical_extension_verify_client, + critical_extension_verify_server, critical_extension_verify_none]. error_handling_tests()-> @@ -122,6 +123,8 @@ init_per_testcase(TestCase, Config) when TestCase == cert_expired; ssl:clear_pem_cache(), init_per_testcase(common, Config); init_per_testcase(_TestCase, Config) -> + ssl:stop(), + ssl:start(), ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), Config. @@ -136,7 +139,7 @@ end_per_testcase(_TestCase, Config) -> verify_peer() -> [{doc,"Test option verify_peer"}]. verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -190,7 +193,7 @@ server_verify_client_once() -> [{doc,"Test server option verify_client_once"}]. server_verify_client_once(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -230,7 +233,7 @@ server_require_peer_cert_ok() -> server_require_peer_cert_ok(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -259,7 +262,7 @@ server_require_peer_cert_fail() -> server_require_peer_cert_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - BadClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + BadClientOpts = ssl_test_lib:ssl_options(client_opts, []), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -290,7 +293,7 @@ server_require_peer_cert_partial_chain() -> server_require_peer_cert_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), @@ -325,13 +328,13 @@ server_require_peer_cert_allow_partial_chain() -> server_require_peer_cert_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), - {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), - [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ServerCAs), + {ok, ClientCAs} = file:read_file(proplists:get_value(cacertfile, ClientOpts)), + [{_,_,_}, {_, IntermidiateCA, _}] = public_key:pem_decode(ClientCAs), PartialChain = fun(CertChain) -> case lists:member(IntermidiateCA, CertChain) of @@ -367,7 +370,7 @@ server_require_peer_cert_do_not_allow_partial_chain() -> server_require_peer_cert_do_not_allow_partial_chain(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), @@ -408,7 +411,7 @@ server_require_peer_cert_partial_chain_fun_fail() -> server_require_peer_cert_partial_chain_fun_fail(Config) when is_list(Config) -> ServerOpts = [{verify, verify_peer}, {fail_if_no_peer_cert, true} | ssl_test_lib:ssl_options(server_verification_opts, Config)], - ClientOpts = proplists:get_value(client_verification_opts, Config), + ClientOpts = proplists:get_value(client_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), {ok, ServerCAs} = file:read_file(proplists:get_value(cacertfile, ServerOpts)), @@ -448,7 +451,7 @@ verify_fun_always_run_client() -> verify_fun_always_run_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -492,7 +495,7 @@ verify_fun_always_run_client(Config) when is_list(Config) -> verify_fun_always_run_server() -> [{doc,"Verify that user verify_fun is always run (for valid and valid_peer not only unknown_extension)"}]. verify_fun_always_run_server(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), @@ -524,9 +527,7 @@ verify_fun_always_run_server(Config) when is_list(Config) -> {from, self()}, {mfa, {ssl_test_lib, no_result, []}}, - {options, - [{verify, verify_peer} - | ClientOpts]}]), + {options, ClientOpts}]), %% Client error may be {tls_alert, "handshake failure" } or closed depending on timing %% this is not a bug it is a circumstance of how tcp works! @@ -544,7 +545,7 @@ cert_expired() -> cert_expired(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), @@ -607,11 +608,11 @@ two_digits_str(N) -> lists:flatten(io_lib:format("~p", [N])). %%-------------------------------------------------------------------- -extended_key_usage_verify_peer() -> - [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode"}]. +extended_key_usage_verify_server() -> + [{doc,"Test cert that has a critical extended_key_usage extension in verify_peer mode for server"}]. -extended_key_usage_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), +extended_key_usage_verify_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), @@ -660,7 +661,7 @@ extended_key_usage_verify_peer(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | + {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), ssl_test_lib:check_result(Server, ok, Client, ok), @@ -669,12 +670,12 @@ extended_key_usage_verify_peer(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -extended_key_usage_verify_none() -> - [{doc,"Test cert that has a critical extended_key_usage extension in verify_none mode"}]. +extended_key_usage_verify_client() -> + [{doc,"Test cert that has a critical extended_key_usage extension in client verify_peer mode"}]. -extended_key_usage_verify_none(Config) when is_list(Config) -> +extended_key_usage_verify_client(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -730,11 +731,11 @@ extended_key_usage_verify_none(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- -critical_extension_verify_peer() -> +critical_extension_verify_server() -> [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. -critical_extension_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), +critical_extension_verify_server(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), @@ -766,7 +767,7 @@ critical_extension_verify_peer(Config) when is_list(Config) -> {host, Hostname}, {from, self()}, {mfa, {ssl_test_lib, ReceiveFunction, []}}, - {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + {options, [{verify, verify_none}, {active, Active} | NewClientOpts]}]), %% This certificate has a critical extension that we don't %% understand. Therefore, verification should fail. @@ -775,14 +776,60 @@ critical_extension_verify_peer(Config) when is_list(Config) -> ssl_test_lib:close(Server), ok. +%%-------------------------------------------------------------------- + +critical_extension_verify_client() -> + [{doc,"Test cert that has a critical unknown extension in verify_peer mode"}]. + +critical_extension_verify_client(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + PrivDir = proplists:get_value(priv_dir, Config), + Active = proplists:get_value(active, Config), + ReceiveFunction = proplists:get_value(receive_function, Config), + + KeyFile = filename:join(PrivDir, "otpCA/private/key.pem"), + NewCertName = integer_to_list(erlang:unique_integer()) ++ ".pem", + + ServerCertFile = proplists:get_value(certfile, ServerOpts), + NewServerCertFile = filename:join([PrivDir, "server", NewCertName]), + add_critical_netscape_cert_type(ServerCertFile, NewServerCertFile, KeyFile), + NewServerOpts = [{certfile, NewServerCertFile} | proplists:delete(certfile, ServerOpts)], + + ClientCertFile = proplists:get_value(certfile, ClientOpts), + NewClientCertFile = filename:join([PrivDir, "client", NewCertName]), + add_critical_netscape_cert_type(ClientCertFile, NewClientCertFile, KeyFile), + NewClientOpts = [{certfile, NewClientCertFile} | proplists:delete(certfile, ClientOpts)], + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server_error( + [{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_none}, {active, Active} | NewServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client_error( + [{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, ReceiveFunction, []}}, + {options, [{verify, verify_peer}, {active, Active} | NewClientOpts]}]), + + %% This certificate has a critical extension that we don't + %% understand. Therefore, verification should fail. + tcp_delivery_workaround(Server, {error, {tls_alert, "unsupported certificate"}}, + Client, {error, {tls_alert, "unsupported certificate"}}), + ssl_test_lib:close(Server), + ok. %%-------------------------------------------------------------------- critical_extension_verify_none() -> [{doc,"Test cert that has a critical unknown extension in verify_none mode"}]. critical_extension_verify_none(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_verification_opts, Config), - ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), PrivDir = proplists:get_value(priv_dir, Config), Active = proplists:get_value(active, Config), ReceiveFunction = proplists:get_value(receive_function, Config), @@ -1070,7 +1117,7 @@ client_with_cert_cipher_suites_handshake(Config) when is_list(Config) -> server_verify_no_cacerts() -> [{doc,"Test server must have cacerts if it wants to verify client"}]. server_verify_no_cacerts(Config) when is_list(Config) -> - ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + ServerOpts = proplists:delete(cacertfile, ssl_test_lib:ssl_options(server_opts, Config)), {_, ServerNode, _} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, {from, self()}, @@ -1084,7 +1131,7 @@ server_verify_no_cacerts(Config) when is_list(Config) -> unknown_server_ca_fail() -> [{doc,"Test that the client fails if the ca is unknown in verify_peer mode"}]. unknown_server_ca_fail(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server_error([{node, ServerNode}, {port, 0}, @@ -1128,7 +1175,7 @@ unknown_server_ca_fail(Config) when is_list(Config) -> unknown_server_ca_accept_verify_none() -> [{doc,"Test that the client succeds if the ca is unknown in verify_none mode"}]. unknown_server_ca_accept_verify_none(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1153,7 +1200,7 @@ unknown_server_ca_accept_verify_peer() -> [{doc, "Test that the client succeds if the ca is unknown in verify_peer mode" " with a verify_fun that accepts the unknown ca error"}]. unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts =ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, @@ -1192,7 +1239,7 @@ unknown_server_ca_accept_verify_peer(Config) when is_list(Config) -> unknown_server_ca_accept_backwardscompatibility() -> [{doc,"Test that old style verify_funs will work"}]. unknown_server_ca_accept_backwardscompatibility(Config) when is_list(Config) -> - ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ClientOpts = ssl_test_lib:ssl_options(client_opts, []), ServerOpts = ssl_test_lib:ssl_options(server_verification_opts, Config), {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, diff --git a/lib/ssl/test/ssl_npn_hello_SUITE.erl b/lib/ssl/test/ssl_npn_hello_SUITE.erl index 00eb9fee4f..6ae9efe5e9 100644 --- a/lib/ssl/test/ssl_npn_hello_SUITE.erl +++ b/lib/ssl/test/ssl_npn_hello_SUITE.erl @@ -41,6 +41,15 @@ all() -> create_server_hello_with_advertised_protocols_test, create_server_hello_with_no_advertised_protocols_test]. +init_per_suite(Config) -> + catch crypto:stop(), + try crypto:start() of + ok -> + Config + catch _:_ -> + {skip, "Crypto did not start"} + end. + init_per_testcase(_TestCase, Config) -> ssl_test_lib:ct_log_supported_protocol_versions(Config), ct:timetrap({seconds, 5}), diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 27c670cdc2..fd8af5efaa 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -385,7 +385,9 @@ cert_options(Config) -> SNIServerAKeyFile = filename:join([proplists:get_value(priv_dir, Config), "a.server", "key.pem"]), SNIServerBCertFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "cert.pem"]), SNIServerBKeyFile = filename:join([proplists:get_value(priv_dir, Config), "b.server", "key.pem"]), - [{client_opts, []}, + [{client_opts, [{cacertfile, ClientCaCertFile}, + {certfile, ClientCertFile}, + {keyfile, ClientKeyFile}]}, {client_verification_opts, [{cacertfile, ServerCaCertFile}, {certfile, ClientCertFile}, {keyfile, ClientKeyFile}, @@ -394,7 +396,7 @@ cert_options(Config) -> {certfile, ClientCertFileDigitalSignatureOnly}, {keyfile, ClientKeyFile}, {ssl_imp, new}]}, - {server_opts, [{ssl_imp, new},{reuseaddr, true}, + {server_opts, [{ssl_imp, new},{reuseaddr, true}, {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_anon, [{ssl_imp, new},{reuseaddr, true}, {ciphers, anonymous_suites()}]}, {client_psk, [{ssl_imp, new},{reuseaddr, true}, @@ -494,7 +496,7 @@ make_ecdsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, + {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -519,7 +521,7 @@ make_ecdh_rsa_cert(Config) -> {cacertfile, ServerCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}]}, {server_ecdh_rsa_verify_opts, [{ssl_imp, new},{reuseaddr, true}, - {cacertfile, ServerCaCertFile}, + {cacertfile, ClientCaCertFile}, {certfile, ServerCertFile}, {keyfile, ServerKeyFile}, {verify, verify_peer}]}, {client_ecdh_rsa_opts, [{ssl_imp, new},{reuseaddr, true}, @@ -815,6 +817,12 @@ rsa_suites(CounterPart) -> true; ({ecdhe_rsa, _, _}) when ECC == true -> true; + ({rsa, _, _, _}) -> + true; + ({dhe_rsa, _, _,_}) -> + true; + ({ecdhe_rsa, _, _,_}) when ECC == true -> + true; (_) -> false end, diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 3b51fa8c6b..6afac59109 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 8.0 +SSL_VSN = 8.0.1 diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c57a31fa21..ed44eef912 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -1262,9 +1262,9 @@ handle_event(_, _, State, Data) -> <v> Vsn = term()</v> <v>OldState = NewState = term()</v> <v>Extra = term()</v> - <v>Result = {NewCallbackMode,NewState,NewData} | Reason</v> + <v>Result = {CallbackMode,NewState,NewData} | Reason</v> <v> - NewCallbackMode = + CallbackMode = <seealso marker="#type-callback_mode">callback_mode()</seealso> </v> <v> @@ -1321,11 +1321,19 @@ handle_event(_, _, State, Data) -> <p> If successful, the function must return the updated internal state in an - <c>{NewCallbackMode,NewState,NewData}</c> tuple. + <c>{CallbackMode,NewState,NewData}</c> tuple. </p> <p> - If the function returns <c>Reason</c>, the ongoing - upgrade fails and rolls back to the old release.</p> + If the function returns a failure <c>Reason</c>, the ongoing + upgrade fails and rolls back to the old release. + Note that <c>Reason</c> can not be a 3-tuple since that + will be regarded as a + <c>{CallbackMode,NewState,NewData}</c> tuple, + and that a tuple matching <c>{ok,_}</c> + is an invalid failure <c>Reason</c>. + It is recommended to use an atom as <c>Reason</c> since + it will be wrapped in an <c>{error,Reason}</c> tuple. + </p> <p> This function can use <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml index ad2599c5a0..d8fec1147f 100644 --- a/lib/stdlib/doc/src/notes.xml +++ b/lib/stdlib/doc/src/notes.xml @@ -31,6 +31,21 @@ </header> <p>This document describes the changes made to the STDLIB application.</p> +<section><title>STDLIB 3.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> Correct a bug regarding typed records in the Erlang + shell. The bug was introduced in OTP-19.0. </p> + <p> + Own Id: OTP-13719 Aux Id: ERL-182 </p> + </item> + </list> + </section> + +</section> + <section><title>STDLIB 3.0</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 23bddafeed..c02e6a1a19 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -219,9 +219,10 @@ OldState :: state(), OldData :: data(), Extra :: term()) -> - {NewCallbackMode :: callback_mode(), + {CallbackMode :: callback_mode(), NewState :: state(), - NewData :: data()}. + NewData :: data()} | + (Reason :: term()). %% Format the callback module state in some sensible that is %% often condensed way. For StatusOption =:= 'normal' the perferred @@ -630,11 +631,11 @@ system_code_change( Result -> Result end of - {NewCallbackMode,NewState,NewData} -> - callback_mode(NewCallbackMode) orelse - error({callback_mode,NewCallbackMode}), + {CallbackMode,NewState,NewData} -> + callback_mode(CallbackMode) orelse + error({callback_mode,CallbackMode}), {ok, - S#{callback_mode := NewCallbackMode, + S#{callback_mode := CallbackMode, state := NewState, data := NewData}}; {ok,_} = Error -> diff --git a/lib/stdlib/src/shell.erl b/lib/stdlib/src/shell.erl index 82a3a2be4f..28f37ef8bf 100644 --- a/lib/stdlib/src/shell.erl +++ b/lib/stdlib/src/shell.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2015. All Rights Reserved. +%% Copyright Ericsson AB 1996-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -768,6 +768,8 @@ used_records({call,_,{atom,_,record_info},[A,{atom,_,Name}]}) -> {name, Name, A}; used_records({call,Line,{tuple,_,[M,F]},As}) -> used_records({call,Line,{remote,Line,M,F},As}); +used_records({type,_,record,[{atom,_,Name}|Fs]}) -> + {name, Name, Fs}; used_records(T) when is_tuple(T) -> {expr, tuple_to_list(T)}; used_records(E) -> diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index 8c1c625676..b02d17bdb6 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -47,6 +47,7 @@ fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1, update_element/1, update_counter/1, evil_update_counter/1, partly_bound/1, match_heavy/1]). -export([update_counter_with_default/1]). +-export([update_counter_table_growth/1]). -export([member/1]). -export([memory/1]). -export([select_fail/1]). @@ -100,6 +101,7 @@ heavy_lookup_element_do/1, member_do/1, otp_5340_do/1, otp_7665_do/1, meta_wb_do/1, do_heavy_concurrent/1, tab2file2_do/2, exit_large_table_owner_do/2, types_do/1, sleeper/0, memory_do/1, update_counter_with_default_do/1, + update_counter_table_growth_do/1, ms_tracee_dummy/1, ms_tracee_dummy/2, ms_tracee_dummy/3, ms_tracee_dummy/4 ]). @@ -137,6 +139,7 @@ all() -> rename, rename_unnamed, evil_rename, update_element, update_counter, evil_update_counter, update_counter_with_default, partly_bound, + update_counter_table_growth, match_heavy, {group, fold}, member, t_delete_object, t_init_table, t_whitebox, t_delete_all_objects, t_insert_list, t_test_ms, t_select_delete, t_ets_dets, @@ -1955,6 +1958,16 @@ update_counter_with_default_do(Opts) -> ok. +update_counter_table_growth(_Config) -> + repeat_for_opts(update_counter_table_growth_do). + +update_counter_table_growth_do(Opts) -> + Set = ets_new(b, [set | Opts]), + [ets:update_counter(Set, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)], + OrderedSet = ets_new(b, [ordered_set | Opts]), + [ets:update_counter(OrderedSet, N, {2, 1}, {N, 1}) || N <- lists:seq(1,10000)], + ok. + %% Check that a first-next sequence always works on a fixed table. fixtable_next(Config) when is_list(Config) -> repeat_for_opts(fixtable_next_do, [write_concurrency,all_types]). diff --git a/lib/stdlib/test/shell_SUITE.erl b/lib/stdlib/test/shell_SUITE.erl index dc82e92876..c409a6949b 100644 --- a/lib/stdlib/test/shell_SUITE.erl +++ b/lib/stdlib/test/shell_SUITE.erl @@ -30,7 +30,7 @@ progex_bit_syntax/1, progex_records/1, progex_lc/1, progex_funs/1, otp_5990/1, otp_6166/1, otp_6554/1, - otp_7184/1, otp_7232/1, otp_8393/1, otp_10302/1]). + otp_7184/1, otp_7232/1, otp_8393/1, otp_10302/1, otp_13719/1]). -export([ start_restricted_from_shell/1, start_restricted_on_command_line/1,restricted_local/1]). @@ -91,7 +91,7 @@ groups() -> progex_funs]}, {tickets, [], [otp_5990, otp_6166, otp_6554, otp_7184, - otp_7232, otp_8393, otp_10302]}]. + otp_7232, otp_8393, otp_10302, otp_13719]}]. init_per_suite(Config) -> Config. @@ -2810,6 +2810,19 @@ otp_10302(Config) when is_list(Config) -> test_server:stop_node(Node), ok. +otp_13719(Config) when is_list(Config) -> + Test = <<"-module(otp_13719). + -record(bar, {}). + -record(foo, {bar :: #bar{}}).">>, + File = filename("otp_13719.erl", Config), + Beam = filename("otp_13719.beam", Config), + ok = compile_file(Config, File, Test, []), + RR = "rr(\"" ++ Beam ++ "\"). #foo{}.", + "[bar,foo]\n#foo{bar = undefined}.\n" = t(RR), + file:delete(filename("test.beam", Config)), + file:delete(File), + ok. + scan(B) -> F = fun(Ts) -> case erl_parse:parse_term(Ts) of diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk index 46e3ceac03..41037b8f53 100644 --- a/lib/stdlib/vsn.mk +++ b/lib/stdlib/vsn.mk @@ -1 +1 @@ -STDLIB_VSN = 3.0 +STDLIB_VSN = 3.0.1 diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index ce26c83295..c1152f31a4 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -903,7 +903,7 @@ Please see the function `tempo-define-template'.") "%% gen_statem:start_link/[3,4], this function is called by the new" n "%% process to initialize." n (erlang-skel-separator-end 2) - "-spec init(Args :: term()) -> " n> + "-spec init(Args :: term()) ->" n> "{gen_statem:callback_mode()," n> "State :: term(), Data :: term()} |" n> "{gen_statem:callback_mode()," n> @@ -927,7 +927,7 @@ Please see the function `tempo-define-template'.") "-spec state_name(" n> "gen_statem:event_type(), Msg :: term()," n> "Data :: term()) ->" n> - "gen_statem:state_function_result(). " n + "gen_statem:state_function_result()." n "state_name({call,Caller}, _Msg, Data) ->" n> "{next_state, state_name, Data, [{reply,Caller,ok}]}." n n @@ -940,7 +940,7 @@ Please see the function `tempo-define-template'.") "-spec handle_event(" n> "gen_statem:event_type(), Msg :: term()," n> "State :: term(), Data :: term()) ->" n> - "gen_statem:handle_event_result(). " n + "gen_statem:handle_event_result()." n "handle_event({call,From}, _Msg, State, Data) ->" n> "{next_state, State, Data, [{reply,From,ok}]}." n n @@ -965,9 +965,11 @@ Please see the function `tempo-define-template'.") "-spec code_change(" n> "OldVsn :: term() | {down,term()}," n> "State :: term(), Data :: term(), Extra :: term()) ->" n> - "{ok, NewState :: term(), NewData :: term()}." n + "{gen_statem:callback_mode()," n> + "NewState :: term(), NewData :: term()} |" n> + "(Reason :: term())." n "code_change(_OldVsn, State, Data, _Extra) ->" n> - "{ok, State, Data}." n + "{state_functions, State, Data}." n n (erlang-skel-double-separator-start 3) "%%% Internal functions" n diff --git a/lib/tools/src/fprof.erl b/lib/tools/src/fprof.erl index 8db23dd151..1291a3e5ec 100644 --- a/lib/tools/src/fprof.erl +++ b/lib/tools/src/fprof.erl @@ -1636,6 +1636,11 @@ trace_handler({trace_ts, Pid, gc_major_start, _Func, TS} = Trace, Table, _, Dump dump_stack(Dump, get(Pid), Trace), trace_gc_start(Table, Pid, TS), TS; + +trace_handler({trace_ts, Pid, gc_start, _Func, TS} = Trace, Table, _, Dump) -> + dump_stack(Dump, get(Pid), Trace), + trace_gc_start(Table, Pid, TS), + TS; %% %% gc_end @@ -1648,6 +1653,12 @@ trace_handler({trace_ts, Pid, gc_major_end, _Func, TS} = Trace, Table, _, Dump) dump_stack(Dump, get(Pid), Trace), trace_gc_end(Table, Pid, TS), TS; + +trace_handler({trace_ts, Pid, gc_end, _Func, TS} = Trace, Table, _, Dump) -> + dump_stack(Dump, get(Pid), Trace), + trace_gc_end(Table, Pid, TS), + TS; + %% %% link trace_handler({trace_ts, Pid, link, _OtherPid, TS} = Trace, diff --git a/lib/wx/api_gen/wx_extra/wxListCtrl.erl b/lib/wx/api_gen/wx_extra/wxListCtrl.erl index acdb69fdeb..355a4cdfd1 100644 --- a/lib/wx/api_gen/wx_extra/wxListCtrl.erl +++ b/lib/wx/api_gen/wx_extra/wxListCtrl.erl @@ -43,33 +43,36 @@ SortItems>> <<EXPORT:wxListCtrl new/0, new/1, new/2 wxListCtrl:EXPORT>> <<wxListCtrl_new_0 -%% @spec () -> wxListCtrl() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new() -> wxListCtrl(). new() -> wxe_util:construct(~s, <<>>). wxListCtrl_new_0>> <<wxListCtrl_new_2 -%% @spec (Parent::wxWindow:wxWindow()) -> wxListCtrl() -%% @equiv new(Parent, []) +-spec new(Parent) -> wxListCtrl() when + Parent::wxWindow:wxWindow(). new(Parent) when is_record(Parent, wx_ref) -> new(Parent, []). -%% @spec (Parent::wxWindow:wxWindow(), [Option]) -> wxListCtrl() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} +%% @doc Creates a listctrl with optional callback functions: %% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() +%% OnGetItemText = (This, Item, Column) -> unicode:charlist() +%% OnGetItemAttr = (This, Item) -> wxListItemAttr:wxListItemAttr() %% OnGetItemColumnImage = (This, Item, Column) -> integer() -%% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +%% +%% See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new(Parent, [Option]) -> wxListCtrl() when + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. new(#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options)-> @@ -101,26 +104,27 @@ wxListCtrl_new_2>> <<EXPORT:Create create/2, create/3 Create:EXPORT>> <<Create -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow()) -> bool() %% @equiv create(This,Parent, []) +-spec create(This, Parent) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(). create(This,Parent) when is_record(This, wx_ref),is_record(Parent, wx_ref) -> create(This,Parent, []). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow(), [Option]) -> bool() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} -%% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() -%% OnGetItemColumnImage = (This, Item, Column) -> integer() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlcreate">external documentation</a>. +-spec create(This, Parent, [Option]) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. + create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options) -> ?CLASS(ThisT,wxListCtrl), diff --git a/lib/wx/api_gen/wx_extra/wxXmlResource.erl b/lib/wx/api_gen/wx_extra/wxXmlResource.erl index 7700e2333e..b29ffba7c6 100644 --- a/lib/wx/api_gen/wx_extra/wxXmlResource.erl +++ b/lib/wx/api_gen/wx_extra/wxXmlResource.erl @@ -21,8 +21,6 @@ <<EXPORT:xrcctrl xrcctrl/3 xrcctrl:EXPORT>> <<xrcctrl -%% @spec (Window::wxWindow:wxWindow(),Name::string(), Type::atom()) -> wx:wxObject() - %% @doc Looks up a control with Name in a window created with XML %% resources. You can use it to set/get values from controls. %% The object is type casted to <b>Type</b>. @@ -32,6 +30,10 @@ %% true = wxXmlResource:loadDialog(Xrc, Dlg, Frame, "controls_dialog"), <br /> %% LCtrl = xrcctrl(Dlg, "controls_listctrl", wxListCtrl), <br /> %% wxListCtrl:insertColumn(LCtrl, 0, "Name", [{width, 200}]), <br /> +-spec xrcctrl(Window, Name, Type) -> wx:wx_object() when + Window::wxWindow:wxWindow(), + Name::string(), + Type::atom(). xrcctrl(Window = #wx_ref{}, Name, Type) when is_list(Name), is_atom(Type) -> %% Func Id ~s diff --git a/lib/wx/c_src/wxe_main.cpp b/lib/wx/c_src/wxe_main.cpp index 6fcde42eb5..c7565e33bd 100644 --- a/lib/wx/c_src/wxe_main.cpp +++ b/lib/wx/c_src/wxe_main.cpp @@ -67,6 +67,7 @@ int load_native_gui() int start_native_gui(wxe_data *sd) { int res; + ErlDrvThreadOpts *opts = NULL; wxe_status_m = erl_drv_mutex_create((char *) "wxe_status_m"); wxe_status_c = erl_drv_cond_create((char *)"wxe_status_c"); @@ -78,8 +79,11 @@ int start_native_gui(wxe_data *sd) res = erl_drv_steal_main_thread((char *)"wxwidgets", &wxe_thread,wxe_main_loop,(void *) sd->pdl,NULL); #else + opts = erl_drv_thread_opts_create((char *)"wx thread"); + opts->suggested_stack_size = 8192; res = erl_drv_thread_create((char *)"wxwidgets", - &wxe_thread,wxe_main_loop,(void *) sd->pdl,NULL); + &wxe_thread,wxe_main_loop,(void *) sd->pdl,opts); + erl_drv_thread_opts_destroy(opts); #endif if(res == 0) { erl_drv_mutex_lock(wxe_status_m); diff --git a/lib/wx/include/wx.hrl b/lib/wx/include/wx.hrl index af7cca7ed2..30baeebb18 100644 --- a/lib/wx/include/wx.hrl +++ b/lib/wx/include/wx.hrl @@ -33,94 +33,51 @@ %% Here comes the definitions of all event records. %% they contain the event type and possible some extra information. --record(wxInitDialog, {type :: wxInitDialogEventType()}). %% Callback event: {@link wxInitDialogEvent} --type wxInitDialogEventType() :: 'init_dialog'. --type wxInitDialog() :: #wxInitDialog{}. %% Callback event: {@link wxInitDialogEvent} - --record(wxClose, {type :: wxCloseEventType()}). %% Callback event: {@link wxCloseEvent} --type wxCloseEventType() :: 'close_window' | 'end_session' | 'query_end_session'. --type wxClose() :: #wxClose{}. %% Callback event: {@link wxCloseEvent} - --record(wxStyledText,{type :: wxStyledTextEventType(), %% Callback event: {@link wxStyledTextEvent} - position :: integer(), - key :: integer(), - modifiers :: integer(), - modificationType :: integer(), - text :: unicode:chardata(), - length :: integer(), - linesAdded :: integer(), - line :: integer(), - foldLevelNow :: integer(), - foldLevelPrev :: integer(), - margin :: integer(), - message :: integer(), - wParam :: integer(), - lParam :: integer(), - listType :: integer(), - x :: integer(), - y :: integer(), - dragText :: unicode:chardata(), - dragAllowMove :: boolean(), - dragResult :: wx:wx_enum()}). --type wxStyledTextEventType() :: 'stc_change' | 'stc_styleneeded' | 'stc_charadded' | 'stc_savepointreached' | 'stc_savepointleft' | 'stc_romodifyattempt' | 'stc_key' | 'stc_doubleclick' | 'stc_updateui' | 'stc_modified' | 'stc_macrorecord' | 'stc_marginclick' | 'stc_needshown' | 'stc_painted' | 'stc_userlistselection' | 'stc_uridropped' | 'stc_dwellstart' | 'stc_dwellend' | 'stc_start_drag' | 'stc_drag_over' | 'stc_do_drop' | 'stc_zoom' | 'stc_hotspot_click' | 'stc_hotspot_dclick' | 'stc_calltip_click' | 'stc_autocomp_selection'. --type wxStyledText() :: #wxStyledText{}. %% Callback event: {@link wxStyledTextEvent} - --record(wxFileDirPicker,{type :: wxFileDirPickerEventType(), %% Callback event: {@link wxFileDirPickerEvent} - path :: unicode:chardata()}). --type wxFileDirPickerEventType() :: 'command_filepicker_changed' | 'command_dirpicker_changed'. --type wxFileDirPicker() :: #wxFileDirPicker{}. %% Callback event: {@link wxFileDirPickerEvent} - --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} +-record(wxFocus,{type :: wxFocusEventType(), %% Callback event: {@link wxFocusEvent} + win :: wxWindow:wxWindow()}). +-type wxFocusEventType() :: 'set_focus' | 'kill_focus'. +-type wxFocus() :: #wxFocus{}. %% Callback event: {@link wxFocusEvent} --record(wxIdle, {type :: wxIdleEventType()}). %% Callback event: {@link wxIdleEvent} --type wxIdleEventType() :: 'idle'. --type wxIdle() :: #wxIdle{}. %% Callback event: {@link wxIdleEvent} +-record(wxCommand,{type :: wxCommandEventType(), %% Callback event: {@link wxCommandEvent} + cmdString :: unicode:chardata(), + commandInt :: integer(), + extraLong :: integer()}). +-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(wxColourPicker,{type :: wxColourPickerEventType(), %% Callback event: {@link wxColourPickerEvent} colour :: wx:wx_colour()}). -type wxColourPickerEventType() :: 'command_colourpicker_changed'. -type wxColourPicker() :: #wxColourPicker{}. %% Callback event: {@link wxColourPickerEvent} --record(wxSplitter, {type :: wxSplitterEventType()}). %% Callback event: {@link wxSplitterEvent} --type wxSplitterEventType() :: 'command_splitter_sash_pos_changed' | 'command_splitter_sash_pos_changing' | 'command_splitter_doubleclicked' | 'command_splitter_unsplit'. --type wxSplitter() :: #wxSplitter{}. %% Callback event: {@link wxSplitterEvent} - --record(wxSash,{type :: wxSashEventType(), %% Callback event: {@link wxSashEvent} - edge :: wx:wx_enum(), - dragRect :: {X::integer(), Y::integer(), W::integer(), H::integer()}, - dragStatus :: wx:wx_enum()}). --type wxSashEventType() :: 'sash_dragged'. --type wxSash() :: #wxSash{}. %% Callback event: {@link wxSashEvent} - -record(wxHelp, {type :: wxHelpEventType()}). %% Callback event: {@link wxHelpEvent} -type wxHelpEventType() :: 'help' | 'detailed_help'. -type wxHelp() :: #wxHelp{}. %% Callback event: {@link wxHelpEvent} --record(wxDisplayChanged, {type :: wxDisplayChangedEventType()}). %% Callback event: {@link wxDisplayChangedEvent} --type wxDisplayChangedEventType() :: 'display_changed'. --type wxDisplayChanged() :: #wxDisplayChanged{}. %% Callback event: {@link wxDisplayChangedEvent} - --record(wxMouseCaptureLost, {type :: wxMouseCaptureLostEventType()}). %% Callback event: {@link wxMouseCaptureLostEvent} --type wxMouseCaptureLostEventType() :: 'mouse_capture_lost'. --type wxMouseCaptureLost() :: #wxMouseCaptureLost{}. %% Callback event: {@link wxMouseCaptureLostEvent} +-record(wxSpin,{type :: wxSpinEventType(), %% Callback event: {@link wxSpinEvent} + commandInt :: integer()}). +-type wxSpinEventType() :: 'command_spinctrl_updated' | 'spin_up' | 'spin_down' | 'spin'. +-type wxSpin() :: #wxSpin{}. %% Callback event: {@link wxSpinEvent} --record(wxFontPicker,{type :: wxFontPickerEventType(), %% Callback event: {@link wxFontPickerEvent} - font :: wxFont:wxFont()}). --type wxFontPickerEventType() :: 'command_fontpicker_changed'. --type wxFontPicker() :: #wxFontPicker{}. %% Callback event: {@link wxFontPickerEvent} +-record(wxAuiManager,{type :: wxAuiManagerEventType(), %% Callback event: {@link wxAuiManagerEvent} + manager :: wxAuiManager:wxAuiManager(), + pane :: wxAuiPaneInfo:wxAuiPaneInfo(), + button :: integer(), + veto_flag :: boolean(), + canveto_flag :: boolean(), + dc :: wxDC:wxDC()}). +-type wxAuiManagerEventType() :: 'aui_pane_button' | 'aui_pane_close' | 'aui_pane_maximize' | 'aui_pane_restore' | 'aui_pane_activated' | 'aui_render' | 'aui_find_manager'. +-type wxAuiManager() :: #wxAuiManager{}. %% Callback event: {@link wxAuiManagerEvent} --record(wxFocus,{type :: wxFocusEventType(), %% Callback event: {@link wxFocusEvent} - win :: wxWindow:wxWindow()}). --type wxFocusEventType() :: 'set_focus' | 'kill_focus'. --type wxFocus() :: #wxFocus{}. %% Callback event: {@link wxFocusEvent} +-record(wxDate,{type :: wxDateEventType(), %% Callback event: {@link wxDateEvent} + date :: wx:wx_datetime()}). +-type wxDateEventType() :: 'date_changed'. +-type wxDate() :: #wxDate{}. %% Callback event: {@link wxDateEvent} --record(wxPaletteChanged, {type :: wxPaletteChangedEventType()}). %% Callback event: {@link wxPaletteChangedEvent} --type wxPaletteChangedEventType() :: 'palette_changed'. --type wxPaletteChanged() :: #wxPaletteChanged{}. %% Callback event: {@link wxPaletteChangedEvent} +-record(wxIconize,{type :: wxIconizeEventType(), %% Callback event: {@link wxIconizeEvent} + iconized :: boolean()}). +-type wxIconizeEventType() :: 'iconize'. +-type wxIconize() :: #wxIconize{}. %% Callback event: {@link wxIconizeEvent} -record(wxScroll,{type :: wxScrollEventType(), %% Callback event: {@link wxScrollEvent} commandInt :: integer(), @@ -128,38 +85,41 @@ -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(wxChildFocus, {type :: wxChildFocusEventType()}). %% Callback event: {@link wxChildFocusEvent} --type wxChildFocusEventType() :: 'child_focus'. --type wxChildFocus() :: #wxChildFocus{}. %% Callback event: {@link wxChildFocusEvent} +-record(wxSplitter, {type :: wxSplitterEventType()}). %% Callback event: {@link wxSplitterEvent} +-type wxSplitterEventType() :: 'command_splitter_sash_pos_changed' | 'command_splitter_sash_pos_changing' | 'command_splitter_doubleclicked' | 'command_splitter_unsplit'. +-type wxSplitter() :: #wxSplitter{}. %% Callback event: {@link wxSplitterEvent} --record(wxAuiNotebook,{type :: wxAuiNotebookEventType(), %% Callback event: {@link wxAuiNotebookEvent} - old_selection :: integer(), - selection :: integer(), - drag_source :: wxAuiNotebook:wxAuiNotebook()}). --type wxAuiNotebookEventType() :: 'command_auinotebook_page_close' | 'command_auinotebook_page_changed' | 'command_auinotebook_page_changing' | 'command_auinotebook_button' | 'command_auinotebook_begin_drag' | 'command_auinotebook_end_drag' | 'command_auinotebook_drag_motion' | 'command_auinotebook_allow_dnd' | 'command_auinotebook_tab_middle_down' | 'command_auinotebook_tab_middle_up' | 'command_auinotebook_tab_right_down' | 'command_auinotebook_tab_right_up' | 'command_auinotebook_page_closed' | 'command_auinotebook_drag_done' | 'command_auinotebook_bg_dclick'. --type wxAuiNotebook() :: #wxAuiNotebook{}. %% Callback event: {@link wxAuiNotebookEvent} +-record(wxPaletteChanged, {type :: wxPaletteChangedEventType()}). %% Callback event: {@link wxPaletteChangedEvent} +-type wxPaletteChangedEventType() :: 'palette_changed'. +-type wxPaletteChanged() :: #wxPaletteChanged{}. %% Callback event: {@link wxPaletteChangedEvent} --record(wxSize,{type :: wxSizeEventType(), %% Callback event: {@link wxSizeEvent} - size :: {W::integer(), H::integer()}, - rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). --type wxSizeEventType() :: 'size'. --type wxSize() :: #wxSize{}. %% Callback event: {@link wxSizeEvent} +-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} --record(wxCommand,{type :: wxCommandEventType(), %% Callback event: {@link wxCommandEvent} - cmdString :: unicode:chardata(), - commandInt :: integer(), - extraLong :: integer()}). --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(wxContextMenu,{type :: wxContextMenuEventType(), %% Callback event: {@link wxContextMenuEvent} + pos :: {X::integer(), Y::integer()}}). +-type wxContextMenuEventType() :: 'context_menu'. +-type wxContextMenu() :: #wxContextMenu{}. %% Callback event: {@link wxContextMenuEvent} --record(wxMaximize, {type :: wxMaximizeEventType()}). %% Callback event: {@link wxMaximizeEvent} --type wxMaximizeEventType() :: 'maximize'. --type wxMaximize() :: #wxMaximize{}. %% Callback event: {@link wxMaximizeEvent} +-record(wxFontPicker,{type :: wxFontPickerEventType(), %% Callback event: {@link wxFontPickerEvent} + font :: wxFont:wxFont()}). +-type wxFontPickerEventType() :: 'command_fontpicker_changed'. +-type wxFontPicker() :: #wxFontPicker{}. %% Callback event: {@link wxFontPickerEvent} --record(wxSpin,{type :: wxSpinEventType(), %% Callback event: {@link wxSpinEvent} - commandInt :: integer()}). --type wxSpinEventType() :: 'command_spinctrl_updated' | 'spin_up' | 'spin_down' | 'spin'. --type wxSpin() :: #wxSpin{}. %% Callback event: {@link wxSpinEvent} +-record(wxChildFocus, {type :: wxChildFocusEventType()}). %% Callback event: {@link wxChildFocusEvent} +-type wxChildFocusEventType() :: 'child_focus'. +-type wxChildFocus() :: #wxChildFocus{}. %% Callback event: {@link wxChildFocusEvent} + +-record(wxTaskBarIcon, {type :: wxTaskBarIconEventType()}). %% Callback event: {@link wxTaskBarIconEvent} +-type wxTaskBarIconEventType() :: 'taskbar_move' | 'taskbar_left_down' | 'taskbar_left_up' | 'taskbar_right_down' | 'taskbar_right_up' | 'taskbar_left_dclick' | 'taskbar_right_dclick'. +-type wxTaskBarIcon() :: #wxTaskBarIcon{}. %% Callback event: {@link wxTaskBarIconEvent} + +-record(wxWindowDestroy, {type :: wxWindowDestroyEventType()}). %% Callback event: {@link wxWindowDestroyEvent} +-type wxWindowDestroyEventType() :: 'destroy'. +-type wxWindowDestroy() :: #wxWindowDestroy{}. %% Callback event: {@link wxWindowDestroyEvent} -record(wxMenu,{type :: wxMenuEventType(), %% Callback event: {@link wxMenuEvent} menuId :: integer(), @@ -167,30 +127,63 @@ -type wxMenuEventType() :: 'menu_open' | 'menu_close' | 'menu_highlight'. -type wxMenu() :: #wxMenu{}. %% Callback event: {@link wxMenuEvent} +-record(wxActivate,{type :: wxActivateEventType(), %% Callback event: {@link wxActivateEvent} + active :: boolean()}). +-type wxActivateEventType() :: 'activate' | 'activate_app' | 'hibernate'. +-type wxActivate() :: #wxActivate{}. %% Callback event: {@link wxActivateEvent} + +-record(wxGrid,{type :: wxGridEventType(), %% Callback event: {@link wxGridEvent} + row :: integer(), + col :: integer(), + x :: integer(), + y :: integer(), + selecting :: boolean(), + control :: boolean(), + meta :: boolean(), + shift :: boolean(), + alt :: boolean()}). +-type wxGridEventType() :: 'grid_cell_left_click' | 'grid_cell_right_click' | 'grid_cell_left_dclick' | 'grid_cell_right_dclick' | 'grid_label_left_click' | 'grid_label_right_click' | 'grid_label_left_dclick' | 'grid_label_right_dclick' | 'grid_row_size' | 'grid_col_size' | 'grid_range_select' | 'grid_cell_change' | 'grid_select_cell' | 'grid_editor_shown' | 'grid_editor_hidden' | 'grid_editor_created' | 'grid_cell_begin_drag'. +-type wxGrid() :: #wxGrid{}. %% Callback event: {@link wxGridEvent} + +-record(wxPaint, {type :: wxPaintEventType()}). %% Callback event: {@link wxPaintEvent} +-type wxPaintEventType() :: 'paint'. +-type wxPaint() :: #wxPaint{}. %% Callback event: {@link wxPaintEvent} + -record(wxShow,{type :: wxShowEventType(), %% Callback event: {@link wxShowEvent} show :: boolean()}). -type wxShowEventType() :: 'show'. -type wxShow() :: #wxShow{}. %% Callback event: {@link wxShowEvent} --record(wxWindowDestroy, {type :: wxWindowDestroyEventType()}). %% Callback event: {@link wxWindowDestroyEvent} --type wxWindowDestroyEventType() :: 'destroy'. --type wxWindowDestroy() :: #wxWindowDestroy{}. %% Callback event: {@link wxWindowDestroyEvent} - --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(wxActivate,{type :: wxActivateEventType(), %% Callback event: {@link wxActivateEvent} - active :: boolean()}). --type wxActivateEventType() :: 'activate' | 'activate_app' | 'hibernate'. --type wxActivate() :: #wxActivate{}. %% Callback event: {@link wxActivateEvent} +-record(wxStyledText,{type :: wxStyledTextEventType(), %% Callback event: {@link wxStyledTextEvent} + position :: integer(), + key :: integer(), + modifiers :: integer(), + modificationType :: integer(), + text :: unicode:chardata(), + length :: integer(), + linesAdded :: integer(), + line :: integer(), + foldLevelNow :: integer(), + foldLevelPrev :: integer(), + margin :: integer(), + message :: integer(), + wParam :: integer(), + lParam :: integer(), + listType :: integer(), + x :: integer(), + y :: integer(), + dragText :: unicode:chardata(), + dragAllowMove :: boolean(), + dragResult :: wx:wx_enum()}). +-type wxStyledTextEventType() :: 'stc_change' | 'stc_styleneeded' | 'stc_charadded' | 'stc_savepointreached' | 'stc_savepointleft' | 'stc_romodifyattempt' | 'stc_key' | 'stc_doubleclick' | 'stc_updateui' | 'stc_modified' | 'stc_macrorecord' | 'stc_marginclick' | 'stc_needshown' | 'stc_painted' | 'stc_userlistselection' | 'stc_uridropped' | 'stc_dwellstart' | 'stc_dwellend' | 'stc_start_drag' | 'stc_drag_over' | 'stc_do_drop' | 'stc_zoom' | 'stc_hotspot_click' | 'stc_hotspot_dclick' | 'stc_calltip_click' | 'stc_autocomp_selection'. +-type wxStyledText() :: #wxStyledText{}. %% Callback event: {@link wxStyledTextEvent} --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} +-record(wxAuiNotebook,{type :: wxAuiNotebookEventType(), %% Callback event: {@link wxAuiNotebookEvent} + old_selection :: integer(), + selection :: integer(), + drag_source :: wxAuiNotebook:wxAuiNotebook()}). +-type wxAuiNotebookEventType() :: 'command_auinotebook_page_close' | 'command_auinotebook_page_changed' | 'command_auinotebook_page_changing' | 'command_auinotebook_button' | 'command_auinotebook_begin_drag' | 'command_auinotebook_end_drag' | 'command_auinotebook_drag_motion' | 'command_auinotebook_allow_dnd' | 'command_auinotebook_tab_middle_down' | 'command_auinotebook_tab_middle_up' | 'command_auinotebook_tab_right_down' | 'command_auinotebook_tab_right_up' | 'command_auinotebook_page_closed' | 'command_auinotebook_drag_done' | 'command_auinotebook_bg_dclick'. +-type wxAuiNotebook() :: #wxAuiNotebook{}. %% Callback event: {@link wxAuiNotebookEvent} -record(wxList,{type :: wxListEventType(), %% Callback event: {@link wxListEvent} code :: integer(), @@ -201,9 +194,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(wxClipboardText, {type :: wxClipboardTextEventType()}). %% Callback event: {@link wxClipboardTextEvent} --type wxClipboardTextEventType() :: 'command_text_copy' | 'command_text_cut' | 'command_text_paste'. --type wxClipboardText() :: #wxClipboardText{}. %% Callback event: {@link wxClipboardTextEvent} +-record(wxUpdateUI, {type :: wxUpdateUIEventType()}). %% Callback event: {@link wxUpdateUIEvent} +-type wxUpdateUIEventType() :: 'update_ui'. +-type wxUpdateUI() :: #wxUpdateUI{}. %% Callback event: {@link wxUpdateUIEvent} -record(wxScrollWin,{type :: wxScrollWinEventType(), %% Callback event: {@link wxScrollWinEvent} commandInt :: integer(), @@ -211,92 +204,81 @@ -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} --record(wxIconize,{type :: wxIconizeEventType(), %% Callback event: {@link wxIconizeEvent} - iconized :: boolean()}). --type wxIconizeEventType() :: 'iconize'. --type wxIconize() :: #wxIconize{}. %% Callback event: {@link wxIconizeEvent} +-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} --record(wxUpdateUI, {type :: wxUpdateUIEventType()}). %% Callback event: {@link wxUpdateUIEvent} --type wxUpdateUIEventType() :: 'update_ui'. --type wxUpdateUI() :: #wxUpdateUI{}. %% Callback event: {@link wxUpdateUIEvent} +-record(wxWindowCreate, {type :: wxWindowCreateEventType()}). %% Callback event: {@link wxWindowCreateEvent} +-type wxWindowCreateEventType() :: 'create'. +-type wxWindowCreate() :: #wxWindowCreate{}. %% Callback event: {@link wxWindowCreateEvent} --record(wxMouse,{type :: wxMouseEventType(), %% Callback event: {@link wxMouseEvent} +-record(wxClose, {type :: wxCloseEventType()}). %% Callback event: {@link wxCloseEvent} +-type wxCloseEventType() :: 'close_window' | 'end_session' | 'query_end_session'. +-type wxClose() :: #wxClose{}. %% Callback event: {@link wxCloseEvent} + +-record(wxKey,{type :: wxKeyEventType(), %% Callback event: {@link wxKeyEvent} x :: integer(), y :: integer(), - leftDown :: boolean(), - middleDown :: boolean(), - rightDown :: boolean(), + keyCode :: integer(), controlDown :: boolean(), shiftDown :: boolean(), altDown :: boolean(), metaDown :: boolean(), - wheelRotation :: integer(), - wheelDelta :: integer(), - linesPerAction :: integer()}). --type wxMouseEventType() :: 'left_down' | 'left_up' | 'middle_down' | 'middle_up' | 'right_down' | 'right_up' | 'motion' | 'enter_window' | 'leave_window' | 'left_dclick' | 'middle_dclick' | 'right_dclick' | 'mousewheel'. --type wxMouse() :: #wxMouse{}. %% Callback event: {@link wxMouseEvent} - --record(wxTree,{type :: wxTreeEventType(), %% Callback event: {@link wxTreeEvent} - item :: integer(), - itemOld :: integer(), - pointDrag :: {X::integer(), Y::integer()}}). --type wxTreeEventType() :: 'command_tree_begin_drag' | 'command_tree_begin_rdrag' | 'command_tree_begin_label_edit' | 'command_tree_end_label_edit' | 'command_tree_delete_item' | 'command_tree_get_info' | 'command_tree_set_info' | 'command_tree_item_expanded' | 'command_tree_item_expanding' | 'command_tree_item_collapsed' | 'command_tree_item_collapsing' | 'command_tree_sel_changed' | 'command_tree_sel_changing' | 'command_tree_key_down' | 'command_tree_item_activated' | 'command_tree_item_right_click' | 'command_tree_item_middle_click' | 'command_tree_end_drag' | 'command_tree_state_image_click' | 'command_tree_item_gettooltip' | 'command_tree_item_menu'. --type wxTree() :: #wxTree{}. %% Callback event: {@link wxTreeEvent} - --record(wxSysColourChanged, {type :: wxSysColourChangedEventType()}). %% Callback event: {@link wxSysColourChangedEvent} --type wxSysColourChangedEventType() :: 'sys_colour_changed'. --type wxSysColourChanged() :: #wxSysColourChanged{}. %% Callback event: {@link wxSysColourChangedEvent} + scanCode :: boolean(), + uniChar :: integer(), + rawCode :: integer(), + rawFlags :: integer()}). +-type wxKeyEventType() :: 'char' | 'char_hook' | 'key_down' | 'key_up'. +-type wxKey() :: #wxKey{}. %% Callback event: {@link wxKeyEvent} --record(wxNavigationKey,{type :: wxNavigationKeyEventType(), %% Callback event: {@link wxNavigationKeyEvent} - flags :: integer(), - focus :: wxWindow:wxWindow()}). --type wxNavigationKeyEventType() :: 'navigation_key'. --type wxNavigationKey() :: #wxNavigationKey{}. %% Callback event: {@link wxNavigationKeyEvent} +-record(wxIdle, {type :: wxIdleEventType()}). %% Callback event: {@link wxIdleEvent} +-type wxIdleEventType() :: 'idle'. +-type wxIdle() :: #wxIdle{}. %% Callback event: {@link wxIdleEvent} -record(wxQueryNewPalette, {type :: wxQueryNewPaletteEventType()}). %% Callback event: {@link wxQueryNewPaletteEvent} -type wxQueryNewPaletteEventType() :: 'query_new_palette'. -type wxQueryNewPalette() :: #wxQueryNewPalette{}. %% Callback event: {@link wxQueryNewPaletteEvent} --record(wxMouseCaptureChanged, {type :: wxMouseCaptureChangedEventType()}). %% Callback event: {@link wxMouseCaptureChangedEvent} --type wxMouseCaptureChangedEventType() :: 'mouse_capture_changed'. --type wxMouseCaptureChanged() :: #wxMouseCaptureChanged{}. %% Callback event: {@link wxMouseCaptureChangedEvent} - -record(wxHtmlLink,{type :: wxHtmlLinkEventType(), %% Callback event: {@link wxHtmlLinkEvent} linkInfo :: wx:wx_wxHtmlLinkInfo()}). -type wxHtmlLinkEventType() :: 'command_html_link_clicked'. -type wxHtmlLink() :: #wxHtmlLink{}. %% Callback event: {@link wxHtmlLinkEvent} --record(wxKey,{type :: wxKeyEventType(), %% Callback event: {@link wxKeyEvent} - x :: integer(), - y :: integer(), - keyCode :: integer(), - controlDown :: boolean(), - shiftDown :: boolean(), - altDown :: boolean(), - metaDown :: boolean(), - scanCode :: boolean(), - uniChar :: integer(), - rawCode :: integer(), - rawFlags :: integer()}). --type wxKeyEventType() :: 'char' | 'char_hook' | 'key_down' | 'key_up'. --type wxKey() :: #wxKey{}. %% Callback event: {@link wxKeyEvent} +-record(wxInitDialog, {type :: wxInitDialogEventType()}). %% Callback event: {@link wxInitDialogEvent} +-type wxInitDialogEventType() :: 'init_dialog'. +-type wxInitDialog() :: #wxInitDialog{}. %% Callback event: {@link wxInitDialogEvent} --record(wxTaskBarIcon, {type :: wxTaskBarIconEventType()}). %% Callback event: {@link wxTaskBarIconEvent} --type wxTaskBarIconEventType() :: 'taskbar_move' | 'taskbar_left_down' | 'taskbar_left_up' | 'taskbar_right_down' | 'taskbar_right_up' | 'taskbar_left_dclick' | 'taskbar_right_dclick'. --type wxTaskBarIcon() :: #wxTaskBarIcon{}. %% Callback event: {@link wxTaskBarIconEvent} +-record(wxMaximize, {type :: wxMaximizeEventType()}). %% Callback event: {@link wxMaximizeEvent} +-type wxMaximizeEventType() :: 'maximize'. +-type wxMaximize() :: #wxMaximize{}. %% Callback event: {@link wxMaximizeEvent} --record(wxGrid,{type :: wxGridEventType(), %% Callback event: {@link wxGridEvent} - row :: integer(), - col :: integer(), - x :: integer(), - y :: integer(), - selecting :: boolean(), - control :: boolean(), - meta :: boolean(), - shift :: boolean(), - alt :: boolean()}). --type wxGridEventType() :: 'grid_cell_left_click' | 'grid_cell_right_click' | 'grid_cell_left_dclick' | 'grid_cell_right_dclick' | 'grid_label_left_click' | 'grid_label_right_click' | 'grid_label_left_dclick' | 'grid_label_right_dclick' | 'grid_row_size' | 'grid_col_size' | 'grid_range_select' | 'grid_cell_change' | 'grid_select_cell' | 'grid_editor_shown' | 'grid_editor_hidden' | 'grid_editor_created' | 'grid_cell_begin_drag'. --type wxGrid() :: #wxGrid{}. %% Callback event: {@link wxGridEvent} +-record(wxClipboardText, {type :: wxClipboardTextEventType()}). %% Callback event: {@link wxClipboardTextEvent} +-type wxClipboardTextEventType() :: 'command_text_copy' | 'command_text_cut' | 'command_text_paste'. +-type wxClipboardText() :: #wxClipboardText{}. %% Callback event: {@link wxClipboardTextEvent} + +-record(wxTree,{type :: wxTreeEventType(), %% Callback event: {@link wxTreeEvent} + item :: integer(), + itemOld :: integer(), + pointDrag :: {X::integer(), Y::integer()}}). +-type wxTreeEventType() :: 'command_tree_begin_drag' | 'command_tree_begin_rdrag' | 'command_tree_begin_label_edit' | 'command_tree_end_label_edit' | 'command_tree_delete_item' | 'command_tree_get_info' | 'command_tree_set_info' | 'command_tree_item_expanded' | 'command_tree_item_expanding' | 'command_tree_item_collapsed' | 'command_tree_item_collapsing' | 'command_tree_sel_changed' | 'command_tree_sel_changing' | 'command_tree_key_down' | 'command_tree_item_activated' | 'command_tree_item_right_click' | 'command_tree_item_middle_click' | 'command_tree_end_drag' | 'command_tree_state_image_click' | 'command_tree_item_gettooltip' | 'command_tree_item_menu'. +-type wxTree() :: #wxTree{}. %% Callback event: {@link wxTreeEvent} + +-record(wxErase,{type :: wxEraseEventType(), %% Callback event: {@link wxEraseEvent} + dc :: wxDC:wxDC()}). +-type wxEraseEventType() :: 'erase_background'. +-type wxErase() :: #wxErase{}. %% Callback event: {@link wxEraseEvent} + +-record(wxSash,{type :: wxSashEventType(), %% Callback event: {@link wxSashEvent} + edge :: wx:wx_enum(), + dragRect :: {X::integer(), Y::integer(), W::integer(), H::integer()}, + dragStatus :: wx:wx_enum()}). +-type wxSashEventType() :: 'sash_dragged'. +-type wxSash() :: #wxSash{}. %% Callback event: {@link wxSashEvent} -record(wxCalendar,{type :: wxCalendarEventType(), %% Callback event: {@link wxCalendarEvent} wday :: wx:wx_enum(), @@ -304,42 +286,52 @@ -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} --record(wxWindowCreate, {type :: wxWindowCreateEventType()}). %% Callback event: {@link wxWindowCreateEvent} --type wxWindowCreateEventType() :: 'create'. --type wxWindowCreate() :: #wxWindowCreate{}. %% Callback event: {@link wxWindowCreateEvent} +-record(wxMouse,{type :: wxMouseEventType(), %% Callback event: {@link wxMouseEvent} + x :: integer(), + y :: integer(), + leftDown :: boolean(), + middleDown :: boolean(), + rightDown :: boolean(), + controlDown :: boolean(), + shiftDown :: boolean(), + altDown :: boolean(), + metaDown :: boolean(), + wheelRotation :: integer(), + wheelDelta :: integer(), + linesPerAction :: integer()}). +-type wxMouseEventType() :: 'left_down' | 'left_up' | 'middle_down' | 'middle_up' | 'right_down' | 'right_up' | 'motion' | 'enter_window' | 'leave_window' | 'left_dclick' | 'middle_dclick' | 'right_dclick' | 'mousewheel'. +-type wxMouse() :: #wxMouse{}. %% Callback event: {@link wxMouseEvent} --record(wxDate,{type :: wxDateEventType(), %% Callback event: {@link wxDateEvent} - date :: wx:wx_datetime()}). --type wxDateEventType() :: 'date_changed'. --type wxDate() :: #wxDate{}. %% Callback event: {@link wxDateEvent} +-record(wxSize,{type :: wxSizeEventType(), %% Callback event: {@link wxSizeEvent} + size :: {W::integer(), H::integer()}, + rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). +-type wxSizeEventType() :: 'size'. +-type wxSize() :: #wxSize{}. %% Callback event: {@link wxSizeEvent} --record(wxAuiManager,{type :: wxAuiManagerEventType(), %% Callback event: {@link wxAuiManagerEvent} - manager :: wxAuiManager:wxAuiManager(), - pane :: wxAuiPaneInfo:wxAuiPaneInfo(), - button :: integer(), - veto_flag :: boolean(), - canveto_flag :: boolean(), - dc :: wxDC:wxDC()}). --type wxAuiManagerEventType() :: 'aui_pane_button' | 'aui_pane_close' | 'aui_pane_maximize' | 'aui_pane_restore' | 'aui_pane_activated' | 'aui_render' | 'aui_find_manager'. --type wxAuiManager() :: #wxAuiManager{}. %% Callback event: {@link wxAuiManagerEvent} +-record(wxSysColourChanged, {type :: wxSysColourChangedEventType()}). %% Callback event: {@link wxSysColourChangedEvent} +-type wxSysColourChangedEventType() :: 'sys_colour_changed'. +-type wxSysColourChanged() :: #wxSysColourChanged{}. %% Callback event: {@link wxSysColourChangedEvent} --record(wxJoystick,{type :: wxJoystickEventType(), %% Callback event: {@link wxJoystickEvent} +-record(wxDisplayChanged, {type :: wxDisplayChangedEventType()}). %% Callback event: {@link wxDisplayChangedEvent} +-type wxDisplayChangedEventType() :: 'display_changed'. +-type wxDisplayChanged() :: #wxDisplayChanged{}. %% Callback event: {@link wxDisplayChangedEvent} + +-record(wxMove,{type :: wxMoveEventType(), %% Callback event: {@link wxMoveEvent} 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} + rect :: {X::integer(), Y::integer(), W::integer(), H::integer()}}). +-type wxMoveEventType() :: 'move'. +-type wxMove() :: #wxMove{}. %% Callback event: {@link wxMoveEvent} --record(wxPaint, {type :: wxPaintEventType()}). %% Callback event: {@link wxPaintEvent} --type wxPaintEventType() :: 'paint'. --type wxPaint() :: #wxPaint{}. %% Callback event: {@link wxPaintEvent} +-record(wxNavigationKey,{type :: wxNavigationKeyEventType(), %% Callback event: {@link wxNavigationKeyEvent} + flags :: integer(), + focus :: wxWindow:wxWindow()}). +-type wxNavigationKeyEventType() :: 'navigation_key'. +-type wxNavigationKey() :: #wxNavigationKey{}. %% Callback event: {@link wxNavigationKeyEvent} --record(wxErase,{type :: wxEraseEventType(), %% Callback event: {@link wxEraseEvent} - dc :: wxDC:wxDC()}). --type wxEraseEventType() :: 'erase_background'. --type wxErase() :: #wxErase{}. %% Callback event: {@link wxEraseEvent} +-record(wxFileDirPicker,{type :: wxFileDirPickerEventType(), %% Callback event: {@link wxFileDirPickerEvent} + path :: unicode:chardata()}). +-type wxFileDirPickerEventType() :: 'command_filepicker_changed' | 'command_dirpicker_changed'. +-type wxFileDirPicker() :: #wxFileDirPicker{}. %% Callback event: {@link wxFileDirPickerEvent} -record(wxSetCursor,{type :: wxSetCursorEventType(), %% Callback event: {@link wxSetCursorEvent} x :: integer(), @@ -348,6 +340,14 @@ -type wxSetCursorEventType() :: 'set_cursor'. -type wxSetCursor() :: #wxSetCursor{}. %% Callback event: {@link wxSetCursorEvent} +-record(wxMouseCaptureChanged, {type :: wxMouseCaptureChangedEventType()}). %% Callback event: {@link wxMouseCaptureChangedEvent} +-type wxMouseCaptureChangedEventType() :: 'mouse_capture_changed'. +-type wxMouseCaptureChanged() :: #wxMouseCaptureChanged{}. %% Callback event: {@link wxMouseCaptureChangedEvent} + +-record(wxMouseCaptureLost, {type :: wxMouseCaptureLostEventType()}). %% Callback event: {@link wxMouseCaptureLostEvent} +-type wxMouseCaptureLostEventType() :: 'mouse_capture_lost'. +-type wxMouseCaptureLost() :: #wxMouseCaptureLost{}. %% Callback event: {@link wxMouseCaptureLostEvent} + -type event() :: wxActivate() | wxAuiManager() | wxAuiNotebook() | wxCalendar() | wxChildFocus() | wxClipboardText() | wxClose() | wxColourPicker() | wxCommand() | wxContextMenu() | wxDate() | wxDisplayChanged() | wxErase() | wxFileDirPicker() | wxFocus() | wxFontPicker() | wxGrid() | wxHelp() | wxHtmlLink() | wxIconize() | wxIdle() | wxInitDialog() | wxJoystick() | wxKey() | wxList() | wxMaximize() | wxMenu() | wxMouse() | wxMouseCaptureChanged() | wxMouseCaptureLost() | wxMove() | wxNavigationKey() | wxNotebook() | wxPaint() | wxPaletteChanged() | wxQueryNewPalette() | wxSash() | wxScroll() | wxScrollWin() | wxSetCursor() | wxShow() | wxSize() | wxSpin() | wxSplitter() | wxStyledText() | wxSysColourChanged() | wxTaskBarIcon() | wxTree() | wxUpdateUI() | wxWindowCreate() | wxWindowDestroy(). -type wxEventType() :: wxActivateEventType() | wxAuiManagerEventType() | wxAuiNotebookEventType() | wxCalendarEventType() | wxChildFocusEventType() | wxClipboardTextEventType() | wxCloseEventType() | wxColourPickerEventType() | wxCommandEventType() | wxContextMenuEventType() | wxDateEventType() | wxDisplayChangedEventType() | wxEraseEventType() | wxFileDirPickerEventType() | wxFocusEventType() | wxFontPickerEventType() | wxGridEventType() | wxHelpEventType() | wxHtmlLinkEventType() | wxIconizeEventType() | wxIdleEventType() | wxInitDialogEventType() | wxJoystickEventType() | wxKeyEventType() | wxListEventType() | wxMaximizeEventType() | wxMenuEventType() | wxMouseCaptureChangedEventType() | wxMouseCaptureLostEventType() | wxMouseEventType() | wxMoveEventType() | wxNavigationKeyEventType() | wxNotebookEventType() | wxPaintEventType() | wxPaletteChangedEventType() | wxQueryNewPaletteEventType() | wxSashEventType() | wxScrollEventType() | wxScrollWinEventType() | wxSetCursorEventType() | wxShowEventType() | wxSizeEventType() | wxSpinEventType() | wxSplitterEventType() | wxStyledTextEventType() | wxSysColourChangedEventType() | wxTaskBarIconEventType() | wxTreeEventType() | wxUpdateUIEventType() | wxWindowCreateEventType() | wxWindowDestroyEventType(). diff --git a/lib/wx/src/gen/wxListCtrl.erl b/lib/wx/src/gen/wxListCtrl.erl index d1a063d900..851686062a 100644 --- a/lib/wx/src/gen/wxListCtrl.erl +++ b/lib/wx/src/gen/wxListCtrl.erl @@ -94,31 +94,34 @@ parent_class(_Class) -> erlang:error({badtype, ?MODULE}). -type wxListCtrl() :: wx:wx_object(). -%% @spec () -> wxListCtrl() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new() -> wxListCtrl(). new() -> wxe_util:construct(?wxListCtrl_new_0, <<>>). -%% @spec (Parent::wxWindow:wxWindow()) -> wxListCtrl() -%% @equiv new(Parent, []) +-spec new(Parent) -> wxListCtrl() when + Parent::wxWindow:wxWindow(). new(Parent) when is_record(Parent, wx_ref) -> new(Parent, []). -%% @spec (Parent::wxWindow:wxWindow(), [Option]) -> wxListCtrl() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} +%% @doc Creates a listctrl with optional callback functions: %% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() +%% OnGetItemText = (This, Item, Column) -> unicode:charlist() +%% OnGetItemAttr = (This, Item) -> wxListItemAttr:wxListItemAttr() %% OnGetItemColumnImage = (This, Item, Column) -> integer() -%% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +%% +%% See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlwxlistctrl">external documentation</a>. +-spec new(Parent, [Option]) -> wxListCtrl() when + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. new(#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options)-> @@ -185,26 +188,27 @@ clearAll(#wx_ref{type=ThisT,ref=ThisRef}) -> <<ThisRef:32/?UI>>). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow()) -> bool() %% @equiv create(This,Parent, []) +-spec create(This, Parent) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(). create(This,Parent) when is_record(This, wx_ref),is_record(Parent, wx_ref) -> create(This,Parent, []). -%% @spec (This::wxListCtrl(), Parent::wxWindow:wxWindow(), [Option]) -> bool() -%% Option = {winid, integer()} | -%% {pos, {X::integer(),Y::integer()}} | -%% {size, {W::integer(),H::integer()}} | -%% {style, integer()} | -%% {validator, wx:wx()} | -%% {onGetItemText, OnGetItemText} | -%% {onGetItemAttr, OnGetItemAttr} | -%% {onGetItemColumnImage, OnGetItemColumnImage} -%% -%% OnGetItemText = (This, Item, Column) -> wxString() -%% OnGetItemAttr = (This, Item) -> wxListItemAttr() -%% OnGetItemColumnImage = (This, Item, Column) -> integer() %% @doc See <a href="http://www.wxwidgets.org/manuals/stable/wx_wxlistctrl.html#wxlistctrlcreate">external documentation</a>. +-spec create(This, Parent, [Option]) -> wxListCtrl() when + This::wxWindow:wxWindow(), + Parent::wxWindow:wxWindow(), + Option::{winid, integer()} | + {pos, {X::integer(),Y::integer()}} | + {size, {W::integer(),H::integer()}} | + {style, integer()} | + {validator, wx:wx_object()} | + {onGetItemText, function()} | + {onGetItemAttr, function()} | + {onGetItemColumnImage, function()}. + create(#wx_ref{type=ThisT,ref=ThisRef},#wx_ref{type=ParentT,ref=ParentRef}, Options) when is_list(Options) -> ?CLASS(ThisT,wxListCtrl), diff --git a/lib/wx/src/gen/wxXmlResource.erl b/lib/wx/src/gen/wxXmlResource.erl index ae02c74751..aa65b8b04e 100644 --- a/lib/wx/src/gen/wxXmlResource.erl +++ b/lib/wx/src/gen/wxXmlResource.erl @@ -334,8 +334,6 @@ unload(#wx_ref{type=ThisT,ref=ThisRef},Filename) <<ThisRef:32/?UI,(byte_size(Filename_UC)):32/?UI,(Filename_UC)/binary, 0:(((8- ((0+byte_size(Filename_UC)) band 16#7)) band 16#7))/unit:8>>). -%% @spec (Window::wxWindow:wxWindow(),Name::string(), Type::atom()) -> wx:wxObject() - %% @doc Looks up a control with Name in a window created with XML %% resources. You can use it to set/get values from controls. %% The object is type casted to <b>Type</b>. @@ -345,6 +343,10 @@ unload(#wx_ref{type=ThisT,ref=ThisRef},Filename) %% true = wxXmlResource:loadDialog(Xrc, Dlg, Frame, "controls_dialog"), <br /> %% LCtrl = xrcctrl(Dlg, "controls_listctrl", wxListCtrl), <br /> %% wxListCtrl:insertColumn(LCtrl, 0, "Name", [{width, 200}]), <br /> +-spec xrcctrl(Window, Name, Type) -> wx:wx_object() when + Window::wxWindow:wxWindow(), + Name::string(), + Type::atom(). xrcctrl(Window = #wx_ref{}, Name, Type) when is_list(Name), is_atom(Type) -> %% Func Id ?wxXmlResource_xrcctrl diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl index 40ee308358..40170b6eb1 100644 --- a/lib/wx/src/wx_object.erl +++ b/lib/wx/src/wx_object.erl @@ -55,7 +55,7 @@ %% When stop is returned in one of the functions above with Reason = %% normal | shutdown | Term, terminate(State) is called. It lets the %% user module clean up, it is always called when server terminates or -%% when wxObject() in the driver is deleted. If the Parent process +%% when wx_object() in the driver is deleted. If the Parent process %% terminates the Module:terminate/2 function is called. <br/> %% terminate(Reason, State) %% @@ -171,58 +171,59 @@ %% ----------------------------------------------------------------- -%% @spec (Mod, Args, Options) -> wxWindow:wxWindow() -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). - -%% @spec (Name, Mod, Args, Options) -> wxWindow:wxWindow() -%% Name = {local, atom()} -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug + %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Name::{local, atom()}, + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, nolink, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). -%% @spec (Mod, Args, Options) -> wxWindow:wxWindow() -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start_link(Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). -%% @spec (Name, Mod, Args, Options) -> wxWindow:wxWindow() -%% Name = {local, atom()} -%% Mod = atom() -%% Args = term() -%% Options = [{timeout, Timeout} | {debug, [Flag]}] -%% Flag = trace | log | {logfile, File} | statistics | debug %% @doc Starts a generic wx_object server and invokes Mod:init(Args) in the %% new process. +-spec start_link(Name, Mod, Args, Options) -> wxWindow:wxWindow() | {error, term()} when + Name::{local, atom()}, + Mod::atom(), + Args::term(), + Flag::trace | log | {logfile, string()} | statistics | debug, + Options::[{timeout, timeout()} | {debug, [Flag]}]. start_link(Name, Mod, Args, Options) -> gen_response(gen:start(?MODULE, link, Name, Mod, Args, [get(?WXE_IDENTIFIER)|Options])). - + gen_response({ok, Pid}) -> receive {ack, Pid, Ref = #wx_ref{}} -> Ref end; gen_response(Reply) -> Reply. -%% @spec (Ref::wxObject()|atom()|pid()) -> ok %% @doc Stops a generic wx_object server with reason 'normal'. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the process does not exist, an %% exception is raised. +-spec stop(Obj) -> ok when + Obj::wx:wx_object()|atom()|pid(). stop(Ref = #wx_ref{state=Pid}) when is_pid(Pid) -> try gen:stop(Pid) @@ -236,11 +237,14 @@ stop(Name) when is_atom(Name) orelse is_pid(Name) -> erlang:error({ExitReason, {?MODULE, stop, [Name]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Reason::term(), Timeout::timeout()) -> ok %% @doc Stops a generic wx_object server with the given Reason. %% Invokes terminate(Reason,State) in the server. The call waits until %% the process is terminated. If the call times out, or if the process %% does not exist, an exception is raised. +-spec stop(Obj, Reason, Timeout) -> ok when + Obj::wx:wx_object()|atom()|pid(), + Reason::term(), + Timeout::timeout(). stop(Ref = #wx_ref{state=Pid}, Reason, Timeout) when is_pid(Pid) -> try gen:stop(Pid, Reason, Timeout) @@ -254,12 +258,14 @@ stop(Name, Reason, Timeout) when is_atom(Name) orelse is_pid(Name) -> erlang:error({ExitReason, {?MODULE, stop, [Name, Reason, Timeout]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Request::term()) -> term() %% @doc Make a call to a wx_object server. %% The call waits until it gets a result. %% Invokes handle_call(Request, From, State) in the server +-spec call(Obj, Request) -> term() when + Obj::wx:wx_object()|atom()|pid(), + Request::term(). call(Ref = #wx_ref{state=Pid}, Request) when is_pid(Pid) -> - try + try {ok,Res} = gen:call(Pid, '$gen_call', Request, infinity), Res catch _:Reason -> @@ -272,10 +278,13 @@ call(Name, Request) when is_atom(Name) orelse is_pid(Name) -> catch _:Reason -> erlang:error({Reason, {?MODULE, call, [Name, Request]}}) end. - -%% @spec (Ref::wxObject()|atom()|pid(), Request::term(), Timeout::integer()) -> term() + %% @doc Make a call to a wx_object server with a timeout. %% Invokes handle_call(Request, From, State) in server +-spec call(Obj, Request, Timeout) -> term() when + Obj::wx:wx_object()|atom()|pid(), + Request::term(), + Timeout::integer(). call(Ref = #wx_ref{state=Pid}, Request, Timeout) when is_pid(Pid) -> try {ok,Res} = gen:call(Pid, '$gen_call', Request, Timeout), @@ -291,10 +300,11 @@ call(Name, Request, Timeout) when is_atom(Name) orelse is_pid(Name) -> erlang:error({Reason, {?MODULE, call, [Name, Request, Timeout]}}) end. -%% @spec (Ref::wxObject()|atom()|pid(), Request::term()) -> ok %% @doc Make a cast to a wx_object server. %% Invokes handle_cast(Request, State) in the server - +-spec cast(Obj, Request) -> ok when + Obj::wx:wx_object()|atom()|pid(), + Request::term(). cast(#wx_ref{state=Pid}, Request) when is_pid(Pid) -> Pid ! {'$gen_cast',Request}, ok; @@ -302,21 +312,23 @@ cast(Name, Request) when is_atom(Name) orelse is_pid(Name) -> Name ! {'$gen_cast',Request}, ok. -%% @spec (Ref::wxObject()) -> pid() %% @doc Get the pid of the object handle. +-spec get_pid(Obj) -> pid() when + Obj::wx:wx_object()|atom()|pid(). get_pid(#wx_ref{state=Pid}) when is_pid(Pid) -> Pid. -%% @spec (Ref::wxObject(), pid()) -> wxObject() %% @doc Sets the controlling process of the object handle. +-spec set_pid(Obj, pid()) -> wx:wx_object() when + Obj::wx:wx_object()|atom()|pid(). set_pid(#wx_ref{}=R, Pid) when is_pid(Pid) -> R#wx_ref{state=Pid}. %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- -%% @spec (From::tuple(), Reply::term()) -> pid() %% @doc Get the pid of the object handle. +-spec reply({pid(), Tag::term()}, Reply::term()) -> pid(). reply({To, Tag}, Reply) -> catch To ! {Tag, Reply}. |