diff options
Diffstat (limited to 'lib')
174 files changed, 4846 insertions, 2407 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/src/compile.erl b/lib/compiler/src/compile.erl index 82ff8a95f3..e951a25e04 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -43,6 +43,10 @@ -type abstract_code() :: [erl_parse:abstract_form()]. +%% Internal representations used for 'from_asm' and 'from_beam' compilation can +%% also be valid, but have no relevant types defined. +-type forms() :: abstract_code() | cerl:c_module(). + -type option() :: atom() | {atom(), term()} | {'d', atom(), term()}. -type err_info() :: {erl_anno:line() | 'none', @@ -88,7 +92,7 @@ file(File, Opt) -> forms(Forms) -> forms(Forms, ?DEFAULT_OPTIONS). --spec forms(abstract_code(), [option()] | option()) -> comp_ret(). +-spec forms(forms(), [option()] | option()) -> comp_ret(). forms(Forms, Opts) when is_list(Opts) -> do_compile({forms,Forms}, [binary|Opts++env_default_opts()]); @@ -116,7 +120,7 @@ noenv_file(File, Opts) when is_list(Opts) -> noenv_file(File, Opt) -> noenv_file(File, [Opt|?DEFAULT_OPTIONS]). --spec noenv_forms(abstract_code(), [option()] | option()) -> comp_ret(). +-spec noenv_forms(forms(), [option()] | option()) -> comp_ret(). noenv_forms(Forms, Opts) when is_list(Opts) -> do_compile({forms,Forms}, [binary|Opts]); @@ -236,6 +240,8 @@ format_error({epp,E}) -> epp:format_error(E); format_error(write_error) -> "error writing file"; +format_error({write_error, Error}) -> + io_lib:format("error writing file: ~ts", [file:format_error(Error)]); format_error({rename,From,To,Error}) -> io_lib:format("failed to rename ~ts to ~ts: ~ts", [From,To,file:format_error(Error)]); @@ -1206,7 +1212,7 @@ makedep_output(#compile{code=Code,options=Opts,ofile=Ofile}=St) -> end, {ok,St} catch - exit:_ -> + error:_ -> %% Couldn't write to output Makefile. Err = {St#compile.ifile,[{none,?MODULE,write_error}]}, {error,St#compile{errors=St#compile.errors++[Err]}} @@ -1479,8 +1485,8 @@ save_binary_1(St) -> end, {error,St#compile{errors=St#compile.errors ++ Es}} end; - {error,_Error} -> - Es = [{Tfile,[{none,compile,write_error}]}], + {error,Error} -> + Es = [{Tfile,[{none,compile,{write_error,Error}}]}], {error,St#compile{errors=St#compile.errors ++ Es}} end. @@ -1628,8 +1634,8 @@ listing(LFun, Ext, St) -> LFun(Lf, Code), ok = file:close(Lf), {ok,St}; - {error,_Error} -> - Es = [{Lfile,[{none,compile,write_error}]}], + {error,Error} -> + Es = [{Lfile,[{none,compile,{write_error,Error}}]}], {error,St#compile{errors=St#compile.errors ++ Es}} end. 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/debugger/src/dbg_iserver.erl b/lib/debugger/src/dbg_iserver.erl index 3561454685..3e959e8e30 100644 --- a/lib/debugger/src/dbg_iserver.erl +++ b/lib/debugger/src/dbg_iserver.erl @@ -214,7 +214,6 @@ handle_call({new_process, Pid, Meta, Function}, _From, State) -> %% Code loading handle_call({load, Mod, Src, Bin}, _From, State) -> - %% Create an ETS table for storing information about the module Db = State#state.db, ModDb = ets:new(Mod, [ordered_set, public]), @@ -285,23 +284,28 @@ handle_call({contents, Mod, Pid}, _From, State) -> {reply, {ok, Bin}, State}; handle_call({raw_contents, Mod, Pid}, _From, State) -> Db = State#state.db, - [{{Mod, refs}, ModDbs}] = ets:lookup(Db, {Mod, refs}), - ModDb = if - Pid =:= any -> hd(ModDbs); - true -> - lists:foldl(fun(T, not_found) -> - [{T, Pids}] = ets:lookup(Db, T), - case lists:member(Pid, Pids) of - true -> T; - false -> not_found - end; - (_T, T) -> T - end, - not_found, - ModDbs) - end, - [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), - {reply, {ok, Bin}, State}; + case ets:lookup(Db, {Mod, refs}) of + [{{Mod, refs}, ModDbs}] -> + ModDb = + if + Pid =:= any -> hd(ModDbs); + true -> + lists:foldl(fun(T, not_found) -> + [{T, Pids}] = ets:lookup(Db, T), + case lists:member(Pid, Pids) of + true -> T; + false -> not_found + end; + (_T, T) -> T + end, + not_found, + ModDbs) + end, + [{mod_raw, Bin}] = ets:lookup(ModDb, mod_raw), + {reply, {ok, Bin}, State}; + [] -> % code not interpreted + {reply, not_found, State} + end; handle_call({is_interpreted, Mod, Name, Arity}, _From, State) -> Db = State#state.db, Reply = case ets:lookup(Db, {Mod, refs}) of diff --git a/lib/debugger/src/dbg_wx_trace.erl b/lib/debugger/src/dbg_wx_trace.erl index 6af19af33b..29c8e8cefb 100644 --- a/lib/debugger/src/dbg_wx_trace.erl +++ b/lib/debugger/src/dbg_wx_trace.erl @@ -818,11 +818,14 @@ gui_show_module(Win, Mod, Line, _Cm, Pid, How) -> gui_load_module(Win, Mod, _Pid) -> dbg_wx_trace_win:display(Win,{text, "Loading module..."}), - %% Contents = int:contents(Mod, Pid), - {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), - Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), - dbg_wx_trace_win:display(Win,{text, ""}), - Win2. + case dbg_iserver:call({raw_contents, Mod, any}) of + {ok, Contents} -> + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2; + not_found -> + dbg_wx_trace_win:show_no_code(Win) + end. gui_update_bindings(Win,Meta) -> Bs = int:meta(Meta, bindings, nostack), diff --git a/lib/debugger/src/dbg_wx_view.erl b/lib/debugger/src/dbg_wx_view.erl index 91fc0d08cb..86d009238f 100644 --- a/lib/debugger/src/dbg_wx_view.erl +++ b/lib/debugger/src/dbg_wx_view.erl @@ -263,7 +263,11 @@ shortcut(_) -> false. gui_load_module(Win, Mod) -> dbg_wx_trace_win:display(Win,{text, "Loading module..."}), - {ok, Contents} = dbg_iserver:call({raw_contents, Mod, any}), - Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), - dbg_wx_trace_win:display(Win,{text, ""}), - Win2. + case dbg_iserver:call({raw_contents, Mod, any}) of + {ok, Contents} -> + Win2 = dbg_wx_trace_win:show_code(Win, Mod, Contents), + dbg_wx_trace_win:display(Win,{text, ""}), + Win2; + not_found -> + dbg_wx_trace_win:show_no_code(Win) + end. diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml index a5a52fee61..6400072b1f 100644 --- a/lib/dialyzer/doc/src/notes.xml +++ b/lib/dialyzer/doc/src/notes.xml @@ -32,6 +32,20 @@ <p>This document describes the changes made to the Dialyzer application.</p> +<section><title>Dialyzer 3.0.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Fix a map related bug.</p> + <p> + Own Id: OTP-13709 Aux Id: ERL-177, PR-1115 </p> + </item> + </list> + </section> + +</section> + <section><title>Dialyzer 3.0</title> <section><title>Fixed Bugs and Malfunctions</title> 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/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 9399789464..963c953447 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -978,12 +978,21 @@ handle_case(Tree, Map, State) -> false -> State1 end, Map2 = join_maps_begin(Map1), - {MapList, State3, Type} = + {MapList, State3, Type, Warns} = handle_clauses(Clauses, Arg, ArgType, ArgType, State2, - [], Map2, [], []), + [], Map2, [], [], []), + %% Non-Erlang BEAM languages, such as Elixir, expand language constructs + %% into case statements. In that case, we do not want to warn on + %% individual clauses not matching unless none of them can. + SupressForced = is_compiler_generated(cerl:get_ann(Tree)) + andalso not (t_is_none(Type)), + State4 = lists:foldl(fun({T,R,M,F}, S) -> + state__add_warning( + S,T,R,M,F andalso (not SupressForced)) + end, State3, Warns), Map3 = join_maps_end(MapList, Map2), debug_pp_map(Map3), - {State3, Map3, Type} + {State4, Map3, Type} end. %%---------------------------------------- @@ -1082,22 +1091,24 @@ handle_receive(Tree, Map, State) -> RaceListSize + 1, State); false -> State end, - {MapList, State2, ReceiveType} = + {MapList, State2, ReceiveType, Warns} = handle_clauses(Clauses, ?no_arg, t_any(), t_any(), State1, [], Map, - [], []), + [], [], []), + State3 = lists:foldl(fun({T,R,M,F}, S) -> state__add_warning(S,T,R,M,F) end, + State2, Warns), Map1 = join_maps(MapList, Map), - {State3, Map2, TimeoutType} = traverse(Timeout, Map1, State2), - Opaques = State3#state.opaques, + {State4, Map2, TimeoutType} = traverse(Timeout, Map1, State3), + Opaques = State4#state.opaques, case (t_is_atom(TimeoutType, Opaques) andalso (t_atom_vals(TimeoutType, Opaques) =:= ['infinity'])) of true -> - {State3, Map2, ReceiveType}; + {State4, Map2, ReceiveType}; false -> Action = cerl:receive_action(Tree), - {State4, Map3, ActionType} = traverse(Action, Map, State3), + {State5, Map3, ActionType} = traverse(Action, Map, State4), Map4 = join_maps([Map3, Map1], Map), Type = t_sup(ReceiveType, ActionType), - {State4, Map4, Type} + {State5, Map4, Type} end. %%---------------------------------------- @@ -1245,7 +1256,7 @@ handle_tuple(Tree, Map, State) -> %% Clauses %% handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, - Acc, ClauseAcc) -> + Acc, ClauseAcc, WarnAcc0) -> IsRaceAnalysisEnabled = is_race_analysis_enabled(State), State1 = case IsRaceAnalysisEnabled of @@ -1258,8 +1269,8 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, State); false -> State end, - {State2, ClauseMap, BodyType, NewArgType} = - do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1), + {State2, ClauseMap, BodyType, NewArgType, WarnAcc} = + do_clause(C, Arg, ArgType, OrigArgType, MapIn, State1, WarnAcc0), {NewClauseAcc, State3} = case IsRaceAnalysisEnabled of true -> @@ -1277,9 +1288,9 @@ handle_clauses([C|Left], Arg, ArgType, OrigArgType, State, CaseTypes, MapIn, false -> {[BodyType|CaseTypes], [ClauseMap|Acc]} end, handle_clauses(Left, Arg, NewArgType, OrigArgType, State3, - NewCaseTypes, MapIn, NewAcc, NewClauseAcc); + NewCaseTypes, MapIn, NewAcc, NewClauseAcc, WarnAcc); handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, - ClauseAcc) -> + ClauseAcc, WarnAcc) -> State1 = case is_race_analysis_enabled(State) of true -> @@ -1289,9 +1300,9 @@ handle_clauses([], _Arg, _ArgType, _OrigArgType, State, CaseTypes, _MapIn, Acc, RaceListSize + 1, State); false -> State end, - {lists:reverse(Acc), State1, t_sup(CaseTypes)}. + {lists:reverse(Acc), State1, t_sup(CaseTypes), WarnAcc}. -do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> +do_clause(C, Arg, ArgType0, OrigArgType, Map, State, Warns) -> Pats = cerl:clause_pats(C), Guard = cerl:clause_guard(C), Body = cerl:clause_body(C), @@ -1323,7 +1334,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> [cerl_prettypr:format(C), format_type(ArgType0, State1)]), case state__warning_mode(State1) of false -> - {State1, Map, t_none(), ArgType0}; + {State1, Map, t_none(), ArgType0, Warns}; true -> {Msg, Force} = case t_is_none(ArgType0) of @@ -1403,8 +1414,7 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {record_match, _} -> ?WARN_MATCHING; {pattern_match_cov, _} -> ?WARN_MATCHING end, - {state__add_warning(State1, WarnType, C, Msg, Force), - Map, t_none(), ArgType0} + {State1, Map, t_none(), ArgType0, [{WarnType, C, Msg, Force}|Warns]} end; {Map2, PatTypes} -> Map3 = @@ -1437,9 +1447,9 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> false -> {guard_fail_pat, [PatString, format_type(ArgType0, State1)]} end, - State2 = + Warn = case Reason of - none -> state__add_warning(State1, ?WARN_MATCHING, C, DefaultMsg); + none -> {?WARN_MATCHING, C, DefaultMsg, false}; {FailGuard, Msg} -> case is_compiler_generated(cerl:get_ann(FailGuard)) of false -> @@ -1448,15 +1458,15 @@ do_clause(C, Arg, ArgType0, OrigArgType, Map, State) -> {neg_guard_fail, _} -> ?WARN_MATCHING; {opaque_guard, _} -> ?WARN_OPAQUE end, - state__add_warning(State1, WarnType, FailGuard, Msg); + {WarnType, FailGuard, Msg, false}; true -> - state__add_warning(State1, ?WARN_MATCHING, C, Msg) + {?WARN_MATCHING, C, Msg, false} end end, - {State2, Map, t_none(), NewArgType}; + {State1, Map, t_none(), NewArgType, [Warn|Warns]}; Map4 -> {RetState, RetMap, BodyType} = traverse(Body, Map4, State1), - {RetState, RetMap, BodyType, NewArgType} + {RetState, RetMap, BodyType, NewArgType, Warns} end end. diff --git a/lib/dialyzer/test/Makefile b/lib/dialyzer/test/Makefile index f43e04dd59..0d8fba438c 100644 --- a/lib/dialyzer/test/Makefile +++ b/lib/dialyzer/test/Makefile @@ -12,6 +12,7 @@ AUXILIARY_FILES=\ dialyzer_common.erl\ file_utils.erl\ dialyzer_SUITE.erl\ + abstract_SUITE.erl\ plt_SUITE.erl # ---------------------------------------------------- diff --git a/lib/dialyzer/test/abstract_SUITE.erl b/lib/dialyzer/test/abstract_SUITE.erl new file mode 100644 index 0000000000..269db3e836 --- /dev/null +++ b/lib/dialyzer/test/abstract_SUITE.erl @@ -0,0 +1,102 @@ +%% This suite contains cases that cannot be written +%% in Erlang itself and must be done via the abstract +%% format. + +-module(abstract_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include("dialyzer_test_constants.hrl"). + +-export([suite/0, all/0, init_per_suite/0, init_per_suite/1]). +-export([generated_case/1]). + +suite() -> + [{timetrap, {minutes, 1}}]. +all() -> + [generated_case]. + +init_per_suite() -> + [{timetrap, ?plt_timeout}]. +init_per_suite(Config) -> + OutDir = ?config(priv_dir, Config), + case dialyzer_common:check_plt(OutDir) of + fail -> {skip, "Plt creation/check failed."}; + ok -> [{dialyzer_options, []}|Config] + end. + +generated_case(Config) when is_list(Config) -> + %% Equivalent to: + %% + %% -module(foo). + %% -export(bar). + %% bar() -> + %% Arg = sample, + %% case Arg of + %% #{} -> map; + %% _ -> Arg:fn() + %% end. + %% + %% Except the case statement and its clauses are marked as autogenerated. + [] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{atom,4,sample}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,[{location,6},{generated,true}],[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + %% With the first clause not auto-generated + [{warn_matching,{_,6},_}] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{atom,4,sample}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,6,[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + %% With Arg set to [] so neither clause matches + [{warn_return_no_exit,{_,3},_}, + {warn_matching,{_,6},_}, + {warn_failing_call,{_,7},_}] = + test([{attribute,1,module,foo}, + {attribute,2,export,[{bar,0}]}, + {function,3,bar,0, + [{clause,3,[],[], + [{match,4,{var,4,'Arg'},{nil,4}}, + {'case',[{location,5},{generated,true}],{var,5,'Arg'}, + [{clause,[{location,6},{generated,true}],[{map,6,[]}],[], + [{atom,6,map}]}, + {clause,[{location,7},{generated,true}],[{var,7,'_'}],[], + [{call,7,{remote,7,{var,7,'Arg'},{atom,7,fn}},[]}]}]}]}]}], + Config, [], []), + ok. + +test(Prog, Config, COpts, DOpts) -> + {ok, BeamFile} = compile(Config, Prog, COpts), + run_dialyzer(Config, succ_typings, [BeamFile], DOpts). + +compile(Config, Prog, CompileOpts) -> + OutDir = ?config(priv_dir, Config), + Opts = [{outdir, OutDir}, debug_info, return_errors | CompileOpts], + {ok, Module, Source} = compile:forms(Prog, Opts), + BeamFile = filename:join([OutDir, lists:concat([Module, ".beam"])]), + ok = file:write_file(BeamFile, Source), + {ok, BeamFile}. + +run_dialyzer(Config, Analysis, Files, Opts) -> + OutDir = ?config(priv_dir, Config), + PltFilename = dialyzer_common:plt_file(OutDir), + dialyzer:run([{analysis_type, Analysis}, + {files, Files}, + {init_plt, PltFilename}, + {check_plt, false}, + {from, byte_code} | + Opts]). diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl index d2b1026c06..48083a2731 100644 --- a/lib/dialyzer/test/dialyzer_common.erl +++ b/lib/dialyzer/test/dialyzer_common.erl @@ -7,7 +7,7 @@ -module(dialyzer_common). --export([check_plt/1, check/4, create_all_suites/0, new_tests/2]). +-export([check_plt/1, check/4, create_all_suites/0, new_tests/2, plt_file/1]). -include_lib("kernel/include/file.hrl"). @@ -39,7 +39,7 @@ check_plt(OutDir) -> io:format("Checking plt:"), - PltFilename = filename:join(OutDir, ?plt_filename), + PltFilename = plt_file(OutDir), case file:read_file_info(PltFilename) of {ok, _} -> dialyzer_check_plt(PltFilename); {error, _ } -> @@ -63,6 +63,11 @@ check_plt(OutDir) -> end end. +-spec plt_file(string()) -> string(). + +plt_file(OutDir) -> + filename:join(OutDir, ?plt_filename). + dialyzer_check_plt(PltFilename) -> try dialyzer:run([{analysis_type, plt_check}, {init_plt, PltFilename}]) of @@ -119,7 +124,7 @@ build_plt(PltFilename) -> 'same' | {differ, [term()]}. check(TestCase, Opts, Dir, OutDir) -> - PltFilename = filename:join(OutDir, ?plt_filename), + PltFilename = plt_file(OutDir), SrcDir = filename:join(Dir, ?input_files_directory), ResDir = filename:join(Dir, ?result_files_directory), Filename = filename:join(SrcDir, atom_to_list(TestCase)), diff --git a/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/a.erl b/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/a.erl new file mode 100644 index 0000000000..827984b20b --- /dev/null +++ b/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/a.erl @@ -0,0 +1,9 @@ +-module(a). +-export([to_map/1, to_map/2]). +-type t() :: #{type := b:t()}. + +-spec to_map(t()) -> map(). +to_map(Resource) -> to_map(Resource, #{}). + +-spec to_map(t(), map()) -> map(). +to_map(_, Map) when is_map(Map) -> #{}. diff --git a/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/b.erl b/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/b.erl new file mode 100644 index 0000000000..31f9bb6b3e --- /dev/null +++ b/lib/dialyzer/test/map_SUITE_data/src/mand_remote_val/b.erl @@ -0,0 +1,3 @@ +-module(b). +-export_type([t/0]). +-type t() :: binary(). diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk index 077fe01e85..b9a28afdd9 100644 --- a/lib/dialyzer/vsn.mk +++ b/lib/dialyzer/vsn.mk @@ -1 +1 @@ -DIALYZER_VSN = 3.0 +DIALYZER_VSN = 3.0.1 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/edoc/src/edoc_extract.erl b/lib/edoc/src/edoc_extract.erl index e7a4c36ca4..2d6cb04a6d 100644 --- a/lib/edoc/src/edoc_extract.erl +++ b/lib/edoc/src/edoc_extract.erl @@ -32,9 +32,9 @@ %% %% @headerfile "edoc.hrl" (disabled until it can be made private) -include("edoc.hrl"). -%% @type filename() = file:filename(). -%% @type proplist() = proplists:property(). -%% @type syntaxTree() = erl_syntax:syntaxTree(). +%% @type filename() = //kernel/file:filename(). +%% @type proplist() = //stdlib/proplists:property(). +%% @type syntaxTree() = //syntax_tools/erl_syntax:syntaxTree(). %% @spec source(File::filename(), Env::edoc_env(), Options::proplist()) %% -> {ModuleName, edoc:edoc_module()} @@ -639,11 +639,11 @@ file_macros(_Context, Env) -> %% %% The idea is to mimic how the @type tag works. %% Using @type: -%% @type t() = t1(). Some docs of t/0; -%% Further docs of t/0. +%%```@type t() = t1(). Some docs of t/0; +%% Further docs of t/0.''' %% The same thing using -type: -%% -type t() :: t1(). % Some docs of t/0; -%% Further docs of t/0. +%%```-type t() :: t1(). % Some docs of t/0; +%% Further docs of t/0.''' find_type_docs(Forms0, Comments, Env, File) -> Tree = erl_recomment:recomment_forms(Forms0, Comments), Forms = preprocess_forms(Tree), diff --git a/lib/edoc/src/edoc_lib.erl b/lib/edoc/src/edoc_lib.erl index dcc239f6b4..cc0a8d0b94 100644 --- a/lib/edoc/src/edoc_lib.erl +++ b/lib/edoc/src/edoc_lib.erl @@ -935,7 +935,7 @@ get_doc_env(Opts) -> %% Modules = [atom()] %% proplist() = [term()] %% -%% @type proplist() = proplists:property(). +%% @type proplist() = //stdlib/proplists:property(). %% @type edoc_env(). Environment information needed by EDoc for %% generating references. The data representation is not documented. %% diff --git a/lib/edoc/src/edoc_tags.erl b/lib/edoc/src/edoc_tags.erl index 9e2e41e902..93f423b906 100644 --- a/lib/edoc/src/edoc_tags.erl +++ b/lib/edoc/src/edoc_tags.erl @@ -213,8 +213,10 @@ filter_tags([#tag{name = N, line = L} = T | Ts], Tags, Where, Ts1) -> true -> filter_tags(Ts, Tags, Where, [T | Ts1]); false -> - [warning(L, Where, "tag @~s not recognized.", [N]) || - Where =/= no], + case Where of + no -> ok; + _ -> warning(L, Where, "tag @~s not recognized.", [N]) + end, filter_tags(Ts, Tags, Where, Ts1) end; filter_tags([], _, _, Ts) -> @@ -451,7 +453,7 @@ check_type(#tag{line = L, data = Data}, P0, Ls, Ts) -> check_type(#t_def{type = Type}, P, Ls, Ts) -> check_type(Type, P, Ls, Ts); check_type(#t_type{name = Name, args = Args}, P, Ls, Ts) -> - _ = check_used_type(Name, Args, P, Ls), + check_used_type(Name, Args, P, Ls), check_types3(Args++Ts, P, Ls); check_type(#t_var{}, P, Ls, Ts) -> check_types3(Ts, P, Ls); @@ -503,7 +505,8 @@ check_used_type(#t_name{name = N, module = Mod}=Name, Args, P, LocalTypes) -> false -> #parms{warn = W, line = L, file = File} = P, %% true = ets:insert(DT, TypeName), - [type_warning(L, File, "missing type", N, NArgs) || W] + _ = [type_warning(L, File, "missing type", N, NArgs) || W], + ok end. type_warning(Line, File, S, N, NArgs) -> diff --git a/lib/erl_docgen/priv/bin/xref_mod_app.escript b/lib/erl_docgen/priv/bin/xref_mod_app.escript index ac4278bf22..4a418fe144 100755 --- a/lib/erl_docgen/priv/bin/xref_mod_app.escript +++ b/lib/erl_docgen/priv/bin/xref_mod_app.escript @@ -84,7 +84,7 @@ preloaded(TopDir) -> %% It's OK if too much data is generated as long as all applications %% and all modules are mentioned. appmods(D) -> - ErlFiles = filelib:wildcard(filename:join([D,"src","*.erl"])), + ErlFiles = filelib:wildcard(filename:join([D,"src","**","*.erl"])), AppV = filename:basename(D), App = case string:rstr(AppV, "-") of 0 -> AppV; diff --git a/lib/erl_docgen/priv/dtd/erlref.dtd b/lib/erl_docgen/priv/dtd/erlref.dtd index 835407520a..615b88b61a 100644 --- a/lib/erl_docgen/priv/dtd/erlref.dtd +++ b/lib/erl_docgen/priv/dtd/erlref.dtd @@ -29,7 +29,7 @@ <!-- `name' is used in common.refs.dtd and must therefore be defined in each *ref. dtd --> -<!ELEMENT name (#PCDATA) > +<!ELEMENT name (#PCDATA|seealso)* > <!ATTLIST name name CDATA #IMPLIED arity CDATA #IMPLIED clause_i CDATA #IMPLIED diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index fe6c7d3c28..edab8e1c7e 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -2007,10 +2007,10 @@ </xsl:variable> <xsl:choose> <xsl:when test="ancestor::datatype"> - <a name="type-{$fname}"><span class="bold_code"><xsl:value-of select="."/></span></a><br/> + <a name="type-{$fname}"></a><span class="bold_code"><xsl:apply-templates/></span><br/> </xsl:when> <xsl:otherwise> - <a name="{$fname}-{$arity}"><span class="bold_code"><xsl:value-of select="."/></span></a><br/> + <a name="{$fname}-{$arity}"></a><span class="bold_code"><xsl:apply-templates/></span><br/> </xsl:otherwise> </xsl:choose> </xsl:when> diff --git a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl index 0ac7985a48..4f3af1f767 100644 --- a/lib/erl_docgen/src/docgen_edoc_xml_cb.erl +++ b/lib/erl_docgen/src/docgen_edoc_xml_cb.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2001-2015. All Rights Reserved. +%% Copyright Ericsson AB 2001-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. @@ -563,7 +563,14 @@ otp_xmlify_a_fileref(FileRef1, AppS) -> true -> case split(Marker0, "-") of [Func,Arity] -> - Func++"/"++Arity; + try list_to_integer(Arity) of + _ -> + Func++"/"++Arity + catch + _:_ -> + %% This is "type-"<a-type>. + Marker0 + end; _ -> Marker0 end @@ -831,19 +838,49 @@ local_types([]) -> []; local_types(Es) -> local_defs2(get_elem(localdef, Es)). +-define(LOCAL_TYPES, edoc_local_defs). + local_defs2([]) -> []; local_defs2(Es) -> + case collect_local_types(Es) of + [] -> local_defs3(Es); + LocalTypes -> + ?LOCAL_TYPES = ets:new(?LOCAL_TYPES, [named_table]), + true = ets:insert(?LOCAL_TYPES, LocalTypes), + try + local_defs3(Es) + after + ets:delete(?LOCAL_TYPES) + end + end. + +local_defs3(Es) -> {type,[?NL | [{v, localdef2(E)} || E <- Es]]}. +%% Does not work well for parametrized types. +collect_local_types(Es) -> + lists:append([collect_local_type(E) || E <- Es]). + +collect_local_type(#xmlElement{content = Es}) -> + case get_elem(typevar, Es) of + [] -> + [{t_abstype(get_content(abstype, Es))}]; + [_] -> + [] + end. + %% Like localdef/1, but does not use label_anchor/2 -- we don't want any %% markers or em tags in <v> tag, plain text only! +%% When used stand-alone, EDoc generates links to local types. An ETS +%% table holds local types, to avoid generating links to them. localdef2(#xmlElement{content = Es}) -> - case get_elem(typevar, Es) of - [] -> - t_utype(get_elem(type, Es)); - [V] -> - t_var(V) ++ [" = "] ++ t_utype(get_elem(type, Es)) - end. + Var = case get_elem(typevar, Es) of + [] -> + [t_abstype(get_content(abstype, Es))]; + [V] -> + t_var(V) + end, + Var ++ [" = "] ++ t_utype(get_elem(type, Es)). type_name(#xmlElement{content = Es}) -> t_name(get_elem(erlangName, get_content(typedef, Es))). @@ -855,10 +892,9 @@ types(Ts) -> typedecl(Name, #xmlElement{content = Es}) -> TypedefEs = get_content(typedef, Es), Id = "type-"++Name, - [{tag, typedef(TypedefEs)}, + [{tag, [{marker,[{id,Id}],[]}] ++ typedef(TypedefEs)}, ?NL, - {item, [{marker,[{id,Id}],[]} | - local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)]}, + {item, local_defs(get_elem(localdef, TypedefEs)) ++ fulldesc(Es)}, ?NL]. typedef(Es) -> @@ -866,14 +902,14 @@ typedef(Es) -> ++ seq(fun t_utype_elem/1, get_content(argtypes, Es), [")"])), case get_elem(type, Es) of [] -> - [{tt, Name}]; + Name; Type -> - [{tt, Name ++ [" = "] ++ t_utype(Type)}] + Name ++ [" = "] ++ t_utype(Type) end. -local_defs([]) -> []; +local_defs([]) -> [{p,[]}]; local_defs(Es) -> - [?NL, {ul, [{li, [{tt, localdef(E)}]} || E <- Es]}]. + [?NL, {ul, [{li, [{p, localdef(E)}]} || E <- Es]}]. localdef(E = #xmlElement{content = Es}) -> Var = case get_elem(typevar, Es) of @@ -917,6 +953,7 @@ seealso_module(Es) -> Es1 -> {section,[{title,["See also"]},{p,seq(fun see/1, Es1, [])}]} end. + seealso_function(Es) -> case get_elem(see, Es) of [] -> []; @@ -988,7 +1025,14 @@ t_name([E]) -> end. t_utype([E]) -> - t_utype_elem(E). + flatten_type(t_utype_elem(E)). + +%% Make sure see also are top elements of lists. +flatten_type(T) -> + [case is_integer(E) of + true -> [E]; + false -> E + end || E <- lists:flatten(T)]. t_utype_elem(E=#xmlElement{content = Es}) -> case get_attrval(name, E) of @@ -1021,16 +1065,14 @@ t_type([#xmlElement{name = tuple, content = Es}]) -> t_tuple(Es); t_type([#xmlElement{name = 'fun', content = Es}]) -> t_fun(Es); -t_type([#xmlElement{name = abstype, content = Es}]) -> - t_abstype(Es); +t_type([E = #xmlElement{name = abstype, content = Es}]) -> + t_abstype(E, Es); t_type([#xmlElement{name = union, content = Es}]) -> t_union(Es); t_type([#xmlElement{name = record, content = Es}]) -> t_record(Es); t_type([#xmlElement{name = map, content = Es}]) -> - t_map(Es); -t_type([#xmlElement{name = map_field, content = Es}]) -> - t_map_field(Es). + t_map(Es). t_var(E) -> [get_attrval(name, E)]. @@ -1065,35 +1107,53 @@ t_fun(Es) -> t_record([E|Es]) -> ["#", get_attrval(value, E), "{"++ seq(fun t_field/1, Es) ++"}"]. + t_field(#xmlElement{name=field, content=[Atom,Type]}) -> [get_attrval(value, Atom), "="] ++ t_utype_elem(Type). t_map(Es) -> - ["#{"] ++ seq(fun t_utype_elem/1, Es, ["}"]). + ["#{"] ++ seq(fun t_map_field/1, Es, ["}"]). + +t_map_field(E = #xmlElement{name = map_field, content = [K,V]}) -> + KElem = t_utype_elem(K), + VElem = t_utype_elem(V), + AS = case get_attrval(assoc_type, E) of + "assoc" -> " => "; + "exact" -> " := " + end, + [KElem ++ AS ++ VElem]. -t_map_field([K,V]) -> - [t_utype_elem(K) ++ " => " ++ t_utype_elem(V)]. +t_abstype(E, Es) -> + see_type(E, t_abstype(Es)). t_abstype(Es) -> - case split_at_colon(t_name(get_elem(erlangName, Es)),[]) of - {Mod,Type} -> - [Type, "("] ++ - seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) ++ - [" (see module ", Mod, ")"]; - Type -> - [Type, "("] ++ - seq(fun t_utype_elem/1, get_elem(type, Es), [")"]) + Name = t_name(get_elem(erlangName, Es)), + [Name, "("] ++ seq(fun t_utype_elem/1, get_elem(type, Es), [")"]). + +see_type(E, Es0) -> + case get_attrval(href, E) of + [] -> Es0; + Href0 -> + try + false = is_local_type(Es0), + %% Fails for parametrized types: + Text = #xmlText{value = lists:append(Es0)}, + {Href, Es} = otp_xmlify_a_href(Href0, [Text]), + [{seealso, [{marker, Href}], Es}] + catch + _:_ -> + Es0 + end end. -%% Split at one colon, but not at two (or more) -split_at_colon([$:,$:|_]=Rest,Acc) -> - lists:reverse(Acc)++Rest; -split_at_colon([$:|Type],Acc) -> - {lists:reverse(Acc),Type}; -split_at_colon([Char|Rest],Acc) -> - split_at_colon(Rest,[Char|Acc]); -split_at_colon([],Acc) -> - lists:reverse(Acc). +is_local_type(Es) -> + try + [_] = ets:lookup(?LOCAL_TYPES, Es), + true + catch + _:_-> + false + end. t_union(Es) -> seq(fun t_utype_elem/1, Es, " | ", []). diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c index 6dc51adee1..624100ad49 100644 --- a/lib/erl_interface/src/connect/ei_connect.c +++ b/lib/erl_interface/src/connect/ei_connect.c @@ -1708,28 +1708,36 @@ error: static int get_home(char *buf, int size) { - char* homedrive; - char* homepath; - #ifdef __WIN32__ - homedrive = getenv("HOMEDRIVE"); - homepath = getenv("HOMEPATH"); -#else - homedrive = ""; - homepath = getenv("HOME"); -#endif + char* homedrive = getenv("HOMEDRIVE"); + char* homepath = getenv("HOMEPATH"); - if (!homedrive || !homepath) { - buf[0] = '.'; - buf[1] = '\0'; - return 1; - } else if (strlen(homedrive)+strlen(homepath) < size-1) { + if (homedrive && homepath) { + if (strlen(homedrive)+strlen(homepath) >= size) + return 0; strcpy(buf, homedrive); strcat(buf, homepath); return 1; } - - return 0; + else { + int len = GetWindowsDirectory(buf, size); + if (len) { + return (len < size); + } + } +#else + char* homepath = getenv("HOME"); + if (homepath) { + if (strlen(homepath) >= size) + return 0; + strcpy(buf, homepath); + return 1; + } +#endif + + buf[0] = '.'; + buf[1] = '\0'; + return 1; } 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/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl b/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl index 5451f1fe7d..b1f7bd7572 100644 --- a/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl +++ b/lib/hipe/amd64/hipe_amd64_ra_sse2_postconditions.erl @@ -87,22 +87,29 @@ do_fp_unop(I, TempMap) -> %%% Fix an fmove op. do_fmove(I, TempMap) -> #fmove{src=Src,dst=Dst} = I, - case is_mem_opnd(Dst, TempMap) and is_mem_opnd(Src, TempMap) of + case + (is_mem_opnd(Src, TempMap) andalso is_mem_opnd(Dst, TempMap)) + orelse (is_mem_opnd(Src, TempMap) andalso (not is_float_temp(Dst))) + orelse ((not is_float_temp(Src)) andalso is_mem_opnd(Dst, TempMap)) + of true -> - Tmp = clone(Src), + Tmp = spill_temp(double), {[#fmove{src=Src, dst=Tmp},I#fmove{src=Tmp,dst=Dst}], true}; false -> {[I], false} end. +is_float_temp(#x86_temp{type=Type}) -> Type =:= double; +is_float_temp(#x86_mem{}) -> false. + %%% Check if an operand denotes a memory cell (mem or pseudo). is_mem_opnd(Opnd, TempMap) -> R = case Opnd of #x86_mem{} -> true; - #x86_temp{} -> + #x86_temp{type=double} -> Reg = hipe_x86:temp_reg(Opnd), case hipe_x86:temp_is_allocatable(Opnd) of true -> @@ -176,6 +183,9 @@ clone(Dst) -> #x86_mem{} -> hipe_x86:mem_type(Dst); #x86_temp{} -> hipe_x86:temp_type(Dst) end, + spill_temp(Type). + +spill_temp(Type) -> hipe_x86:mk_new_temp(Type). %%% Make a certain reg into a clone of Dst diff --git a/lib/hipe/arm/hipe_arm_registers.erl b/lib/hipe/arm/hipe_arm_registers.erl index 24cd929d41..dcf039676b 100644 --- a/lib/hipe/arm/hipe_arm_registers.erl +++ b/lib/hipe/arm/hipe_arm_registers.erl @@ -67,6 +67,8 @@ -define(R15, 15). -define(LAST_PRECOLOURED, 15). % must handle both GPR and FPR ranges +-define(LR, ?R14). + -define(ARG0, ?R1). -define(ARG1, ?R2). -define(ARG2, ?R3). @@ -114,7 +116,7 @@ stack_pointer() -> ?STACK_POINTER. proc_pointer() -> ?PROC_POINTER. -lr() -> ?R14. +lr() -> ?LR. pc() -> ?R15. @@ -198,7 +200,9 @@ call_clobbered() -> % does the RA strip the type or not? ]. tailcall_clobbered() -> % tailcall crapola needs one temp - [{?TEMP1,tagged},{?TEMP1,untagged}]. + [{?TEMP1,tagged},{?TEMP1,untagged} + ,{?LR,tagged},{?LR,untagged} + ]. live_at_return() -> [%%{?LR,untagged}, diff --git a/lib/hipe/arm/hipe_rtl_to_arm.erl b/lib/hipe/arm/hipe_rtl_to_arm.erl index ad5a559995..93342aba33 100644 --- a/lib/hipe/arm/hipe_rtl_to_arm.erl +++ b/lib/hipe/arm/hipe_rtl_to_arm.erl @@ -148,10 +148,11 @@ mk_shift_ir(S, Dst, Src1, ShiftOp, Src2) -> mk_li(Tmp, Src1, mk_shift_rr(S, Dst, Tmp, ShiftOp, Src2)). -mk_shift_ri(S, Dst, Src1, ShiftOp, Src2) when is_integer(Src2) -> - if Src2 >= 0, Src2 < 32 -> ok; - true -> io:format("~w: excessive immediate shift ~w\n", [?MODULE,Src2]) - end, +mk_shift_ri(S, Dst, Src1, ShiftOp, 0) + when ShiftOp =:= lsl; ShiftOp =:= lsr; ShiftOp =:= asr -> + [hipe_arm:mk_move(S, Dst, Src1)]; +mk_shift_ri(S, Dst, Src1, ShiftOp, Src2) + when is_integer(Src2), Src2 > 0, Src2 < 32 -> Am1 = {Src1,ShiftOp,Src2}, [hipe_arm:mk_move(S, Dst, Am1)]. diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl index 7826dada9d..243bb6e25d 100644 --- a/lib/hipe/cerl/erl_types.erl +++ b/lib/hipe/cerl/erl_types.erl @@ -1664,10 +1664,12 @@ t_map(Pairs0, DefK0, DefV0) -> %% define(DEBUG, true). try validate_map_elements(Pairs) - catch error:badarg -> error(badarg, [Pairs0,DefK0,DefV0]); - error:{badarg, E} -> error({badarg, E}, [Pairs0,DefK0,DefV0]) + catch error:badarg -> error(badarg, [Pairs0,DefK0,DefV0]) end, - ?map(Pairs, DefK, DefV). + case map_pairs_are_none(Pairs) of + true -> ?none; + false -> ?map(Pairs, DefK, DefV) + end. normalise_map_optionals([], _, _) -> []; normalise_map_optionals([E={K,?opt,?none}|T], DefK, DefV) -> @@ -1684,7 +1686,6 @@ normalise_map_optionals([E={K,?opt,V}|T], DefK, DefV) -> normalise_map_optionals([E|T], DefK, DefV) -> [E|normalise_map_optionals(T, DefK, DefV)]. -validate_map_elements([{_,?mand,?none}|_]) -> error({badarg, none_in_mand}); validate_map_elements([{K1,_,_}|Rest=[{K2,_,_}|_]]) -> case is_singleton_type(K1) andalso K1 < K2 of false -> error(badarg); @@ -1697,6 +1698,10 @@ validate_map_elements([{K,_,_}]) -> end; validate_map_elements([]) -> true. +map_pairs_are_none([]) -> false; +map_pairs_are_none([{_,?mand,?none}|_]) -> true; +map_pairs_are_none([_|Ps]) -> map_pairs_are_none(Ps). + -spec t_is_map(erl_type()) -> boolean(). t_is_map(Type) -> @@ -2833,12 +2838,7 @@ t_inf(?map(_, ADefK, ADefV) = A, ?map(_, BDefK, BDefV) = B, _Opaques) -> %% becomes mandatory in the infinumum (K, _, V1, _, V2) -> {K, ?mand, t_inf(V1, V2)} end, A, B), - %% If the infinimum of any mandatory values is ?none, the entire map infinimum - %% is ?none. - case lists:any(fun({_,?mand,?none})->true; ({_,_,_}) -> false end, Pairs) of - true -> t_none(); - false -> t_map(Pairs, t_inf(ADefK, BDefK), t_inf(ADefV, BDefV)) - end; + t_map(Pairs, t_inf(ADefK, BDefK), t_inf(ADefV, BDefV)); t_inf(?matchstate(Pres1, Slots1), ?matchstate(Pres2, Slots2), _Opaques) -> ?matchstate(t_inf(Pres1, Pres2), t_inf(Slots1, Slots2)); t_inf(?nil, ?nil, _Opaques) -> ?nil; diff --git a/lib/hipe/ppc/hipe_ppc.erl b/lib/hipe/ppc/hipe_ppc.erl index 0fa96162f6..380e791bc1 100644 --- a/lib/hipe/ppc/hipe_ppc.erl +++ b/lib/hipe/ppc/hipe_ppc.erl @@ -167,8 +167,10 @@ temp_is_precoloured(#ppc_temp{reg=Reg,type=Type}) -> _ -> hipe_ppc_registers:is_precoloured_gpr(Reg) end. -mk_simm16(Value) -> #ppc_simm16{value=Value}. -mk_uimm16(Value) -> #ppc_uimm16{value=Value}. +mk_simm16(Value) when Value >= -(1 bsl 15), Value < (1 bsl 15) -> + #ppc_simm16{value=Value}. +mk_uimm16(Value) when Value >= 0, Value < (1 bsl 16) -> + #ppc_uimm16{value=Value}. mk_mfa(M, F, A) -> #ppc_mfa{m=M, f=F, a=A}. @@ -240,7 +242,11 @@ mk_li(Dst, Value, Tail) -> % Dst can be R0 Value =< 16#7FFFFFFF -> mk_li32(Dst, R0, Value, Tail); true -> - Highest = (Value bsr 48), % Value@highest + Highest = case (Value bsr 48) of % Value@highest + TopBitSet when TopBitSet >= (1 bsl 15) -> + TopBitSet - (1 bsl 16); % encoder needs it to be negative + FitsSimm16 -> FitsSimm16 + end, Higher = (Value bsr 32) band 16#FFFF, % Value@higher High = (Value bsr 16) band 16#FFFF, % Value@h Low = Value band 16#FFFF, % Value@l diff --git a/lib/hipe/ppc/hipe_ppc_assemble.erl b/lib/hipe/ppc/hipe_ppc_assemble.erl index ff9da01b11..d89ff6235c 100644 --- a/lib/hipe/ppc/hipe_ppc_assemble.erl +++ b/lib/hipe/ppc/hipe_ppc_assemble.erl @@ -175,7 +175,8 @@ do_slwi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 32 -> {Dst, Src1, {sh,N}, {mb,0}, {me,31-N}}. do_srwi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 32 -> - {Dst, Src1, {sh,32-N}, {mb,N}, {me,31}}. + %% SH should be 0 (not 32) when N is 0 + {Dst, Src1, {sh,(32-N) band 31}, {mb,N}, {me,31}}. do_srawi_src2({uimm,N}) when is_integer(N), 0 =< N, N < 32 -> {sh,N}. @@ -184,7 +185,8 @@ do_sldi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 64 -> {Dst, Src1, {sh6,N}, {me6,63-N}}. do_srdi_opnds(Dst, Src1, {uimm,N}) when is_integer(N), 0 =< N, N < 64 -> - {Dst, Src1, {sh6,64-N}, {mb6,N}}. + %% SH should be 0 (not 64) when N is 0 + {Dst, Src1, {sh6,(64-N) band 63}, {mb6,N}}. do_sradi_src2({uimm,N}) when is_integer(N), 0 =< N, N < 64 -> {sh6,N}. @@ -246,6 +248,7 @@ do_load(I) -> case LdOp of 'ld' -> do_disp_ds(Disp); 'ldu' -> do_disp_ds(Disp); + 'lwa' -> do_disp_ds(Disp); _ -> do_disp(Disp) end, NewBase = do_reg(Base), diff --git a/lib/hipe/sparc/hipe_sparc_registers.erl b/lib/hipe/sparc/hipe_sparc_registers.erl index 884215702b..6681a10070 100644 --- a/lib/hipe/sparc/hipe_sparc_registers.erl +++ b/lib/hipe/sparc/hipe_sparc_registers.erl @@ -86,6 +86,8 @@ -define(I7, 31). -define(LAST_PRECOLOURED,31). % must handle both GRP and FPR ranges +-define(RA, ?O7). + -define(ARG0, ?O1). -define(ARG1, ?O2). -define(ARG2, ?O3). @@ -174,7 +176,7 @@ stack_pointer() -> ?STACK_POINTER. proc_pointer() -> ?PROC_POINTER. -return_address() -> ?O7. +return_address() -> ?RA. g0() -> ?G0. @@ -283,7 +285,9 @@ call_clobbered() -> % does the RA strip the type or not? ]. tailcall_clobbered() -> % tailcall crapola needs one temp - [{?TEMP1,tagged},{?TEMP1,untagged}]. + [{?TEMP1,tagged},{?TEMP1,untagged} + ,{?RA,tagged},{?RA,untagged} + ]. live_at_return() -> [{?HEAP_POINTER,untagged}, diff --git a/lib/hipe/x86/hipe_x86_postpass.erl b/lib/hipe/x86/hipe_x86_postpass.erl index 939baeccec..4515822a34 100644 --- a/lib/hipe/x86/hipe_x86_postpass.erl +++ b/lib/hipe/x86/hipe_x86_postpass.erl @@ -95,7 +95,8 @@ peep([I=#move{src=#x86_temp{reg=Src}, dst=#x86_temp{reg=Dst}}, %% ElimBinALMDouble %% ---------------- -peep([Move=#move{src=Src, dst=Dst}, Alu=#alu{src=Src, dst=Dst}|Insns], Res, Lst) -> +peep([Move=#move{src=Src, dst=Dst}, Alu=#alu{src=Src, dst=Dst}|Insns], Res, Lst) + when not is_record(Dst, x86_mem) -> peep([Alu#alu{src=Dst}|Insns], [Move|Res], [elimBinALMDouble|Lst]); 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 5321203511..caa5a083a3 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,38 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.3</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> + <item> + <p> + A debug message was accidently left enabled in the ftp + client.</p> + <p> + Own Id: OTP-13712 Aux Id: seq13143 </p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.3</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/ftp/ftp.erl b/lib/inets/src/ftp/ftp.erl index bbf25f8e90..8bad91bf98 100644 --- a/lib/inets/src/ftp/ftp.erl +++ b/lib/inets/src/ftp/ftp.erl @@ -106,8 +106,8 @@ -type common_reason() :: 'econn' | 'eclosed' | term(). -type file_write_error_reason() :: term(). % See file:write for more info -%%-define(DBG(F,A), 'n/a'). --define(DBG(F,A), io:format(F,A)). +-define(DBG(F,A), 'n/a'). +%%-define(DBG(F,A), io:format(F,A)). %%%========================================================================= %%% API - CLIENT FUNCTIONS @@ -2099,7 +2099,7 @@ handle_ctrl_result({pos_prel, _}, #state{caller = {transfer_data, Bin}} %%-------------------------------------------------------------------------- %% Default -handle_ctrl_result({Status, Lines}, #state{client = From} = State) +handle_ctrl_result({Status, _Lines}, #state{client = From} = State) when From =/= undefined -> ctrl_result_response(Status, State, {error, Status}). 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 543e0d44fd..747724a86b 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.3 +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/code.xml b/lib/kernel/doc/src/code.xml index d3611d6a03..21f8a2d54a 100644 --- a/lib/kernel/doc/src/code.xml +++ b/lib/kernel/doc/src/code.xml @@ -651,6 +651,11 @@ ok = code:finish_loading(Prepared), <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code marked as old. If some processes still linger in the old code, these processes are killed before the code is removed.</p> + <note><p>As of ERTS version 9.0, a process is only considered + to be lingering in the code if it has direct references to the code. + For more information see documentation of + <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>, + which is used in order to determine this.</p></note> <p>Returns <c>true</c> if successful and any process is needed to be killed, otherwise <c>false</c>.</p> </desc> @@ -661,6 +666,11 @@ ok = code:finish_loading(Prepared), <desc> <p>Purges the code for <c><anno>Module</anno></c>, that is, removes code marked as old, but only if no processes linger in it.</p> + <note><p>As of ERTS version 9.0, a process is only considered + to be lingering in the code if it has direct references to the code. + For more information see documentation of + <seealso marker="erts:erlang#check_process_code/3"><c>erlang:check_process_code/3</c></seealso>, + which is used in order to determine this.</p></note> <p>Returns <c>false</c> if the module cannot be purged because of processes lingering in old code, otherwise <c>true</c>.</p> </desc> 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/init_SUITE.erl b/lib/kernel/test/init_SUITE.erl index 1370e23195..2b59eb2bfe 100644 --- a/lib/kernel/test/init_SUITE.erl +++ b/lib/kernel/test/init_SUITE.erl @@ -27,7 +27,8 @@ -export([get_arguments/1, get_argument/1, boot_var/1, restart/1, many_restarts/0, many_restarts/1, get_plain_arguments/1, - reboot/1, stop_status/1, stop/1, get_status/1, script_id/1]). + reboot/1, stop_status/1, stop/1, get_status/1, script_id/1, + find_system_processes/0]). -export([boot1/1, boot2/1]). -export([init_per_testcase/2, end_per_testcase/2]). @@ -355,12 +356,16 @@ wait_for(N,Node,EHPid) -> restart(Config) when is_list(Config) -> Args = args(), + Pa = " -pa " ++ filename:dirname(code:which(?MODULE)), + %% Currently test_server:start_node cannot be used. The restarted %% node immediately halts due to the implementation of %% test_server:start_node. - {ok, Node} = loose_node:start(init_test, Args, ?DEFAULT_TIMEOUT_SEC), + {ok, Node} = loose_node:start(init_test, Args ++ Pa, ?DEFAULT_TIMEOUT_SEC), %% Ok, the node is up, now the real test test begins. erlang:monitor_node(Node, true), + SysProcs0 = rpc:call(Node, ?MODULE, find_system_processes, []), + [InitPid, PurgerPid, LitCollectorPid, DirtyCodePid] = SysProcs0, InitPid = rpc:call(Node, erlang, whereis, [init]), PurgerPid = rpc:call(Node, erlang, whereis, [erts_code_purger]), Procs = rpc:call(Node, erlang, processes, []), @@ -375,6 +380,9 @@ restart(Config) when is_list(Config) -> end, ok = wait_restart(30, Node), + SysProcs1 = rpc:call(Node, ?MODULE, find_system_processes, []), + [InitPid1, PurgerPid1, LitCollectorPid1, DirtyCodePid1] = SysProcs1, + %% Still the same init process! InitPid1 = rpc:call(Node, erlang, whereis, [init]), InitP = pid_to_list(InitPid), @@ -385,8 +393,24 @@ restart(Config) when is_list(Config) -> PurgerP = pid_to_list(PurgerPid), PurgerP = pid_to_list(PurgerPid1), + %% and same literal area collector process! + case LitCollectorPid of + undefined -> undefined = LitCollectorPid1; + _ -> + LitCollectorP = pid_to_list(LitCollectorPid), + LitCollectorP = pid_to_list(LitCollectorPid1) + end, + + %% and same dirty process code checker process! + case DirtyCodePid of + undefined -> undefined = DirtyCodePid1; + _ -> + DirtyCodeP = pid_to_list(DirtyCodePid), + DirtyCodeP = pid_to_list(DirtyCodePid1) + end, + NewProcs0 = rpc:call(Node, erlang, processes, []), - NewProcs = NewProcs0 -- [InitPid1, PurgerPid1], + NewProcs = NewProcs0 -- SysProcs1, case check_processes(NewProcs, MaxPid) of true -> ok; @@ -406,6 +430,37 @@ restart(Config) when is_list(Config) -> loose_node:stop(Node), ok. +-record(sys_procs, {init, + code_purger, + literal_collector, + dirty_proc_checker}). + +find_system_processes() -> + find_system_procs(processes(), #sys_procs{}). + +find_system_procs([], SysProcs) -> + [SysProcs#sys_procs.init, + SysProcs#sys_procs.code_purger, + SysProcs#sys_procs.literal_collector, + SysProcs#sys_procs.dirty_proc_checker]; +find_system_procs([P|Ps], SysProcs) -> + case process_info(P, initial_call) of + {initial_call,{otp_ring0,start,2}} -> + undefined = SysProcs#sys_procs.init, + find_system_procs(Ps, SysProcs#sys_procs{init = P}); + {initial_call,{erts_code_purger,start,0}} -> + undefined = SysProcs#sys_procs.code_purger, + find_system_procs(Ps, SysProcs#sys_procs{code_purger = P}); + {initial_call,{erts_literal_area_collector,start,0}} -> + undefined = SysProcs#sys_procs.literal_collector, + find_system_procs(Ps, SysProcs#sys_procs{literal_collector = P}); + {initial_call,{erts_dirty_process_code_checker,start,0}} -> + undefined = SysProcs#sys_procs.dirty_proc_checker, + find_system_procs(Ps, SysProcs#sys_procs{dirty_proc_checker = P}); + _ -> + find_system_procs(Ps, SysProcs) + end. + wait_restart(0, _Node) -> ct:fail(not_restarted); wait_restart(N, Node) -> 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/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml index 505d0dcc89..f79f75fead 100644 --- a/lib/observer/doc/src/notes.xml +++ b/lib/observer/doc/src/notes.xml @@ -32,6 +32,23 @@ <p>This document describes the changes made to the Observer application.</p> +<section><title>Observer 2.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Fixed a crash happening when observing another node, who + have a different number of schedulers than the current + one.</p> + <p> + Own Id: OTP-13702 Aux Id: ERL-171 </p> + </item> + </list> + </section> + +</section> + <section><title>Observer 2.2</title> <section><title>Improvements and New Features</title> diff --git a/lib/observer/src/observer_perf_wx.erl b/lib/observer/src/observer_perf_wx.erl index 1010a6af4c..6a1aac92c7 100644 --- a/lib/observer/src/observer_perf_wx.erl +++ b/lib/observer/src/observer_perf_wx.erl @@ -235,12 +235,14 @@ terminate(_Event, #state{appmon=Pid}) -> code_change(_, _, State) -> State. -restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti}=State) -> +restart_fetcher(Node, #state{appmon=Old, panel=Panel, time=#ti{fetch=Freq}=Ti, wins=Wins0}=State) -> catch Old ! exit, Me = self(), Pid = spawn_link(Node, observer_backend, fetch_stats, [Me, round(1000/Freq)]), wxWindow:refresh(Panel), - precalc(State#state{active=true, appmon=Pid, samples=reset_data(), time=Ti#ti{tick=0}}). + Wins = [W#win{state=undefined} || W <- Wins0], + precalc(State#state{active=true, appmon=Pid, samples=reset_data(), + wins=Wins, time=Ti#ti{tick=0}}). reset_data() -> {0, queue:new()}. @@ -253,18 +255,25 @@ add_data(Stats, {N, Q}, Wins, _, Active) -> add_data_1([#win{state={_,St}}|_]=Wins0, Last, N, {Drop, Q}, Active) when St /= undefined -> - {Wins, Stat} = - lists:mapfoldl(fun(Win0, Entry) -> - {Win1,Stat} = add_data_2(Win0, Last, Entry), - case Active of - true -> - Win = add_data_3(Win1, N, Drop, Stat, Q), - {Win, Stat}; - false -> - {Win1, Stat} - end - end, #{}, Wins0), - {Wins, {N,queue:in(Stat#{}, Q)}}; + try + {Wins, Stat} = + lists:mapfoldl(fun(Win0, Entry) -> + {Win1,Stat} = add_data_2(Win0, Last, Entry), + case Active of + true -> + Win = add_data_3(Win1, N, Drop, Stat, Q), + {Win, Stat}; + false -> + {Win1, Stat} + end + end, #{}, Wins0), + {Wins, {N,queue:in(Stat#{}, Q)}} + catch no_scheduler_change -> + {[Win#win{state=init_data(Id, Last), + info = info(Id, Last)} + || #win{name=Id}=Win <- Wins0], {0,queue:new()}} + end; + add_data_1(Wins, Stats, 1, {_, Q}, _) -> {[Win#win{state=init_data(Id, Stats), info = info(Id, Stats)} @@ -409,7 +418,8 @@ collect_data(utilz, MemInfo, Max) -> calc_delta([{Id, WN, TN}|Ss], [{Id, WP, TP}|Ps]) -> [100*(WN-WP) div (TN-TP)|calc_delta(Ss, Ps)]; -calc_delta([], []) -> []. +calc_delta([], []) -> []; +calc_delta(_, _) -> throw(no_scheduler_change). precalc(#state{samples=Data0, paint=Paint, time=Ti, wins=Wins0}=State) -> Wins = [precalc(Ti, Data0, Paint, Win) || Win <- Wins0], diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk index f214810199..9a7c6a546f 100644 --- a/lib/observer/vsn.mk +++ b/lib/observer/vsn.mk @@ -1 +1 @@ -OBSERVER_VSN = 2.2 +OBSERVER_VSN = 2.2.1 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.hrl b/lib/reltool/src/reltool.hrl index 9ac22b9450..89b5f3a997 100644 --- a/lib/reltool/src/reltool.hrl +++ b/lib/reltool/src/reltool.hrl @@ -154,19 +154,19 @@ -record(app_info, { - description = "" :: string(), - id = "" :: string(), - vsn = "" :: app_vsn(), - modules = [] :: [mod_name()], - maxP = infinity :: integer() | infinity, - maxT = infinity :: integer() | infinity, - registered = [] :: [atom()], - incl_apps = [] :: [app_name()], - applications = [] :: [app_name()], - env = [] :: [{atom(), term()}], - mod = undefined :: {mod_name(), [term()]} | undefined, - start_phases = undefined :: [{atom(), term()}] | undefined, - runtime_dependencies = [] :: [string()] + description = "" :: '_' | string(), + id = "" :: '_' | string(), + vsn = "" :: '_' | app_vsn(), + modules = [] :: '_' | [mod_name()], + maxP = infinity :: '_' | integer() | infinity, + maxT = infinity :: '_' | integer() | infinity, + registered = [] :: '_' | [atom()], + incl_apps = [] :: '_' | '$3' | [app_name()], + applications = [] :: '_' | '$2' | [app_name()], + env = [] :: '_' | [{atom(), term()}], + mod = undefined :: '_' | {mod_name(), [term()]} | undefined, + start_phases = undefined :: '_' | [{atom(), term()}] | undefined, + runtime_dependencies = [] :: '_' | [string()] }). -record(regexp, {source, compiled}). 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/src/sasl.app.src b/lib/sasl/src/sasl.app.src index 4ee8a7d6c8..633cdfa070 100644 --- a/lib/sasl/src/sasl.app.src +++ b/lib/sasl/src/sasl.app.src @@ -46,5 +46,5 @@ {errlog_type, all}]}, {mod, {sasl, []}}, {runtime_dependencies, ["tools-2.6.14","stdlib-3.0","kernel-5.0", - "erts-8.0"]}]}. + "erts-8.1"]}]}. diff --git a/lib/sasl/src/systools_make.erl b/lib/sasl/src/systools_make.erl index 352e4984df..efe6cc9eb4 100644 --- a/lib/sasl/src/systools_make.erl +++ b/lib/sasl/src/systools_make.erl @@ -1491,8 +1491,9 @@ mandatory_modules() -> preloaded() -> %% Sorted [erl_prim_loader,erl_tracer,erlang, - erts_code_purger, - erts_internal,init,otp_ring0,prim_eval,prim_file, + erts_code_purger,erts_dirty_process_code_checker, + erts_internal,erts_literal_area_collector, + init,otp_ring0,prim_eval,prim_file, prim_inet,prim_zip,zlib]. %%______________________________________________________________________ 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/snmp/src/agent/snmpa_conf.erl b/lib/snmp/src/agent/snmpa_conf.erl index 94325a8ed6..fc5116dac9 100644 --- a/lib/snmp/src/agent/snmpa_conf.erl +++ b/lib/snmp/src/agent/snmpa_conf.erl @@ -154,7 +154,7 @@ do_write_agent_conf(Fd, {intAgentMaxPacketSize = Tag, Val} ) -> do_write_agent_conf(Fd, {snmpEngineMaxMessageSize = Tag, Val} ) -> io:format(Fd, "{~w, ~w}.~n", [Tag, Val]); do_write_agent_conf(Fd, {snmpEngineID = Tag, Val} ) -> - io:format(Fd, "{~w, \"~s\"}.~n", [Tag, Val]); + io:format(Fd, "{~w, ~p}.~n", [Tag, Val]); do_write_agent_conf(_Fd, Crap) -> error({bad_agent_config, Crap}). @@ -758,9 +758,9 @@ do_write_usm_conf( PrivP, PrivKeyC, OwnPrivKeyC, Public, AuthKey, PrivKey}) -> io:format(Fd, "{", []), - io:format(Fd, "\"~s\", ", [EngineID]), - io:format(Fd, "\"~s\", ", [UserName]), - io:format(Fd, "\"~s\", ", [SecName]), + io:format(Fd, "~p, ", [EngineID]), + io:format(Fd, "~p, ", [UserName]), + io:format(Fd, "~p, ", [SecName]), io:format(Fd, "~w, ", [Clone]), io:format(Fd, "~w, ", [AuthP]), do_write_usm2(Fd, AuthKeyC, ", "), @@ -859,15 +859,15 @@ do_write_vacm_conf( {vacmSecurityToGroup, SecModel, SecName, GroupName}) -> io:format( - Fd, "{vacmSecurityToGroup, ~w, \"~s\", \"~s\"}.~n", + Fd, "{vacmSecurityToGroup, ~w, ~p, ~p}.~n", [SecModel, SecName, GroupName]); do_write_vacm_conf( Fd, {vacmAccess, GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV}) -> io:format( - Fd, "{vacmAccess, \"~s\", \"~s\", ~w, ~w, ~w, " - "\"~s\", \"~s\", \"~s\"}.~n", + Fd, "{vacmAccess, ~p, ~p, ~w, ~w, ~w, " + "~p, ~p, ~p}.~n", [GroupName, Prefix, SecModel, SecLevel, Match, RV, WV, NV]); do_write_vacm_conf( @@ -875,7 +875,7 @@ do_write_vacm_conf( {vacmViewTreeFamily, ViewIndex, ViewSubtree, ViewStatus, ViewMask}) -> io:format( - Fd, "{vacmViewTreeFamily, \"~s\", ~w, ~w, ~w}.~n", + Fd, "{vacmViewTreeFamily, ~p, ~w, ~w, ~w}.~n", [ViewIndex, ViewSubtree, ViewStatus, ViewMask]); do_write_vacm_conf(_Fd, Crap) -> error({bad_vacm_config, Crap}). diff --git a/lib/snmp/test/modules.mk b/lib/snmp/test/modules.mk index 87539f88f7..0f54e67c65 100644 --- a/lib/snmp/test/modules.mk +++ b/lib/snmp/test/modules.mk @@ -31,6 +31,7 @@ SUITE_MODULES = \ snmp_agent_mibs_test \ snmp_agent_nfilter_test \ snmp_agent_test \ + snmp_agent_conf_test \ snmp_agent_test_lib \ snmp_manager_config_test \ snmp_manager_user \ diff --git a/lib/snmp/test/snmp_SUITE.erl b/lib/snmp/test/snmp_SUITE.erl index 3b9219739b..05bd86253b 100644 --- a/lib/snmp/test/snmp_SUITE.erl +++ b/lib/snmp/test/snmp_SUITE.erl @@ -81,7 +81,8 @@ groups() -> {group, note_store_test}]}, {agent, [], [{group, mibs_test}, {group, nfilter_test}, - {group, agent_test}, + {group, agent_test}, + {group, agent_conf_test}, {group, snmpnet_test}]}, {manager, [], [{group, manager_config_test}, {group, manager_user_test}, @@ -97,6 +98,7 @@ groups() -> {mibs_test, [], [{snmp_agent_mibs_test, all}]}, {nfilter_test, [], [{snmp_agent_nfilter_test, all}]}, {agent_test, [], [{snmp_agent_test, all}]}, + {agent_conf_test, [], [{snmp_agent_conf_test, all}]}, {snmpnet_test, [], [{snmp_to_snmpnet_SUITE, all}]}, {manager_config_test, [], [{snmp_manager_config_test, all}]}, {manager_user_test, [], [{snmp_manager_user_test, all}]}, diff --git a/lib/snmp/test/snmp_agent_conf_test.erl b/lib/snmp/test/snmp_agent_conf_test.erl new file mode 100644 index 0000000000..0a22bd47d1 --- /dev/null +++ b/lib/snmp/test/snmp_agent_conf_test.erl @@ -0,0 +1,210 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2003-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. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(snmp_agent_conf_test). + +%%---------------------------------------------------------------------- +%% Include files +%%---------------------------------------------------------------------- + +%-include_lib("test_server/include/test_server.hrl"). +%-include("snmp_test_lib.hrl"). +-include_lib("common_test/include/ct.hrl"). + +-export([ + all/0, + groups/0, + init_per_suite/1, + end_per_suite/1, + + check_agent/1, + check_usm/1, + check_vacm/1 + ]). + + +all() -> [ + check_agent, + check_usm, + check_vacm + ]. + + +groups() -> + []. + + +init_per_suite(Config) -> + PrivDir = ?config(priv_dir, Config), + PrivSubdir = filename:join(PrivDir, "snmp_agent_conf_test"), + ok = filelib:ensure_dir(filename:join(PrivSubdir, "dummy")), + [{priv_subdir, PrivSubdir} | Config]. + +end_per_suite(_Config) -> + ok. + +%%====================================================================== +%% Test data +%%====================================================================== + +engine_ids() -> [ + "plain eid", + "here\"eid", + "comes\neid", + "trouble\0eid", + binary_to_list(<<"ä¸å›½å¼•æ“Žæ ‡è¯†ç¬¦"/utf8>>) +]. + +snmp_admin_strings() -> [ + "plain string", + "heres\"eid", + "trouble\neid", + binary_to_list(<<"ä¸å›½å¼•æ“Žæ ‡è¯†ç¬¦"/utf8>>) +]. + + +%%====================================================================== +%% Test functions +%%====================================================================== + + +check_agent(Config) -> + Dir = ?config(priv_subdir, Config), + lists:foreach( + fun(EngineId) -> check_agent_by_engineid(Dir, EngineId) end, + engine_ids() + ), + ok. + +check_agent_by_engineid(Dir, EngineId) -> + WEntries = [ + snmpa_conf:agent_entry(intAgentIpAddress, {0,0,0,0}), + snmpa_conf:agent_entry(intAgentUDPPort, 161), + snmpa_conf:agent_entry(snmpEngineMaxMessageSize, 484), + snmpa_conf:agent_entry(snmpEngineID, EngineId) + ], + + ok = snmpa_conf:write_agent_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_agent_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + +%%====================================================================== + +check_usm(Config) -> + Dir = ?config(priv_subdir, Config), + EngineId = hd(engine_ids()), + UserName = hd(snmp_admin_strings()), + SecName = hd(snmp_admin_strings()), + + %% vary engine id + lists:foreach( + fun(EngineId_) -> check_usm_by_params(Dir, EngineId_, UserName, SecName) end, + engine_ids() + ), + + %% vary user name + lists:foreach( + fun(UserName_) -> check_usm_by_params(Dir, EngineId, UserName_, SecName) end, + snmp_admin_strings() + ), + + %% vary sec name + lists:foreach( + fun(SecName_) -> check_usm_by_params(Dir, EngineId, UserName, SecName_) end, + snmp_admin_strings() + ), + + ok. + +check_usm_by_params(Dir, EngineId, UserName, SecName) -> + WEntries = [ + snmpa_conf:usm_entry( + EngineId, + UserName, + SecName, + zeroDotZero, + usmNoAuthProtocol, % authproto + "", "", + usmNoPrivProtocol, % privproto + "", "", "", + [], %AuthKey + []) %PrivKey + ], + + ok = snmpa_conf:write_usm_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_usm_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + +%%====================================================================== + +check_vacm(Config) -> + Dir = ?config(priv_subdir, Config), + + %% vary sec name + lists:foreach( + fun(SecName_) -> check_vacm_by_params(Dir, SecName_) end, + snmp_admin_strings() + ), + + ok. + + +check_vacm_by_params(Dir, SecName) -> + WEntries = [ + %% SecModel, SecName, GroupName + snmpa_conf:vacm_s2g_entry(usm, SecName, SecName), + %% GroupName,Prefix,SecModel,SecLevel,Match,ReadView,WriteView,NotifyView + snmpa_conf:vacm_acc_entry(SecName, "", any, noAuthNoPriv, exact, "all", "all", "all") + ], + + ok = snmpa_conf:write_vacm_config(Dir, WEntries), + {ok, REntries} = snmpa_conf:read_vacm_config(Dir), + + true = is_subset(WEntries, REntries), + ok. + + + +%%====================================================================== + + +%% additional tests needed: +% check_context() +% check_community() +% check_standard() +% check_target_addr() +% check_target_params() +% check_notify() + + +%%====================================================================== +%% Local utility functions +%%====================================================================== + +is_subset(List1, List2) -> + io:format("Check ~p is subset of ~p\n", [List1, List2]), + sets:is_subset( + sets:from_list(List1), + sets:from_list(List2) + ). diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml index fd15c334a3..f9d11b2a60 100644 --- a/lib/ssh/doc/src/notes.xml +++ b/lib/ssh/doc/src/notes.xml @@ -30,6 +30,30 @@ <file>notes.xml</file> </header> +<section><title>Ssh 4.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SSH client does not any longer retry a bad password given + as option to ssh:connect et al.</p> + <p> + Own Id: OTP-13674 Aux Id: TR-HU92273 </p> + </item> + <item> + <p> + Removed possible hanging risk for a certain timing + sequence when communicating client and server executes on + the same node.</p> + <p> + Own Id: OTP-13715</p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.3</title> <section><title>Improvements and New Features</title> @@ -108,6 +132,22 @@ </section> +<section><title>Ssh 4.2.2.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + SSH client does not any longer retry a bad password given + as option to ssh:connect et al.</p> + <p> + Own Id: OTP-13674 Aux Id: TR-HU92273 </p> + </item> + </list> + </section> + +</section> + <section><title>Ssh 4.2.2</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssh/src/ssh_auth.erl b/lib/ssh/src/ssh_auth.erl index 49eec8072f..fb5e086656 100644 --- a/lib/ssh/src/ssh_auth.erl +++ b/lib/ssh/src/ssh_auth.erl @@ -31,12 +31,111 @@ -export([publickey_msg/1, password_msg/1, keyboard_interactive_msg/1, service_request_msg/1, init_userauth_request_msg/1, userauth_request_msg/1, handle_userauth_request/3, - handle_userauth_info_request/3, handle_userauth_info_response/2 + handle_userauth_info_request/2, handle_userauth_info_response/2 ]). %%-------------------------------------------------------------------- %%% Internal application API %%-------------------------------------------------------------------- +%%%---------------------------------------------------------------- +userauth_request_msg(#ssh{userauth_methods = ServerMethods, + userauth_supported_methods = UserPrefMethods, % Note: this is not documented as supported for clients + userauth_preference = ClientMethods0 + } = Ssh0) -> + case sort_select_mthds(ClientMethods0, UserPrefMethods, ServerMethods) of + [] -> + Msg = #ssh_msg_disconnect{code = ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, + description = "Unable to connect using the available authentication methods", + language = "en"}, + {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh0)}; + + [{Pref,Module,Function,Args} | Prefs] -> + Ssh = case Pref of + "keyboard-interactive" -> Ssh0; + _ -> Ssh0#ssh{userauth_preference = Prefs} + end, + case Module:Function(Args ++ [Ssh]) of + {not_ok, Ssh1} -> + userauth_request_msg(Ssh1#ssh{userauth_preference = Prefs}); + Result -> + {Pref,Result} + end + end. + + + +sort_select_mthds(Clients, undefined, Servers) -> + %% User has not expressed an opinion via option "auth_methods", use the server's prefs + sort_select_mthds1(Clients, Servers, string:tokens(?SUPPORTED_AUTH_METHODS,",")); + +sort_select_mthds(Clients, Users0, Servers0) -> + %% The User has an opinion, use the intersection of that and the Servers whishes but + %% in the Users order + sort_select_mthds1(Clients, string:tokens(Users0,","), Servers0). + + +sort_select_mthds1(Clients, Users0, Servers0) -> + Servers = unique(Servers0), + Users = unique(Users0), + [C || Key <- Users, + lists:member(Key, Servers), + C <- Clients, + element(1,C) == Key]. + +unique(L) -> + lists:reverse( + lists:foldl(fun(E,Acc) -> + case lists:member(E,Acc) of + true -> Acc; + false -> [E|Acc] + end + end, [], L)). + + +%%%---- userauth_request_msg "callbacks" +password_msg([#ssh{opts = Opts, io_cb = IoCb, + user = User, service = Service} = Ssh0]) -> + {Password,Ssh} = + case proplists:get_value(password, Opts) of + undefined when IoCb == ssh_no_io -> + {not_ok, Ssh0}; + undefined -> + {IoCb:read_password("ssh password: ",Ssh0), Ssh0}; + PW -> + %% If "password" option is given it should not be tried again + {PW, Ssh0#ssh{opts = lists:keyreplace(password,1,Opts,{password,not_ok})}} + end, + case Password of + not_ok -> + {not_ok, Ssh}; + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "password", + data = + <<?BOOLEAN(?FALSE), + ?STRING(unicode:characters_to_binary(Password))>>}, + Ssh) + end. + +%% See RFC 4256 for info on keyboard-interactive +keyboard_interactive_msg([#ssh{user = User, + opts = Opts, + service = Service} = Ssh]) -> + case proplists:get_value(password, Opts) of + not_ok -> + {not_ok,Ssh}; % No need to use a failed pwd once more + _ -> + ssh_transport:ssh_packet( + #ssh_msg_userauth_request{user = User, + service = Service, + method = "keyboard-interactive", + data = << ?STRING(<<"">>), + ?STRING(<<>>) >> }, + Ssh) + end. + publickey_msg([Alg, #ssh{user = User, session_id = SessionId, service = Service, @@ -48,7 +147,7 @@ publickey_msg([Alg, #ssh{user = User, StrAlgo = atom_to_list(Alg), case encode_public_key(StrAlgo, ssh_transport:extract_public_key(PrivKey)) of not_ok -> - not_ok; + {not_ok, Ssh}; PubKeyBlob -> SigData = build_sig_data(SessionId, User, Service, PubKeyBlob, StrAlgo), @@ -65,52 +164,15 @@ publickey_msg([Alg, #ssh{user = User, Ssh) end; _Error -> - not_ok - end. - -password_msg([#ssh{opts = Opts, io_cb = IoCb, - user = User, service = Service} = Ssh]) -> - Password = case proplists:get_value(password, Opts) of - undefined -> - user_interaction(IoCb, Ssh); - PW -> - PW - end, - case Password of - not_ok -> - not_ok; - _ -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "password", - data = - <<?BOOLEAN(?FALSE), - ?STRING(unicode:characters_to_binary(Password))>>}, - Ssh) + {not_ok, Ssh} end. -user_interaction(ssh_no_io, _) -> - not_ok; -user_interaction(IoCb, Ssh) -> - IoCb:read_password("ssh password: ", Ssh). - - -%% See RFC 4256 for info on keyboard-interactive -keyboard_interactive_msg([#ssh{user = User, - service = Service} = Ssh]) -> - ssh_transport:ssh_packet( - #ssh_msg_userauth_request{user = User, - service = Service, - method = "keyboard-interactive", - data = << ?STRING(<<"">>), - ?STRING(<<>>) >> }, - Ssh). - +%%%---------------------------------------------------------------- service_request_msg(Ssh) -> ssh_transport:ssh_packet(#ssh_msg_service_request{name = "ssh-userauth"}, Ssh#ssh{service = "ssh-userauth"}). +%%%---------------------------------------------------------------- init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> case user_name(Opts) of {ok, User} -> @@ -140,34 +202,9 @@ init_userauth_request_msg(#ssh{opts = Opts} = Ssh) -> description = ErrStr}) end. -userauth_request_msg(#ssh{userauth_preference = []} = Ssh) -> - Msg = #ssh_msg_disconnect{code = - ?SSH_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE, - description = "Unable to connect using the available" - " authentication methods", - language = "en"}, - {disconnect, Msg, ssh_transport:ssh_packet(Msg, Ssh)}; - -userauth_request_msg(#ssh{userauth_methods = Methods, - userauth_preference = [{Pref, Module, - Function, Args} | Prefs]} - = Ssh0) -> - Ssh = Ssh0#ssh{userauth_preference = Prefs}, - case lists:member(Pref, Methods) of - true -> - case Module:Function(Args ++ [Ssh]) of - not_ok -> - userauth_request_msg(Ssh); - Result -> - {Pref,Result} - end; - false -> - userauth_request_msg(Ssh) - end. - - -handle_userauth_request(#ssh_msg_service_request{name = - Name = "ssh-userauth"}, +%%%---------------------------------------------------------------- +%%% called by server +handle_userauth_request(#ssh_msg_service_request{name = Name = "ssh-userauth"}, _, Ssh) -> {ok, ssh_transport:ssh_packet(#ssh_msg_service_accept{name = Name}, Ssh#ssh{service = "ssh-connection"})}; @@ -319,21 +356,28 @@ handle_userauth_request(#ssh_msg_userauth_request{user = User, partial_success = false}, Ssh)}. - -handle_userauth_info_request( - #ssh_msg_userauth_info_request{name = Name, - instruction = Instr, - num_prompts = NumPrompts, - data = Data}, IoCb, - #ssh{opts = Opts} = Ssh) -> +%%%---------------------------------------------------------------- +%%% keyboard-interactive client +handle_userauth_info_request(#ssh_msg_userauth_info_request{name = Name, + instruction = Instr, + num_prompts = NumPrompts, + data = Data}, + #ssh{opts = Opts, + io_cb = IoCb + } = Ssh) -> PromptInfos = decode_keyboard_interactive_prompts(NumPrompts,Data), - Responses = keyboard_interact_get_responses(IoCb, Opts, - Name, Instr, PromptInfos), - {ok, - ssh_transport:ssh_packet( - #ssh_msg_userauth_info_response{num_responses = NumPrompts, - data = Responses}, Ssh)}. + case keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) of + not_ok -> + not_ok; + Responses -> + {ok, + ssh_transport:ssh_packet( + #ssh_msg_userauth_info_response{num_responses = NumPrompts, + data = Responses}, Ssh)} + end. +%%%---------------------------------------------------------------- +%%% keyboard-interactive server handle_userauth_info_response(#ssh_msg_userauth_info_response{num_responses = 1, data = <<?UINT32(Sz), Password:Sz/binary>>}, #ssh{opts = Opts, @@ -369,11 +413,6 @@ method_preference(Algs) -> [{"publickey", ?MODULE, publickey_msg, [A]} | Acc] end, [{"password", ?MODULE, password_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, - {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []}, {"keyboard-interactive", ?MODULE, keyboard_interactive_msg, []} ], Algs). @@ -473,6 +512,9 @@ keyboard_interact_get_responses(IoCb, Opts, Name, Instr, PromptInfos) -> proplists:get_value(password, Opts, undefined), IoCb, Name, Instr, PromptInfos, Opts, NumPrompts). + +keyboard_interact_get_responses(_, _, not_ok, _, _, _, _, _, _) -> + not_ok; keyboard_interact_get_responses(_, undefined, Password, _, _, _, _, _, 1) when Password =/= undefined -> [Password]; %% Password auth implemented with keyboard-interaction and passwd is known @@ -486,17 +528,18 @@ keyboard_interact_get_responses(true, Fun, _Pwd, _IoCb, Name, Instr, PromptInfos keyboard_interact_fun(Fun, Name, Instr, PromptInfos, NumPrompts). keyboard_interact(IoCb, Name, Instr, Prompts, Opts) -> - if Name /= "" -> IoCb:format("~s~n", [Name]); - true -> ok - end, - if Instr /= "" -> IoCb:format("~s~n", [Instr]); - true -> ok - end, + write_if_nonempty(IoCb, Name), + write_if_nonempty(IoCb, Instr), lists:map(fun({Prompt, true}) -> IoCb:read_line(Prompt, Opts); ({Prompt, false}) -> IoCb:read_password(Prompt, Opts) end, Prompts). +write_if_nonempty(_, "") -> ok; +write_if_nonempty(_, <<>>) -> ok; +write_if_nonempty(IoCb, Text) -> IoCb:format("~s~n",[Text]). + + keyboard_interact_fun(KbdInteractFun, Name, Instr, PromptInfos, NumPrompts) -> Prompts = lists:map(fun({Prompt, _Echo}) -> Prompt end, PromptInfos), diff --git a/lib/ssh/src/ssh_connection_handler.erl b/lib/ssh/src/ssh_connection_handler.erl index e952a333ff..dcb6ff9343 100644 --- a/lib/ssh/src/ssh_connection_handler.erl +++ b/lib/ssh/src/ssh_connection_handler.erl @@ -60,7 +60,8 @@ ]). %%% Behaviour callbacks --export([handle_event/4, terminate/3, format_status/2, code_change/4]). +-export([callback_mode/0, handle_event/4, terminate/3, + format_status/2, code_change/4]). %%% Exports not intended to be used :). They are used for spawning and tests -export([init_connection_handler/3, % proc_lib:spawn needs this @@ -374,14 +375,12 @@ init_connection_handler(Role, Socket, Opts) -> S -> gen_statem:enter_loop(?MODULE, [], %%[{debug,[trace,log,statistics,debug]} || Role==server], - handle_event_function, {hello,Role}, S) catch _:Error -> gen_statem:enter_loop(?MODULE, [], - handle_event_function, {init_error,Error}, S0) end. @@ -428,7 +427,12 @@ init_connection(server, C = #connection{}, Opts) -> init_ssh_record(Role, Socket, Opts) -> {ok, PeerAddr} = inet:peername(Socket), KeyCb = proplists:get_value(key_cb, Opts, ssh_file), - AuthMethods = proplists:get_value(auth_methods, Opts, ?SUPPORTED_AUTH_METHODS), + AuthMethods = proplists:get_value(auth_methods, + Opts, + case Role of + server -> ?SUPPORTED_AUTH_METHODS; + client -> undefined + end), S0 = #ssh{role = Role, key_cb = KeyCb, opts = Opts, @@ -499,6 +503,9 @@ init_ssh_record(Role, Socket, Opts) -> %%% ######## Error in the initialisation #### +callback_mode() -> + handle_event_function. + handle_event(_, _Event, {init_error,Error}, _) -> case Error of {badmatch,{error,enotconn}} -> @@ -794,9 +801,13 @@ handle_event(_, #ssh_msg_userauth_banner{message = Msg}, {userauth,client}, D) - handle_event(_, #ssh_msg_userauth_info_request{} = Msg, {userauth_keyboard_interactive, client}, #data{ssh_params = Ssh0} = D) -> - {ok, {Reply, Ssh}} = ssh_auth:handle_userauth_info_request(Msg, Ssh0#ssh.io_cb, Ssh0), - send_bytes(Reply, D), - {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; + case ssh_auth:handle_userauth_info_request(Msg, Ssh0) of + {ok, {Reply, Ssh}} -> + send_bytes(Reply, D), + {next_state, {userauth_keyboard_interactive_info_response,client}, D#data{ssh_params = Ssh}}; + not_ok -> + {next_state, {userauth,client}, D, [{next_event, internal, Msg}]} + end; handle_event(_, #ssh_msg_userauth_info_response{} = Msg, {userauth_keyboard_interactive, server}, D) -> case ssh_auth:handle_userauth_info_response(Msg, D#data.ssh_params) of @@ -819,7 +830,18 @@ handle_event(_, Msg = #ssh_msg_userauth_failure{}, {userauth_keyboard_interactiv D = D0#data{ssh_params = Ssh0#ssh{userauth_preference=Prefs}}, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; -handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, D) -> +handle_event(_, Msg=#ssh_msg_userauth_failure{}, {userauth_keyboard_interactive_info_response, client}, + #data{ssh_params = Ssh0} = D0) -> + Opts = Ssh0#ssh.opts, + D = case proplists:get_value(password, Opts) of + undefined -> + D0; + _ -> + D0#data{ssh_params = + Ssh0#ssh{opts = + lists:keyreplace(password,1,Opts, + {password,not_ok})}} % FIXME:intermodule dependency + end, {next_state, {userauth,client}, D, [{next_event, internal, Msg}]}; handle_event(_, Msg=#ssh_msg_userauth_success{}, {userauth_keyboard_interactive_info_response, client}, D) -> @@ -1006,13 +1028,13 @@ handle_event({call,From}, get_print_info, StateName, D) -> {keep_state_and_data, [{reply,From,Reply}]}; handle_event({call,From}, {connection_info, Options}, _, D) -> - Info = ssh_info(Options, D, []), + Info = fold_keys(Options, fun conn_info/2, D), {keep_state_and_data, [{reply,From,Info}]}; handle_event({call,From}, {channel_info,ChannelId,Options}, _, D) -> case ssh_channel:cache_lookup(cache(D), ChannelId) of #channel{} = Channel -> - Info = ssh_channel_info(Options, Channel, []), + Info = fold_keys(Options, fun chann_info/2, Channel), {keep_state_and_data, [{reply,From,Info}]}; undefined -> {keep_state_and_data, [{reply,From,[]}]} @@ -1206,8 +1228,9 @@ handle_event(internal, prepare_next_packet, _, D) -> Sz when Sz >= Enough -> self() ! {D#data.transport_protocol, D#data.socket, <<>>}; _ -> - inet:setopts(D#data.socket, [{active, once}]) + ok end, + inet:setopts(D#data.socket, [{active, once}]), keep_state_and_data; handle_event(info, {CloseTag,Socket}, StateName, @@ -1315,12 +1338,10 @@ terminate(shutdown, StateName, State0) -> State = send_msg(#ssh_msg_disconnect{code = ?SSH_DISCONNECT_BY_APPLICATION, description = "Application shutdown"}, State0), -timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead finalize_termination(StateName, State); %% terminate({shutdown,Msg}, StateName, State0) when is_record(Msg,ssh_msg_disconnect)-> %% State = send_msg(Msg, State0), -%% timer:sleep(400), %% FIXME!!! gen_tcp:shutdown instead %% finalize_termination(StateName, Msg, State); terminate({shutdown,_R}, StateName, State) -> @@ -1382,12 +1403,12 @@ fmt_stat_rec(FieldNames, Rec, Exclude) -> state_name(), #data{}, term() - ) -> {gen_statem:callback_mode(), state_name(), #data{}}. + ) -> {ok, state_name(), #data{}}. %% . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . code_change(_OldVsn, StateName, State, _Extra) -> - {handle_event_function, StateName, State}. + {ok, StateName, State}. %%==================================================================== @@ -1635,7 +1656,6 @@ new_channel_id(#data{connection_state = #connection{channel_id_seed = Id} = disconnect(Msg=#ssh_msg_disconnect{description=Description}, _StateName, State0) -> State = send_msg(Msg, State0), disconnect_fun(Description, State), -timer:sleep(400), {stop, {shutdown,Description}, State}. %%%---------------------------------------------------------------- @@ -1644,43 +1664,43 @@ counterpart_versions(NumVsn, StrVsn, #ssh{role = server} = Ssh) -> counterpart_versions(NumVsn, StrVsn, #ssh{role = client} = Ssh) -> Ssh#ssh{s_vsn = NumVsn , s_version = StrVsn}. -ssh_info([], _State, Acc) -> - Acc; -ssh_info([client_version | Rest], #data{ssh_params = #ssh{c_vsn = IntVsn, - c_version = StringVsn}} = State, Acc) -> - ssh_info(Rest, State, [{client_version, {IntVsn, StringVsn}} | Acc]); - -ssh_info([server_version | Rest], #data{ssh_params =#ssh{s_vsn = IntVsn, - s_version = StringVsn}} = State, Acc) -> - ssh_info(Rest, State, [{server_version, {IntVsn, StringVsn}} | Acc]); -ssh_info([peer | Rest], #data{ssh_params = #ssh{peer = Peer}} = State, Acc) -> - ssh_info(Rest, State, [{peer, Peer} | Acc]); -ssh_info([sockname | Rest], #data{socket = Socket} = State, Acc) -> - {ok, SockName} = inet:sockname(Socket), - ssh_info(Rest, State, [{sockname, SockName}|Acc]); -ssh_info([user | Rest], #data{auth_user = User} = State, Acc) -> - ssh_info(Rest, State, [{user, User}|Acc]); -ssh_info([ _ | Rest], State, Acc) -> - ssh_info(Rest, State, Acc). - - -ssh_channel_info([], _, Acc) -> - Acc; +%%%---------------------------------------------------------------- +conn_info(client_version, #data{ssh_params=S}) -> {S#ssh.c_vsn, S#ssh.c_version}; +conn_info(server_version, #data{ssh_params=S}) -> {S#ssh.s_vsn, S#ssh.s_version}; +conn_info(peer, #data{ssh_params=S}) -> S#ssh.peer; +conn_info(user, D) -> D#data.auth_user; +conn_info(sockname, D) -> {ok, SockName} = inet:sockname(D#data.socket), + SockName; +%% dbg options ( = not documented): +conn_info(socket, D) -> D#data.socket; +conn_info(chan_ids, D) -> + ssh_channel:cache_foldl(fun(#channel{local_id=Id}, Acc) -> + [Id | Acc] + end, [], cache(D)). -ssh_channel_info([recv_window | Rest], #channel{recv_window_size = WinSize, - recv_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{recv_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([send_window | Rest], #channel{send_window_size = WinSize, - send_packet_size = Packsize - } = Channel, Acc) -> - ssh_channel_info(Rest, Channel, [{send_window, {{win_size, WinSize}, - {packet_size, Packsize}}} | Acc]); -ssh_channel_info([ _ | Rest], Channel, Acc) -> - ssh_channel_info(Rest, Channel, Acc). +%%%---------------------------------------------------------------- +chann_info(recv_window, C) -> + {{win_size, C#channel.recv_window_size}, + {packet_size, C#channel.recv_packet_size}}; +chann_info(send_window, C) -> + {{win_size, C#channel.send_window_size}, + {packet_size, C#channel.send_packet_size}}; +%% dbg options ( = not documented): +chann_info(pid, C) -> + C#channel.user. +%%%---------------------------------------------------------------- +%% Assisting meta function for the *_info functions +fold_keys(Keys, Fun, Extra) -> + lists:foldr(fun(Key, Acc) -> + try Fun(Key, Extra) of + Value -> [{Key,Value}|Acc] + catch + _:_ -> Acc + end + end, [], Keys). +%%%---------------------------------------------------------------- log_error(Reason) -> Report = io_lib:format("Erlang ssh connection handler failed with reason:~n" " ~p~n" @@ -1689,7 +1709,6 @@ log_error(Reason) -> [Reason, erlang:get_stacktrace()]), error_logger:error_report(Report). - %%%---------------------------------------------------------------- not_connected_filter({connection_reply, _Data}) -> true; not_connected_filter(_) -> false. diff --git a/lib/ssh/src/ssh_dbg.erl b/lib/ssh/src/ssh_dbg.erl index 480795cfc7..bd6bc0335b 100644 --- a/lib/ssh/src/ssh_dbg.erl +++ b/lib/ssh/src/ssh_dbg.erl @@ -24,6 +24,7 @@ -export([messages/0, messages/1, + messages/2, stop/0 ]). @@ -36,12 +37,16 @@ writer, acc = []}). %%%================================================================ -messages() -> messages(fun(String,_D) -> io:format(String) end). -%% messages() -> messages(fun(String,Acc) -> [String|Acc] end) +messages() -> + messages(fun(String,_D) -> io:format(String) end). messages(Write) when is_function(Write,2) -> + messages(Write, fun(X) -> X end). + +messages(Write, MangleArg) when is_function(Write,2), + is_function(MangleArg,1) -> catch dbg:start(), - setup_tracer(Write), + setup_tracer(Write, MangleArg), dbg:p(new,c), dbg_ssh_messages(). @@ -63,18 +68,30 @@ msg_formater({trace,_Pid,return_from,{ssh_message,encode,1},_Res}, D) -> msg_formater({trace,_Pid,call,{ssh_message,decode,_}}, D) -> D; msg_formater({trace,Pid,return_from,{ssh_message,decode,1},Msg}, D) -> - fmt("~nRECV ~p ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); + fmt("~n~p RECV ~s~n", [Pid,wr_record(shrink_bin(Msg))], D); msg_formater({trace,_Pid,call,{ssh_transport,select_algorithm,_}}, D) -> D; msg_formater({trace,Pid,return_from,{ssh_transport,select_algorithm,3},{ok,Alg}}, D) -> - fmt("~nALGORITHMS ~p~n~s~n", [Pid, wr_record(Alg)], D); + fmt("~n~p ALGORITHMS~n~s~n", [Pid, wr_record(Alg)], D); + + +msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Pid}, D) -> + fmt("~n~p TCP SEND on ~p~n ~p~n", [Pid,Sock, shrink_bin(Bytes)], D); + +msg_formater({trace,Pid,send,{tcp,Sock,Bytes},Dest}, D) -> + fmt("~n~p TCP SEND from ~p TO ~p~n ~p~n", [Pid,Sock,Dest, shrink_bin(Bytes)], D); msg_formater({trace,Pid,send,ErlangMsg,Dest}, D) -> - fmt("~nERL MSG ~p SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); + fmt("~n~p ERL MSG SEND TO ~p~n ~p~n", [Pid,Dest, shrink_bin(ErlangMsg)], D); + + +msg_formater({trace,Pid,'receive',{tcp,Sock,Bytes}}, D) -> + fmt("~n~p TCP RECEIVE on ~p~n ~p~n", [Pid,Sock,shrink_bin(Bytes)], D); msg_formater({trace,Pid,'receive',ErlangMsg}, D) -> - fmt("~nERL MSG ~p RECIEVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); + fmt("~n~p ERL MSG RECEIVE~n ~p~n", [Pid,shrink_bin(ErlangMsg)], D); + msg_formater(M, D) -> fmt("~nDBG ~n~p~n", [shrink_bin(M)], D). @@ -87,8 +104,10 @@ fmt(Fmt, Args, D=#data{writer=Write,acc=Acc}) -> D#data{acc = Write(io_lib:format(Fmt, Args), Acc)}. %%%---------------------------------------------------------------- -setup_tracer(Write) -> - Handler = fun msg_formater/2, +setup_tracer(Write, MangleArg) -> + Handler = fun(Arg, D) -> + msg_formater(MangleArg(Arg), D) + end, InitialData = #data{writer = Write}, {ok,_} = dbg:tracer(process, {Handler, InitialData}), ok. diff --git a/lib/ssh/src/ssh_io.erl b/lib/ssh/src/ssh_io.erl index 026d0f6151..1d8f370884 100644 --- a/lib/ssh/src/ssh_io.erl +++ b/lib/ssh/src/ssh_io.erl @@ -31,56 +31,55 @@ read_line(Prompt, Ssh) -> format("~s", [listify(Prompt)]), proplists:get_value(user_pid, Ssh) ! {self(), question}, receive - Answer -> + Answer when is_list(Answer) -> Answer end. yes_no(Prompt, Ssh) -> - io:format("~s [y/n]?", [Prompt]), + format("~s [y/n]?", [Prompt]), proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), question}, receive - Answer -> + %% I can't see that the atoms y and n are ever received, but it must + %% be investigated before removing + y -> yes; + n -> no; + + Answer when is_list(Answer) -> case trim(Answer) of "y" -> yes; "n" -> no; "Y" -> yes; "N" -> no; - y -> yes; - n -> no; _ -> - io:format("please answer y or n\n"), + format("please answer y or n\n",[]), yes_no(Prompt, Ssh) end end. -read_password(Prompt, Ssh) -> +read_password(Prompt, #ssh{opts=Opts}) -> read_password(Prompt, Opts); +read_password(Prompt, Opts) when is_list(Opts) -> format("~s", [listify(Prompt)]), - case is_list(Ssh) of - false -> - proplists:get_value(user_pid, Ssh#ssh.opts) ! {self(), user_password}; - _ -> - proplists:get_value(user_pid, Ssh) ! {self(), user_password} - end, + proplists:get_value(user_pid, Opts) ! {self(), user_password}, receive - Answer -> - case Answer of - "" -> - read_password(Prompt, Ssh); - Pass -> Pass - end + Answer when is_list(Answer) -> + case trim(Answer) of + "" -> + read_password(Prompt, Opts); + Pwd -> + Pwd + end end. -listify(A) when is_atom(A) -> - atom_to_list(A); -listify(L) when is_list(L) -> - L; -listify(B) when is_binary(B) -> - binary_to_list(B). format(Fmt, Args) -> io:format(Fmt, Args). +%%%================================================================ +listify(A) when is_atom(A) -> atom_to_list(A); +listify(L) when is_list(L) -> L; +listify(B) when is_binary(B) -> binary_to_list(B). + trim(Line) when is_list(Line) -> lists:reverse(trim1(lists:reverse(trim1(Line)))); @@ -93,6 +92,3 @@ trim1([$\r|Cs]) -> trim(Cs); trim1([$\n|Cs]) -> trim(Cs); trim1([$\t|Cs]) -> trim(Cs); trim1(Cs) -> Cs. - - - diff --git a/lib/ssh/test/ssh_basic_SUITE.erl b/lib/ssh/test/ssh_basic_SUITE.erl index 733414e23a..d52d453007 100644 --- a/lib/ssh/test/ssh_basic_SUITE.erl +++ b/lib/ssh/test/ssh_basic_SUITE.erl @@ -50,7 +50,12 @@ inet6_option/1, inet_option/1, internal_error/1, - known_hosts/1, + known_hosts/1, + login_bad_pwd_no_retry1/1, + login_bad_pwd_no_retry2/1, + login_bad_pwd_no_retry3/1, + login_bad_pwd_no_retry4/1, + login_bad_pwd_no_retry5/1, misc_ssh_options/1, openssh_zlib_basic_test/1, packet_size_zero/1, @@ -100,7 +105,8 @@ all() -> daemon_opt_fd, multi_daemon_opt_fd, packet_size_zero, - ssh_info_print + ssh_info_print, + {group, login_bad_pwd_no_retry} ]. groups() -> @@ -116,7 +122,13 @@ groups() -> {dsa_pass_key, [], [pass_phrase]}, {rsa_pass_key, [], [pass_phrase]}, {key_cb, [], [key_callback, key_callback_options]}, - {internal_error, [], [internal_error]} + {internal_error, [], [internal_error]}, + {login_bad_pwd_no_retry, [], [login_bad_pwd_no_retry1, + login_bad_pwd_no_retry2, + login_bad_pwd_no_retry3, + login_bad_pwd_no_retry4, + login_bad_pwd_no_retry5 + ]} ]. @@ -1090,6 +1102,72 @@ ssh_info_print(Config) -> %%-------------------------------------------------------------------- +%% Check that a basd pwd is not tried more times. Could cause lock-out +%% on server + +login_bad_pwd_no_retry1(Config) -> + login_bad_pwd_no_retry(Config, "keyboard-interactive,password"). + +login_bad_pwd_no_retry2(Config) -> + login_bad_pwd_no_retry(Config, "password,keyboard-interactive"). + +login_bad_pwd_no_retry3(Config) -> + login_bad_pwd_no_retry(Config, "password,publickey,keyboard-interactive"). + +login_bad_pwd_no_retry4(Config) -> + login_bad_pwd_no_retry(Config, "password,other,keyboard-interactive"). + +login_bad_pwd_no_retry5(Config) -> + login_bad_pwd_no_retry(Config, "password,other,keyboard-interactive,password,password"). + + + + + +login_bad_pwd_no_retry(Config, AuthMethods) -> + PrivDir = proplists:get_value(priv_dir, Config), + UserDir = filename:join(PrivDir, nopubkey), % to make sure we don't use public-key-auth + file:make_dir(UserDir), + SysDir = proplists:get_value(data_dir, Config), + + Parent = self(), + PwdFun = fun(_, _, _, undefined) -> {false, 1}; + (_, _, _, _) -> Parent ! retry_bad_pwd, + false + end, + + {DaemonRef, _Host, Port} = + ssh_test_lib:daemon([{system_dir, SysDir}, + {user_dir, UserDir}, + {auth_methods, AuthMethods}, + {user_passwords, [{"foo","somepwd"}]}, + {pwdfun, PwdFun} + ]), + + ConnRes = ssh:connect("localhost", Port, + [{silently_accept_hosts, true}, + {user, "foo"}, + {password, "badpwd"}, + {user_dir, UserDir}, + {user_interaction, false}]), + + receive + retry_bad_pwd -> + ssh:stop_daemon(DaemonRef), + {fail, "Retry bad password"} + after 0 -> + case ConnRes of + {error,"Unable to connect using the available authentication methods"} -> + ssh:stop_daemon(DaemonRef), + ok; + {ok,Conn} -> + ssh:close(Conn), + ssh:stop_daemon(DaemonRef), + {fail, "Connect erroneosly succeded"} + end + end. + +%%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ %%-------------------------------------------------------------------- %% Due to timing the error message may or may not be delivered to diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk index b165928877..575c1af3a9 100644 --- a/lib/ssh/vsn.mk +++ b/lib/ssh/vsn.mk @@ -1,5 +1,5 @@ #-*-makefile-*- ; force emacs to enter makefile-mode -SSH_VSN = 4.3 +SSH_VSN = 4.3.1 APP_VSN = "ssh-$(SSH_VSN)" 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/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl index b8be686b99..a0d9982aaa 100644 --- a/lib/ssl/src/dtls_connection.erl +++ b/lib/ssl/src/dtls_connection.erl @@ -65,9 +65,7 @@ hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states connection/3]). %% gen_statem callbacks --export([terminate/3, code_change/4, format_status/2]). - --define(GEN_STATEM_CB_MODE, state_functions). +-export([callback_mode/0, terminate/3, code_change/4, format_status/2]). %%==================================================================== %% Internal application API @@ -161,12 +159,15 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + gen_statem:enter_loop(?MODULE, [], init, State) catch throw:Error -> - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error,State0}) + gen_statem:enter_loop(?MODULE, [], error, {Error,State0}) end. +callback_mode() -> + state_functions. + %%-------------------------------------------------------------------- %% State functionsconnection/2 %%-------------------------------------------------------------------- @@ -376,7 +377,7 @@ terminate(Reason, StateName, State) -> %% Description: Convert process state when code is changed %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State, _Extra) -> - {?GEN_STATEM_CB_MODE, StateName, State}. + {ok, StateName, State}. format_status(Type, Data) -> ssl_connection:format_status(Type, Data). 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/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl index 9880befa94..eaf866c339 100644 --- a/lib/ssl/src/tls_connection.erl +++ b/lib/ssl/src/tls_connection.erl @@ -68,10 +68,8 @@ hello/3, certify/3, cipher/3, abbreviated/3, %% Handshake states connection/3]). %% gen_statem callbacks --export([terminate/3, code_change/4, format_status/2]). +-export([callback_mode/0, terminate/3, code_change/4, format_status/2]). --define(GEN_STATEM_CB_MODE, state_functions). - %%==================================================================== %% Internal application API %%==================================================================== @@ -169,11 +167,14 @@ init([Role, Host, Port, Socket, Options, User, CbInfo]) -> State0 = initial_state(Role, Host, Port, Socket, Options, User, CbInfo), try State = ssl_connection:ssl_config(State0#state.ssl_options, Role, State0), - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, init, State) + gen_statem:enter_loop(?MODULE, [], init, State) catch throw:Error -> - gen_statem:enter_loop(?MODULE, [], ?GEN_STATEM_CB_MODE, error, {Error, State0}) + gen_statem:enter_loop(?MODULE, [], error, {Error, State0}) end. +callback_mode() -> + state_functions. + %%-------------------------------------------------------------------- %% State functions %%-------------------------------------------------------------------- @@ -457,9 +458,9 @@ format_status(Type, Data) -> %%-------------------------------------------------------------------- code_change(_OldVsn, StateName, State0, {Direction, From, To}) -> State = convert_state(State0, Direction, From, To), - {?GEN_STATEM_CB_MODE, StateName, State}; + {ok, StateName, State}; code_change(_OldVsn, StateName, State, _) -> - {?GEN_STATEM_CB_MODE, StateName, State}. + {ok, StateName, State}. %%-------------------------------------------------------------------- %%% Internal functions 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_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index 5ebf9bb2de..8740e8c8f0 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -109,11 +109,11 @@ common_end(_, _Config) -> basic() -> [{doc,"Test that two nodes can connect via ssl distribution"}]. basic(Config) when is_list(Config) -> - NH1 = start_ssl_node(Config), + gen_dist_test(basic_test, Config). + +basic_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(Config), Node2 = NH2#node_handle.nodename, - pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), @@ -161,18 +161,16 @@ basic(Config) when is_list(Config) -> ok end end) - end, - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + end. %%-------------------------------------------------------------------- payload() -> [{doc,"Test that send a lot of data between the ssl distributed noes"}]. payload(Config) when is_list(Config) -> - NH1 = start_ssl_node(Config), + gen_dist_test(payload_test, Config). + +payload_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(Config), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -204,10 +202,8 @@ payload(Config) when is_list(Config) -> ok end end) - end, - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + end. + %%-------------------------------------------------------------------- plain_options() -> [{doc,"Test specifying additional options"}]. @@ -218,20 +214,17 @@ plain_options(Config) when is_list(Config) -> "client_verify verify_none server_verify verify_none " "server_depth 1 client_depth 1 " "server_hibernate_after 500 client_hibernate_after 500", + gen_dist_test(plain_options_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +plain_options_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- plain_verify_options() -> [{doc,"Test specifying additional options"}]. @@ -240,20 +233,18 @@ plain_verify_options(Config) when is_list(Config) -> "client_secure_renegotiate true " "server_reuse_sessions true client_reuse_sessions true " "server_hibernate_after 500 client_hibernate_after 500", + gen_dist_test(plain_verify_options_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +plain_verify_options_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, - + pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), - + [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). + - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- nodelay_option() -> [{doc,"Test specifying dist_nodelay option"}]. @@ -265,6 +256,7 @@ nodelay_option(Config) -> after application:unset_env(kernel, dist_nodelay) end. +%%-------------------------------------------------------------------- listen_port_options() -> [{doc, "Test specifying listening ports"}]. @@ -285,32 +277,39 @@ listen_port_options(Config) when is_list(Config) -> #node_handle{} -> %% If the node was able to start, it didn't take the port %% option into account. + stop_ssl_node(NH1), exit(unexpected_success) catch exit:{accept_failed, timeout} -> %% The node failed to start, as expected. ok end, - + %% Try again, now specifying a high max port. PortOpt2 = "-kernel inet_dist_listen_min " ++ integer_to_list(Port1) ++ - " inet_dist_listen_max 65535", + " inet_dist_listen_max 65535", NH2 = start_ssl_node([{additional_dist_opts, PortOpt2} | Config]), - Node2 = NH2#node_handle.nodename, - Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), - {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), - {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), - - %% The new port should be higher: - if Port2 > Port1 -> - ok; - true -> - error({port, Port2, not_higher_than, Port1}) + + try + Node2 = NH2#node_handle.nodename, + Name2 = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node2)), + {ok, NodesPorts2} = apply_on_ssl_node(NH2, fun net_adm:names/0), + {Name2, Port2} = lists:keyfind(Name2, 1, NodesPorts2), + + %% The new port should be higher: + if Port2 > Port1 -> + ok; + true -> + error({port, Port2, not_higher_than, Port1}) + end + catch + _:Reason -> + stop_ssl_node(NH2), + ct:fail(Reason) end, - - stop_ssl_node(NH1), stop_ssl_node(NH2), success(Config). + %%-------------------------------------------------------------------- listen_options() -> [{doc, "Test inet_dist_listen_options"}]. @@ -329,28 +328,25 @@ do_listen_options(Prio, Config) -> end, Options = "-kernel inet_dist_listen_options " ++ PriorityString, - - NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), - Node2 = NH2#node_handle.nodename, - + gen_dist_test(listen_options_test, [{prio, Prio}, {additional_dist_opts, Options} | Config]). + +listen_options_test(NH1, NH2, Config) -> + Prio = proplists:get_value(prio, Config), + Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), PrioritiesNode1 = apply_on_ssl_node(NH1, fun get_socket_priorities/0), PrioritiesNode2 = apply_on_ssl_node(NH2, fun get_socket_priorities/0), - + Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], - ?t:format("Elevated1: ~p~n", [Elevated1]), + ct:pal("Elevated1: ~p~n", [Elevated1]), Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], - ?t:format("Elevated2: ~p~n", [Elevated2]), + ct:pal("Elevated2: ~p~n", [Elevated2]), [_|_] = Elevated1, - [_|_] = Elevated2, + [_|_] = Elevated2. - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- connect_options() -> [{doc, "Test inet_dist_connect_options"}]. @@ -369,9 +365,11 @@ do_connect_options(Prio, Config) -> end, Options = "-kernel inet_dist_connect_options " ++ PriorityString, + gen_dist_test(connect_options_test, + [{prio, Prio}, {additional_dist_opts, Options} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, Options} | Config]), +connect_options_test(NH1, NH2, Config) -> + Prio = proplists:get_value(prio, Config), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -382,17 +380,14 @@ do_connect_options(Prio, Config) -> apply_on_ssl_node(NH2, fun get_socket_priorities/0), Elevated1 = [P || P <- PrioritiesNode1, P =:= Prio], - ?t:format("Elevated1: ~p~n", [Elevated1]), + ct:pal("Elevated1: ~p~n", [Elevated1]), Elevated2 = [P || P <- PrioritiesNode2, P =:= Prio], - ?t:format("Elevated2: ~p~n", [Elevated2]), + ct:pal("Elevated2: ~p~n", [Elevated2]), %% Node 1 will have a socket with elevated priority. [_|_] = Elevated1, %% Node 2 will not, since it only applies to outbound connections. - [] = Elevated2, + [] = Elevated2. - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). %%-------------------------------------------------------------------- use_interface() -> [{doc, "Test inet_dist_use_interface"}]. @@ -403,22 +398,28 @@ use_interface(Config) when is_list(Config) -> %% Start a node, and get the port number it's listening on. NH1 = start_ssl_node([{additional_dist_opts, Options} | Config]), - Node1 = NH1#node_handle.nodename, - Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), - {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), - {Name, Port} = lists:keyfind(Name, 1, NodesPorts), - - %% Now find the socket listening on that port, and check its sockname. - Sockets = apply_on_ssl_node( - NH1, - fun() -> - [inet:sockname(P) || - P <- inet_ports(), - {ok, Port} =:= (catch inet:port(P))] - end), - %% And check that it's actually listening on localhost. - [{ok,{{127,0,0,1},Port}}] = Sockets, - + + try + Node1 = NH1#node_handle.nodename, + Name = lists:takewhile(fun(C) -> C =/= $@ end, atom_to_list(Node1)), + {ok, NodesPorts} = apply_on_ssl_node(NH1, fun net_adm:names/0), + {Name, Port} = lists:keyfind(Name, 1, NodesPorts), + + %% Now find the socket listening on that port, and check its sockname. + Sockets = apply_on_ssl_node( + NH1, + fun() -> + [inet:sockname(P) || + P <- inet_ports(), + {ok, Port} =:= (catch inet:port(P))] + end), + %% And check that it's actually listening on localhost. + [{ok,{{127,0,0,1},Port}}] = Sockets + catch + _:Reason -> + stop_ssl_node(NH1), + ct:fail(Reason) + end, stop_ssl_node(NH1), success(Config). %%-------------------------------------------------------------------- @@ -430,11 +431,11 @@ verify_fun_fail(Config) when is_list(Config) -> "\"{ssl_dist_SUITE,verify_fail_always,{}}\" " "client_verify verify_peer client_verify_fun " "\"{ssl_dist_SUITE,verify_fail_always,{}}\" ", + gen_dist_test(verify_fun_fail_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +verify_fun_fail_test(NH1, NH2, _) -> Node2 = NH2#node_handle.nodename, - + pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), @@ -446,25 +447,9 @@ verify_fun_fail(Config) when is_list(Config) -> %% On the server node, it wouldn't run, because the server didn't %% request a certificate from the client. undefined = - apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end), + apply_on_ssl_node(NH2, fun () -> ets:info(verify_fun_ran) end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). -verify_fail_always(_Certificate, _Event, _State) -> - %% Create an ETS table, to record the fact that the verify function ran. - %% Spawn a new process, to avoid the ETS table disappearing. - Parent = self(), - spawn( - fun() -> - ets:new(verify_fun_ran, [public, named_table]), - ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), - Parent ! go_ahead, - timer:sleep(infinity) - end), - receive go_ahead -> ok end, - {fail, bad_certificate}. %%-------------------------------------------------------------------- verify_fun_pass() -> @@ -476,10 +461,10 @@ verify_fun_pass(Config) when is_list(Config) -> "server_fail_if_no_peer_cert true " "client_verify verify_peer client_verify_fun " "\"{ssl_dist_SUITE,verify_pass_always,{}}\" ", + gen_dist_test(verify_fun_pass_test, [{additional_dist_opts, DistOpts} | Config]). - NH1 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), +verify_fun_pass_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node([{additional_dist_opts, DistOpts} | Config]), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), @@ -494,25 +479,8 @@ verify_fun_pass(Config) when is_list(Config) -> %% requested and verified the client's certificate because we %% passed fail_if_no_peer_cert. [{verify_pass_always_ran, true}] = - apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end), + apply_on_ssl_node(NH2, fun () -> ets:tab2list(verify_fun_ran) end). - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). - -verify_pass_always(_Certificate, _Event, State) -> - %% Create an ETS table, to record the fact that the verify function ran. - %% Spawn a new process, to avoid the ETS table disappearing. - Parent = self(), - spawn( - fun() -> - ets:new(verify_fun_ran, [public, named_table]), - ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), - Parent ! go_ahead, - timer:sleep(infinity) - end), - receive go_ahead -> ok end, - {valid, State}. %%-------------------------------------------------------------------- crl_check_pass() -> [{doc,"Test crl_check with non-revoked certificate"}]. @@ -520,10 +488,10 @@ crl_check_pass(Config) when is_list(Config) -> DistOpts = "-ssl_dist_opt client_crl_check true", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_pass_test, NewConfig). - NH1 = start_ssl_node(NewConfig), +crl_check_pass_test(NH1, NH2, Config) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, PrivDir = ?config(priv_dir, Config), @@ -533,11 +501,7 @@ crl_check_pass(Config) when is_list(Config) -> pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_check_fail() -> @@ -549,10 +513,9 @@ crl_check_fail(Config) when is_list(Config) -> %% The server uses a revoked certificate. {server_cert_dir, "revoked"}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_fail_test, NewConfig). - NH1 = start_ssl_node(NewConfig), - %%Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), +crl_check_fail_test(NH1, NH2, Config) -> Node2 = NH2#node_handle.nodename, PrivDir = ?config(priv_dir, Config), @@ -562,11 +525,7 @@ crl_check_fail(Config) when is_list(Config) -> pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_check_best_effort() -> @@ -576,22 +535,18 @@ crl_check_best_effort(Config) when is_list(Config) -> "server_verify verify_peer server_crl_check best_effort", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_check_best_effort_test, NewConfig). +crl_check_best_effort_test(NH1, NH2, _Config) -> %% We don't have the correct CRL at hand, but since crl_check is %% best_effort, we accept it anyway. - NH1 = start_ssl_node(NewConfig), Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_cache_check_pass() -> @@ -605,20 +560,16 @@ crl_cache_check_pass(Config) when is_list(Config) -> "\"{ssl_dist_SUITE,{\\\"" ++ NodeDir ++ "\\\",[]}}\"", NewConfig = [{many_verify_opts, true}, {additional_dist_opts, DistOpts}] ++ Config, + gen_dist_test(crl_cache_check_pass_test, NewConfig). - NH1 = start_ssl_node(NewConfig), +crl_cache_check_pass_test(NH1, NH2, _) -> Node1 = NH1#node_handle.nodename, - NH2 = start_ssl_node(NewConfig), Node2 = NH2#node_handle.nodename, pong = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [Node2] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). + [Node1] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- crl_cache_check_fail() -> @@ -636,44 +587,31 @@ crl_cache_check_fail(Config) when is_list(Config) -> {server_cert_dir, "revoked"}, {additional_dist_opts, DistOpts}] ++ Config, - NH1 = start_ssl_node(NewConfig), - NH2 = start_ssl_node(NewConfig), - Node2 = NH2#node_handle.nodename, + gen_dist_test(crl_cache_check_fail_test, NewConfig). +crl_cache_check_fail_test(NH1, NH2, _) -> + Node2 = NH2#node_handle.nodename, pang = apply_on_ssl_node(NH1, fun () -> net_adm:ping(Node2) end), [] = apply_on_ssl_node(NH1, fun () -> nodes() end), - [] = apply_on_ssl_node(NH2, fun () -> nodes() end), - - stop_ssl_node(NH1), - stop_ssl_node(NH2), - success(Config). - -%% ssl_crl_cache_api callbacks -lookup(_DistributionPoint, _DbHandle) -> - not_available. - -select({rdnSequence, NameParts}, {NodeDir, _}) -> - %% Extract the CN from the issuer name... - [CN] = [CN || - [#'AttributeTypeAndValue'{ - type = ?'id-at-commonName', - value = <<_, _, CN/binary>>}] <- NameParts], - %% ...and use that as the directory name to find the CRL. - error_logger:info_report([{found_cn, CN}]), - CRLFile = filename:join([NodeDir, CN, "crl.pem"]), - {ok, PemBin} = file:read_file(CRLFile), - PemEntries = public_key:pem_decode(PemBin), - CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} - <- PemEntries], - CRLs. - -fresh_crl(_DistributionPoint, CRL) -> - CRL. - + [] = apply_on_ssl_node(NH2, fun () -> nodes() end). %%-------------------------------------------------------------------- %%% Internal functions ----------------------------------------------- %%-------------------------------------------------------------------- +gen_dist_test(Test, Config) -> + NH1 = start_ssl_node(Config), + NH2 = start_ssl_node(Config), + try + ?MODULE:Test(NH1, NH2, Config) + catch + _:Reason -> + stop_ssl_node(NH1), + stop_ssl_node(NH2), + ct:fail(Reason) + end, + stop_ssl_node(NH1), + stop_ssl_node(NH2), + success(Config). %% ssl_node side api %% @@ -742,13 +680,15 @@ stop_ssl_node(#node_handle{connection_handler = Handler, receive {'DOWN', Mon, process, Handler, Reason} -> case Reason of - normal -> ok; - _ -> exit(Reason) + normal -> + ok; + _ -> + ct:pal("Down ~p ~n", [Reason]) end end; Error -> erlang:demonitor(Mon, [flush]), - exit(Error) + ct:pal("Warning ~p ~n", [Error]) end. start_ssl_node(Config) -> @@ -1226,3 +1166,53 @@ vsn(App) -> after application:stop(ssl) end. + +verify_fail_always(_Certificate, _Event, _State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_fail_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {fail, bad_certificate}. + +verify_pass_always(_Certificate, _Event, State) -> + %% Create an ETS table, to record the fact that the verify function ran. + %% Spawn a new process, to avoid the ETS table disappearing. + Parent = self(), + spawn( + fun() -> + ets:new(verify_fun_ran, [public, named_table]), + ets:insert(verify_fun_ran, {verify_pass_always_ran, true}), + Parent ! go_ahead, + timer:sleep(infinity) + end), + receive go_ahead -> ok end, + {valid, State}. + +%% ssl_crl_cache_api callbacks +lookup(_DistributionPoint, _DbHandle) -> + not_available. + +select({rdnSequence, NameParts}, {NodeDir, _}) -> + %% Extract the CN from the issuer name... + [CN] = [CN || + [#'AttributeTypeAndValue'{ + type = ?'id-at-commonName', + value = <<_, _, CN/binary>>}] <- NameParts], + %% ...and use that as the directory name to find the CRL. + error_logger:info_report([{found_cn, CN}]), + CRLFile = filename:join([NodeDir, CN, "crl.pem"]), + {ok, PemBin} = file:read_file(CRLFile), + PemEntries = public_key:pem_decode(PemBin), + CRLs = [ CRL || {'CertificateList', CRL, not_encrypted} + <- PemEntries], + CRLs. + +fresh_crl(_DistributionPoint, CRL) -> + CRL. 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/ets.xml b/lib/stdlib/doc/src/ets.xml index b8e262208d..3653c6a632 100644 --- a/lib/stdlib/doc/src/ets.xml +++ b/lib/stdlib/doc/src/ets.xml @@ -1458,7 +1458,7 @@ is_integer(X), is_integer(Y), X + Y < 4711]]></code> specification returned <c>true</c>.</fsummary> <desc> <p>Matches the objects in table <c><anno>Tab</anno></c> using a - <seealso marker="#match_spec">match specificationc</seealso>. If the + <seealso marker="#match_spec">match specification</seealso>. If the match specification returns <c>true</c> for an object, that object considered a match and is counted. For any other result from the match specification the object is not considered a match and is diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml index c57a31fa21..3322571b2c 100644 --- a/lib/stdlib/doc/src/gen_statem.xml +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -97,6 +97,9 @@ gen_statem module Callback module gen_statem:start gen_statem:start_link -----> Module:init/1 +Server start or code change + -----> Module:callback_mode/0 + gen_statem:stop -----> Module:terminate/3 gen_statem:call @@ -116,9 +119,11 @@ erlang:'!' -----> Module:StateName/3 </p> <p> If a callback function fails or returns a bad value, - the <c>gen_statem</c> terminates. However, an exception of class + the <c>gen_statem</c> terminates, unless otherwise stated. + However, an exception of class <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso> - is not regarded as an error but as a valid return. + is not regarded as an error but as a valid return + from all callback functions. </p> <marker id="state_function"/> <p> @@ -127,7 +132,8 @@ erlang:'!' -----> Module:StateName/3 in a <c>gen_statem</c> is the callback function that is called for all events in this state. It is selected depending on which <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - that the implementation specifies when the server starts. + that the callback module defines with the callback function + <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>. </p> <p> When the @@ -138,9 +144,9 @@ erlang:'!' -----> Module:StateName/3 This gathers all code for a specific state in one function as the <c>gen_statem</c> engine branches depending on state name. - Notice that in this mode the mandatory callback function + Notice the fact that there is a mandatory callback function <seealso marker="#Module:terminate/3"><c>Module:terminate/3</c></seealso> - makes the state name <c>terminate</c> unusable. + makes the state name <c>terminate</c> unusable in this mode. </p> <p> When the @@ -249,11 +255,10 @@ erlang:'!' -----> Module:StateName/3 -behaviour(gen_statem). -export([start/0,push/0,get_count/0,stop/0]). --export([terminate/3,code_change/4,init/1]). +-export([terminate/3,code_change/4,init/1,callback_mode/0]). -export([on/3,off/3]). name() -> pushbutton_statem. % The registered server name -callback_mode() -> state_functions. %% API. This example uses a registered name name() %% and does not link to the caller. @@ -270,15 +275,14 @@ stop() -> terminate(_Reason, _State, _Data) -> void. code_change(_Vsn, State, Data, _Extra) -> - {callback_mode(),State,Data}. + {ok,State,Data}. init([]) -> - %% Set the callback mode and initial state + data. - %% Data is used only as a counter. + %% Set the initial state + data. Data is used only as a counter. State = off, Data = 0, - {callback_mode(),State,Data}. - + {ok,State,Data}. +callback_mode() -> state_functions. -%%% State functions +%%% State function(s) off({call,From}, push, Data) -> %% Go to 'on', increment count and reply @@ -326,18 +330,13 @@ ok To compare styles, here follows the same example using <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> <c>state_functions</c>, or rather the code to replace - from function <c>init/1</c> of the <c>pushbutton.erl</c> + after function <c>init/1</c> of the <c>pushbutton.erl</c> example file above: </p> <code type="erl"> -init([]) -> - %% Set the callback mode and initial state + data. - %% Data is used only as a counter. - State = off, Data = 0, - {handle_event_function,State,Data}. - +callback_mode() -> handle_event_function. -%%% Event handling +%%% State function(s) handle_event({call,From}, push, off, Data) -> %% Go to 'on', increment count and reply @@ -426,8 +425,8 @@ handle_event(_, _, State, Data) -> <desc> <p> Debug option that can be used when starting - a <c>gen_statem</c> server through, for example, - <seealso marker="#enter_loop/5"><c>enter_loop/5</c></seealso>. + a <c>gen_statem</c> server through, + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>. </p> <p> For every entry in <c><anno>Dbgs</anno></c>, @@ -525,12 +524,9 @@ handle_event(_, _, State, Data) -> <desc> <p> The <em>callback mode</em> is selected when starting the - <c>gen_statem</c> using the return value from - <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> - or when calling - <seealso marker="#enter_loop/5"><c>enter_loop/5,6,7</c></seealso>, - and with the return value from - <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso>. + <c>gen_statem</c> and after code change + using the return value from + <seealso marker="#Module:callback_mode/0"><c>Module:callback_mode/0</c></seealso>. </p> <taglist> <tag><c>state_functions</c></tag> @@ -691,7 +687,7 @@ handle_event(_, _, State, Data) -> <seealso marker="#state_function">state function</seealso>, from <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> or by giving them to - <seealso marker="#enter_loop/6"><c>enter_loop/6,7</c></seealso>. + <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso>. </p> <p> Actions are executed in the containing list order. @@ -923,7 +919,8 @@ handle_event(_, _, State, Data) -> </p> <note> <p> - To avoid getting a late reply in the caller's + For <c><anno>Timeout</anno> =/= infinity</c>, + to avoid getting a late reply in the caller's inbox, this function spawns a proxy process that does the call. A late reply gets delivered to the dead proxy process, hence gets discarded. This is @@ -958,35 +955,36 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="5"/> + <name name="enter_loop" arity="4"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> The same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> - except that no + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> + with <c>Actions = []</c> except that no <seealso marker="#type-server_name"><c>server_name()</c></seealso> - must have been registered. + must have been registered. This creates an anonymous server. </p> </desc> </func> <func> - <name name="enter_loop" arity="6"/> + <name name="enter_loop" arity="5"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> If <c><anno>Server_or_Actions</anno></c> is a <c>list()</c>, the same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> except that no <seealso marker="#type-server_name"><c>server_name()</c></seealso> must have been registered and <c>Actions = <anno>Server_or_Actions</anno></c>. + This creates an anonymous server. </p> <p> Otherwise the same as - <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + <seealso marker="#enter_loop/6"><c>enter_loop/6</c></seealso> with <c>Server = <anno>Server_or_Actions</anno></c> and <c>Actions = []</c>. @@ -995,7 +993,7 @@ handle_event(_, _, State, Data) -> </func> <func> - <name name="enter_loop" arity="7"/> + <name name="enter_loop" arity="6"/> <fsummary>Enter the <c>gen_statem</c> receive loop.</fsummary> <desc> <p> @@ -1015,21 +1013,31 @@ handle_event(_, _, State, Data) -> the <c>gen_statem</c> behavior provides. </p> <p> - <c><anno>Module</anno></c>, <c><anno>Opts</anno></c>, and - <c><anno>Server</anno></c> have the same meanings - as when calling + <c><anno>Module</anno></c>, <c><anno>Opts</anno></c> + have the same meaning as when calling <seealso marker="#start_link/3"><c>start[_link]/3,4</c></seealso>. + </p> + <p> + If <c><anno>Server</anno></c> is <c>self()</c> an anonymous + server is created just as when using + <seealso marker="#start_link/3"><c>start[_link]/3</c></seealso>. + If <c><anno>Server</anno></c> is a + <seealso marker="#type-server_name"><c>server_name()</c></seealso> + a named server is created just as when using + <seealso marker="#start_link/4"><c>start[_link]/4</c></seealso>. However, the <seealso marker="#type-server_name"><c>server_name()</c></seealso> name must have been registered accordingly - <em>before</em> this function is called.</p> + <em>before</em> this function is called. + </p> <p> - <c><anno>CallbackMode</anno></c>, <c><anno>State</anno></c>, - <c><anno>Data</anno></c>, and <c><anno>Actions</anno></c> + <c><anno>State</anno></c>, <c><anno>Data</anno></c>, + and <c><anno>Actions</anno></c> have the same meanings as in the return value of <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>. - Also, the callback module <c><anno>Module</anno></c> - does not need to export an <c>init/1</c> function. + Also, the callback module does not need to export a + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + function. </p> <p> The function fails if the calling process was not started by a @@ -1253,6 +1261,48 @@ handle_event(_, _, State, Data) -> <funcs> <func> + <name>Module:callback_mode() -> CallbackMode</name> + <fsummary>Update the internal state during upgrade/downgrade.</fsummary> + <type> + <v> + CallbackMode = + <seealso marker="#type-callback_mode">callback_mode()</seealso> + </v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> + when it needs to find out the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + of the callback module. The value is cached by <c>gen_statem</c> + for efficiency reasons, so this function is only called + once after server start and after code change, + but before the first + <seealso marker="#state_function">state function</seealso> + is called. More occasions may be added in future versions + of <c>gen_statem</c>. + </p> + <p> + Server start happens either when + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + returns or when + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso> + is called. Code change happens when + <seealso marker="#Module:code_change/4"><c>Module:code_change/4</c></seealso> + returns. + </p> + <note> + <p> + If this function's body does not consist of solely one of two + possible + <seealso marker="#type-callback_mode">atoms</seealso> + the callback module is doing something strange. + </p> + </note> + </desc> + </func> + + <func> <name>Module:code_change(OldVsn, OldState, OldData, Extra) -> Result </name> @@ -1262,11 +1312,7 @@ 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> - NewCallbackMode = - <seealso marker="#type-callback_mode">callback_mode()</seealso> - </v> + <v>Result = {ok,NewState,NewData} | Reason</v> <v> OldState = NewState = <seealso marker="#type-state">state()</seealso> @@ -1295,21 +1341,6 @@ handle_event(_, _, State, Data) -> <c>Module</c>. If no such attribute is defined, the version is the checksum of the Beam file. </p> - <note> - <p> - If you would dare to change - <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - during release upgrade/downgrade, the upgrade is no problem, - as the new code surely knows what <em>callback mode</em> - it needs. However, for a downgrade this function must - know from argument <c>Extra</c> that comes from the - <seealso marker="sasl:appup"><c>sasl:appup</c></seealso> - file what <em>callback mode</em> the old code did use. - It can also be possible to figure this out - from argument <c>{down,Vsn}</c>, as <c>Vsn</c> - in effect defines the old callback module version. - </p> - </note> <p> <c>OldState</c> and <c>OldData</c> is the internal state of the <c>gen_statem</c>. @@ -1321,31 +1352,32 @@ handle_event(_, _, State, Data) -> <p> If successful, the function must return the updated internal state in an - <c>{NewCallbackMode,NewState,NewData}</c> tuple. + <c>{ok,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> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Result</c> or <c>Reason</c>. + 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 an <c>{ok,_,_}</c> tuple + since that will be regarded as a + <c>{ok,NewState,NewData}</c> tuple, + and that a tuple matching <c>{ok,_}</c> + is an also 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> </desc> </func> <func> <name>Module:init(Args) -> Result</name> - <fsummary>Initialize process and internal state.</fsummary> + <fsummary> + Optional function for initializing process and internal state. + </fsummary> <type> <v>Args = term()</v> - <v>Result = {CallbackMode,State,Data}</v> - <v> | {CallbackMode,State,Data,Actions}</v> + <v>Result = {ok,State,Data}</v> + <v> | {ok,State,Data,Actions}</v> <v> | {stop,Reason} | ignore</v> - <v> - CallbackMode = - <seealso marker="#type-callback_mode">callback_mode()</seealso> - </v> <v>State = <seealso marker="#type-state">state()</seealso></v> <v> Data = <seealso marker="#type-data">data()</seealso> @@ -1364,7 +1396,7 @@ handle_event(_, _, State, Data) -> <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> or <seealso marker="#start/3"><c>start/3,4</c></seealso>, - this function is called by the new process to initialize + this optional function is called by the new process to initialize the implementation state and server data. </p> <p> @@ -1373,11 +1405,8 @@ handle_event(_, _, State, Data) -> </p> <p> If the initialization is successful, the function is to - return <c>{CallbackMode,State,Data}</c> or - <c>{CallbackMode,State,Data,Actions}</c>. - <c>CallbackMode</c> selects the - <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> - of the <c>gen_statem</c>. + return <c>{ok,State,Data}</c> or + <c>{ok,State,Data,Actions}</c>. <c>State</c> is the initial <seealso marker="#type-state"><c>state()</c></seealso> and <c>Data</c> the initial server @@ -1395,11 +1424,16 @@ handle_event(_, _, State, Data) -> or <c>ignore</c>; see <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Result</c>. - </p> + <note> + <p> + This callback is optional, so a callback module does not need + to export it, but most do. If this function is not exported, + the <c>gen_statem</c> should be started through + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + and + <seealso marker="#enter_loop/4"><c>enter_loop/4-6</c></seealso>. + </p> + </note> </desc> </func> @@ -1430,10 +1464,14 @@ handle_event(_, _, State, Data) -> This callback is optional, so a callback module does not need to export it. The <c>gen_statem</c> module provides a default implementation of this function that returns - <c>{State,Data}</c>. If this callback fails, the default - function returns <c>{State,Info}</c>, - where <c>Info</c> informs of the crash but no details, - to hide possibly sensitive data. + <c>{State,Data}</c>. + </p> + <p> + If this callback is exported but fails, + to hide possibly sensitive data, + the default function will instead return <c>{State,Info}</c>, + where <c>Info</c> says nothing but the fact that + <c>format_status/2</c> has crashed. </p> </note> <p>This function is called by a <c>gen_statem</c> process when @@ -1494,11 +1532,6 @@ handle_event(_, _, State, Data) -> printed in log files. Another use is to hide sensitive data from being written to the error log. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Status</c>. - </p> </desc> </func> @@ -1573,9 +1606,12 @@ handle_event(_, _, State, Data) -> see <seealso marker="#type-action"><c>action()</c></seealso>. </p> <p> - These functions can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso>, - to return the result. + Note the fact that you can use + <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso> + to return the result, which can be useful. + For example to bail out with <c>throw(keep_state_and_data)</c> + from deep within complex code that is in no position to + return <c>{next_state,State,Data}</c>. </p> </desc> </func> @@ -1648,11 +1684,6 @@ handle_event(_, _, State, Data) -> and an error report is issued using <seealso marker="kernel:error_logger#format/2"><c>error_logger:format/2</c></seealso>. </p> - <p> - This function can use - <seealso marker="erts:erlang#throw/1"><c>erlang:throw/1</c></seealso> - to return <c>Ignored</c>, which is ignored anyway. - </p> </desc> </func> </funcs> 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/ets.erl b/lib/stdlib/src/ets.erl index 3f74e01692..20de06fd0b 100644 --- a/lib/stdlib/src/ets.erl +++ b/lib/stdlib/src/ets.erl @@ -232,20 +232,20 @@ match(_) -> match_object(_, _) -> erlang:nif_error(undef). --spec match_object(Tab, Pattern, Limit) -> {[Match], Continuation} | +-spec match_object(Tab, Pattern, Limit) -> {[Object], Continuation} | '$end_of_table' when Tab :: tab(), Pattern :: match_pattern(), Limit :: pos_integer(), - Match :: [term()], + Object :: tuple(), Continuation :: continuation(). match_object(_, _, _) -> erlang:nif_error(undef). --spec match_object(Continuation) -> {[Match], Continuation} | +-spec match_object(Continuation) -> {[Object], Continuation} | '$end_of_table' when - Match :: [term()], + Object :: tuple(), Continuation :: continuation(). match_object(_) -> diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl index 23bddafeed..3b3477b282 100644 --- a/lib/stdlib/src/gen_statem.erl +++ b/lib/stdlib/src/gen_statem.erl @@ -24,7 +24,7 @@ [start/3,start/4,start_link/3,start_link/4, stop/1,stop/3, cast/2,call/2,call/3, - enter_loop/5,enter_loop/6,enter_loop/7, + enter_loop/4,enter_loop/5,enter_loop/6, reply/1,reply/2]). %% gen callbacks @@ -63,8 +63,8 @@ {To :: pid(), Tag :: term()}. % Reply-to specifier for call -type state() :: - state_name() | % For state callback function StateName/5 - term(). % For state callback function handle_event/5 + state_name() | % For StateName/3 callback functios + term(). % For handle_event/4 callback function -type state_name() :: atom(). @@ -174,28 +174,33 @@ %% an {ok, ...} tuple. Thereafter the state callbacks are called %% for all events to this server. -callback init(Args :: term()) -> - {callback_mode(), state(), data()} | - {callback_mode(), state(), data(), [action()] | action()} | + {ok, state(), data()} | + {ok, state(), data(), [action()] | action()} | 'ignore' | {'stop', Reason :: term()}. -%% Example state callback for callback_mode() =:= state_functions -%% state name 'state_name'. +%% This callback shall return the callback mode of the callback module. %% -%% In this mode all states has to be type state_name() i.e atom(). +%% It is called once after init/0 and code_change/4 but before +%% the first state callback StateName/3 or handle_event/4. +-callback callback_mode() -> callback_mode(). + +%% Example state callback for StateName = 'state_name' +%% when callback_mode() =:= state_functions. +%% +%% In this mode all states has to be of type state_name() i.e atom(). %% -%% Note that state callbacks and only state callbacks have arity 5 -%% and that is intended. +%% Note that the only callbacks that have arity 3 are these +%% StateName/3 callbacks and terminate/3, so the state name +%% 'terminate' is unusable in this mode. -callback state_name( event_type(), EventContent :: term(), Data :: data()) -> state_function_result(). %% -%% State callback for callback_mode() =:= handle_event_function. -%% -%% Note that state callbacks and only state callbacks have arity 5 -%% and that is intended. +%% State callback for all states +%% when callback_mode() =:= handle_event_function. -callback handle_event( event_type(), EventContent :: term(), @@ -219,9 +224,8 @@ OldState :: state(), OldData :: data(), Extra :: term()) -> - {NewCallbackMode :: callback_mode(), - NewState :: state(), - NewData :: data()}. + {ok, NewState :: state(), NewData :: data()} | + (Reason :: term()). %% Format the callback module state in some sensible that is %% often condensed way. For StatusOption =:= 'normal' the perferred @@ -239,10 +243,13 @@ [init/1, % One may use enter_loop/5,6,7 instead format_status/2, % Has got a default implementation %% - state_name/3, % Example for callback_mode =:= state_functions: - %% there has to be a StateName/5 callback function for every StateName. + state_name/3, % Example for callback_mode() =:= state_functions: + %% there has to be a StateName/3 callback function + %% for every StateName in your state machine but the state name + %% 'state_name' does of course not have to be used. %% - handle_event/4]). % For callback_mode =:= handle_event_function + handle_event/4 % For callback_mode() =:= handle_event_function + ]). %% Type validation functions callback_mode(CallbackMode) -> @@ -450,43 +457,35 @@ reply({To,Tag}, Reply) when is_pid(To) -> %% the same arguments as you would have returned from init/1 -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data()) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data) -> - enter_loop(Module, Opts, CallbackMode, State, Data, self()). +enter_loop(Module, Opts, State, Data) -> + enter_loop(Module, Opts, State, Data, self()). %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server_or_Actions :: server_name() | pid() | [action()]) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data, Server_or_Actions) -> +enter_loop(Module, Opts, State, Data, Server_or_Actions) -> if is_list(Server_or_Actions) -> - enter_loop( - Module, Opts, CallbackMode, State, Data, - self(), Server_or_Actions); + enter_loop(Module, Opts, State, Data, self(), Server_or_Actions); true -> - enter_loop( - Module, Opts, CallbackMode, State, Data, - Server_or_Actions, []) + enter_loop(Module, Opts, State, Data, Server_or_Actions, []) end. %% -spec enter_loop( Module :: module(), Opts :: [debug_opt()], - CallbackMode :: callback_mode(), State :: state(), Data :: data(), Server :: server_name() | pid(), Actions :: [action()] | action()) -> no_return(). -enter_loop(Module, Opts, CallbackMode, State, Data, Server, Actions) -> +enter_loop(Module, Opts, State, Data, Server, Actions) -> is_atom(Module) orelse error({atom,Module}), - callback_mode(CallbackMode) orelse error({callback_mode,CallbackMode}), Parent = gen:get_parent(), - enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent). + enter(Module, Opts, State, Data, Server, Actions, Parent). %%--------------------------------------------------------------------------- %% API helpers @@ -514,7 +513,7 @@ send(Proc, Msg) -> end. %% Here the init_it/6 and enter_loop/5,6,7 functions converge -enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) -> +enter(Module, Opts, State, Data, Server, Actions, Parent) -> %% The values should already have been type checked Name = gen:get_proc_name(Server), Debug = gen:debug_options(Name, Opts), @@ -530,7 +529,7 @@ enter(Module, Opts, CallbackMode, State, Data, Server, Actions, Parent) -> [Actions,{postpone,false}] end, S = #{ - callback_mode => CallbackMode, + callback_mode => undefined, module => Module, name => Name, %% All fields below will be replaced according to the arguments to @@ -558,9 +557,15 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> Result -> init_result(Starter, Parent, ServerRef, Module, Result, Opts); Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + Name = gen:get_proc_name(ServerRef), gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), - erlang:raise(Class, Reason, erlang:get_stacktrace()) + error_info( + Class, Reason, Stacktrace, + #{name => Name, callback_mode => undefined}, + [], [], undefined), + erlang:raise(Class, Reason, Stacktrace) end. %%--------------------------------------------------------------------------- @@ -568,30 +573,12 @@ init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> case Result of - {CallbackMode,State,Data} -> - case callback_mode(CallbackMode) of - true -> - proc_lib:init_ack(Starter, {ok,self()}), - enter( - Module, Opts, CallbackMode, State, Data, - ServerRef, [], Parent); - false -> - Error = {callback_mode,CallbackMode}, - proc_lib:init_ack(Starter, {error,Error}), - exit(Error) - end; - {CallbackMode,State,Data,Actions} -> - case callback_mode(CallbackMode) of - true -> - proc_lib:init_ack(Starter, {ok,self()}), - enter( - Module, Opts, CallbackMode, State, Data, - ServerRef, Actions, Parent); - false -> - Error = {callback_mode,CallbackMode}, - proc_lib:init_ack(Starter, {error,Error}), - exit(Error) - end; + {ok,State,Data} -> + proc_lib:init_ack(Starter, {ok,self()}), + enter(Module, Opts, State, Data, ServerRef, [], Parent); + {ok,State,Data,Actions} -> + proc_lib:init_ack(Starter, {ok,self()}), + enter(Module, Opts, State, Data, ServerRef, Actions, Parent); {stop,Reason} -> gen:unregister_name(ServerRef), proc_lib:init_ack(Starter, {error,Reason}), @@ -601,8 +588,14 @@ init_result(Starter, Parent, ServerRef, Module, Result, Opts) -> proc_lib:init_ack(Starter, ignore), exit(normal); _ -> - Error = {bad_return_value,Result}, + Name = gen:get_proc_name(ServerRef), + gen:unregister_name(ServerRef), + Error = {bad_return_from_init,Result}, proc_lib:init_ack(Starter, {error,Error}), + error_info( + error, Error, ?STACKTRACE(), + #{name => Name, callback_mode => undefined}, + [], [], undefined), exit(Error) end. @@ -630,11 +623,9 @@ system_code_change( Result -> Result end of - {NewCallbackMode,NewState,NewData} -> - callback_mode(NewCallbackMode) orelse - error({callback_mode,NewCallbackMode}), + {ok,NewState,NewData} -> {ok, - S#{callback_mode := NewCallbackMode, + S#{callback_mode := undefined, state := NewState, data := NewData}}; {ok,_} = Error -> @@ -675,14 +666,14 @@ format_status( %% them, not as the real erlang messages. Use trace for that. %%--------------------------------------------------------------------------- -print_event(Dev, {in,Event}, {Name,_}) -> +print_event(Dev, {in,Event}, {Name,State}) -> io:format( - Dev, "*DBG* ~p received ~s~n", - [Name,event_string(Event)]); -print_event(Dev, {out,Reply,{To,_Tag}}, {Name,_}) -> + Dev, "*DBG* ~p receive ~s in state ~p~n", + [Name,event_string(Event),State]); +print_event(Dev, {out,Reply,{To,_Tag}}, {Name,State}) -> io:format( - Dev, "*DBG* ~p sent ~p to ~p~n", - [Name,Reply,To]); + Dev, "*DBG* ~p send ~p to ~p from state ~p~n", + [Name,Reply,To,State]); print_event(Dev, {Tag,Event,NextState}, {Name,State}) -> StateString = case NextState of @@ -874,22 +865,36 @@ loop_event( %% try case CallbackMode of + undefined -> + Module:callback_mode(); state_functions -> - Module:State(Type, Content, Data); + erlang:apply(Module, State, [Type,Content,Data]); handle_event_function -> Module:handle_event(Type, Content, State, Data) - end of + end + of + Result when CallbackMode =:= undefined -> + loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) catch + Result when CallbackMode =:= undefined -> + loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, Result); Result -> loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result); - error:badarg when CallbackMode =:= state_functions -> + error:badarg -> case erlang:get_stacktrace() of - [{erlang,apply,[Module,State,_],_}|Stacktrace] -> - Args = [Type,Content,Data], + [{erlang,apply, + [Module,State,[Type,Content,Data]=Args], + _} + |Stacktrace] + when CallbackMode =:= state_functions -> + %% We get here e.g if apply fails + %% due to State not being an atom terminate( error, {undef_state_function,{Module,State,Args}}, @@ -901,24 +906,29 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end; error:undef -> - %% Process an undef to check for the simple mistake + %% Process undef to check for the simple mistake %% of calling a nonexistent state function + %% to make the undef more precise case erlang:get_stacktrace() of - [{Module,State, - [Type,Content,Data]=Args, - _} + [{Module,callback_mode,[]=Args,_} + |Stacktrace] + when CallbackMode =:= undefined -> + terminate( + error, + {undef_callback,{Module,callback_mode,Args}}, + Stacktrace, + Debug, S, [Event|Events], State, Data, P); + [{Module,State,[Type,Content,Data]=Args,_} |Stacktrace] - when CallbackMode =:= state_functions -> + when CallbackMode =:= state_functions -> terminate( error, {undef_state_function,{Module,State,Args}}, Stacktrace, Debug, S, [Event|Events], State, Data, P); - [{Module,handle_event, - [Type,Content,State,Data]=Args, - _} + [{Module,handle_event,[Type,Content,State,Data]=Args,_} |Stacktrace] - when CallbackMode =:= handle_event_function -> + when CallbackMode =:= handle_event_function -> terminate( error, {undef_state_function,{Module,handle_event,Args}}, @@ -936,6 +946,25 @@ loop_event( Debug, S, [Event|Events], State, Data, P) end. +%% Interpret callback_mode() result +loop_event_callback_mode( + Parent, Debug, S, Events, State, Data, P, Event, CallbackMode) -> + case callback_mode(CallbackMode) of + true -> + Hibernate = false, % We have already GC:ed recently + loop_event( + Parent, Debug, + S#{callback_mode := CallbackMode}, + Events, + State, Data, P, Event, Hibernate); + false -> + terminate( + error, + {bad_return_from_callback_mode,CallbackMode}, + ?STACKTRACE(), + Debug, S, [Event|Events], State, Data, P) + end. + %% Interpret all callback return variants loop_event_result( Parent, Debug, S, Events, State, Data, P, Event, Result) -> @@ -988,7 +1017,9 @@ loop_event_result( State, Data, P, Event, State, Actions); _ -> terminate( - error, {bad_return_value,Result}, ?STACKTRACE(), + error, + {bad_return_from_state_function,Result}, + ?STACKTRACE(), Debug, S, [Event|Events], State, Data, P) end. @@ -1025,20 +1056,26 @@ loop_event_actions( Postpone, Hibernate, Timeout, NextEvents); false -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; {next_event,Type,Content} -> case event_type(Type) of true -> + NewDebug = + sys_debug(Debug, S, State, {in,{Type,Content}}), loop_event_actions( - Parent, Debug, S, Events, + Parent, NewDebug, S, Events, State, NewData, P, Event, NextState, Actions, Postpone, Hibernate, Timeout, [{Type,Content}|NextEvents]); false -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; %% Actions that set options @@ -1049,7 +1086,9 @@ loop_event_actions( NewPostpone, Hibernate, Timeout, NextEvents); {postpone,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); postpone -> loop_event_actions( @@ -1063,7 +1102,9 @@ loop_event_actions( Postpone, NewHibernate, Timeout, NextEvents); {hibernate,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); hibernate -> loop_event_actions( @@ -1082,7 +1123,9 @@ loop_event_actions( Postpone, Hibernate, NewTimeout, NextEvents); {timeout,_,_} -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P); infinity -> % Clear timer - it will never trigger loop_event_actions( @@ -1097,7 +1140,9 @@ loop_event_actions( Postpone, Hibernate, NewTimeout, NextEvents); _ -> terminate( - error, {bad_action,Action}, ?STACKTRACE(), + error, + {bad_action_from_state_function,Action}, + ?STACKTRACE(), Debug, S, [Event|Events], State, NewData, P) end; %% @@ -1169,7 +1214,9 @@ do_reply_then_terminate( NewDebug, S, Q, State, Data, P, Rs); _ -> terminate( - error, {bad_action,R}, ?STACKTRACE(), + error, + {bad_reply_action_from_state_function,R}, + ?STACKTRACE(), Debug, S, Q, State, Data, P) end. @@ -1188,8 +1235,9 @@ terminate( C:R -> ST = erlang:get_stacktrace(), error_info( - C, R, ST, Debug, S, Q, P, + C, R, ST, S, Q, P, format_status(terminate, get(), S, State, Data)), + sys:print_log(Debug), erlang:raise(C, R, ST) end, case Reason of @@ -1198,8 +1246,9 @@ terminate( {shutdown,_} -> ok; _ -> error_info( - Class, Reason, Stacktrace, Debug, S, Q, P, - format_status(terminate, get(), S, State, Data)) + Class, Reason, Stacktrace, S, Q, P, + format_status(terminate, get(), S, State, Data)), + sys:print_log(Debug) end, case Stacktrace of [] -> @@ -1209,7 +1258,7 @@ terminate( end. error_info( - Class, Reason, Stacktrace, Debug, + Class, Reason, Stacktrace, #{name := Name, callback_mode := CallbackMode}, Q, P, FmtData) -> {FixedReason,FixedStacktrace} = @@ -1276,9 +1325,7 @@ error_info( case FixedStacktrace of [] -> []; _ -> [FixedStacktrace] - end), - sys:print_log(Debug), - ok. + end). %% Call Module:format_status/2 or return a default value @@ -1291,7 +1338,7 @@ format_status(Opt, PDict, #{module := Module}, State, Data) -> _:_ -> format_status_default( Opt, State, - "Module:format_status/2 crashed") + atom_to_list(Module) ++ ":format_status/2 crashed") end; false -> format_status_default(Opt, State, Data) @@ -1299,10 +1346,10 @@ format_status(Opt, PDict, #{module := Module}, State, Data) -> %% The default Module:format_status/2 format_status_default(Opt, State, Data) -> - SSD = {State,Data}, + StateData = {State,Data}, case Opt of terminate -> - SSD; + StateData; _ -> - [{data,[{"State",SSD}]}] + [{data,[{"State",StateData}]}] end. 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/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl index 364314f91b..1d1417c2e6 100644 --- a/lib/stdlib/test/gen_statem_SUITE.erl +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -37,33 +37,32 @@ all() -> {group, stop_handle_event}, {group, abnormal}, {group, abnormal_handle_event}, - shutdown, stop_and_reply, event_order, + shutdown, stop_and_reply, event_order, code_change, {group, sys}, hibernate, enter_loop]. groups() -> - [{start, [], - [start1, start2, start3, start4, start5, start6, start7, - start8, start9, start10, start11, start12, next_events]}, - {start_handle_event, [], - [start1, start2, start3, start4, start5, start6, start7, - start8, start9, start10, start11, start12, next_events]}, - {stop, [], - [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, - {stop_handle_event, [], - [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]}, - {abnormal, [], [abnormal1, abnormal2]}, - {abnormal_handle_event, [], [abnormal1, abnormal2]}, - {sys, [], - [sys1, code_change, - call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]}, - {sys_handle_event, [], - [sys1, - call_format_status, - error_format_status, terminate_crash_format, - get_state, replace_state]}]. + [{start, [], tcs(start)}, + {start_handle_event, [], tcs(start)}, + {stop, [], tcs(stop)}, + {stop_handle_event, [], tcs(stop)}, + {abnormal, [], tcs(abnormal)}, + {abnormal_handle_event, [], tcs(abnormal)}, + {sys, [], tcs(sys)}, + {sys_handle_event, [], tcs(sys)}]. + +tcs(start) -> + [start1, start2, start3, start4, start5, start6, start7, + start8, start9, start10, start11, start12, next_events]; +tcs(stop) -> + [stop1, stop2, stop3, stop4, stop5, stop6, stop7, stop8, stop9, stop10]; +tcs(abnormal) -> + [abnormal1, abnormal2]; +tcs(sys) -> + [sys1, call_format_status, + error_format_status, terminate_crash_format, + get_state, replace_state]. + init_per_suite(Config) -> Config. @@ -461,10 +460,10 @@ abnormal2(Config) -> {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), %% bad return value in the gen_statem loop - {{bad_return_value,badreturn},_} = + {{bad_return_from_state_function,badreturn},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), receive - {'EXIT',Pid,{bad_return_value,badreturn}} -> ok + {'EXIT',Pid,{bad_return_from_state_function,badreturn}} -> ok after 5000 -> ct:fail(gen_statem_did_not_die) end, @@ -633,11 +632,13 @@ sys1(Config) -> sys:resume(Pid), stop_it(Pid). -code_change(Config) -> - Mode = handle_event_function, - {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), +code_change(_Config) -> + {ok,Pid} = + gen_statem:start( + ?MODULE, {callback_mode,state_functions,[]}, []), {idle,data} = sys:get_state(Pid), sys:suspend(Pid), + Mode = handle_event_function, sys:change_code(Pid, ?MODULE, old_vsn, Mode), sys:resume(Pid), {idle,{old_vsn,data,Mode}} = sys:get_state(Pid), @@ -708,7 +709,7 @@ error_format_status(Config) -> gen_statem:start( ?MODULE, start_arg(Config, {data,Data}), []), %% bad return value in the gen_statem loop - {{bad_return_value,badreturn},_} = + {{bad_return_from_state_function,badreturn},_} = ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), receive {error,_, @@ -716,7 +717,7 @@ error_format_status(Config) -> "** State machine"++_, [Pid,{{call,_},badreturn}, {formatted,idle,Data}, - error,{bad_return_value,badreturn}|_]}} -> + error,{bad_return_from_state_function,badreturn}|_]}} -> ok; Other when is_tuple(Other), element(1, Other) =:= error -> error_logger_forwarder:unregister(), @@ -1029,11 +1030,7 @@ enter_loop(_Config) -> end, %% Process not started using proc_lib - CallbackMode = state_functions, - Pid4 = - spawn_link( - gen_statem, enter_loop, - [?MODULE,[],CallbackMode,state0,[]]), + Pid4 = spawn_link(gen_statem, enter_loop, [?MODULE,[],state0,[]]), receive {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> ok @@ -1107,21 +1104,18 @@ enter_loop(Reg1, Reg2) -> anon -> ignore end, proc_lib:init_ack({ok, self()}), - CallbackMode = state_functions, case Reg2 of local -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], {local,armitage}); + ?MODULE, [], state0, [], {local,armitage}); global -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], {global,armitage}); + ?MODULE, [], state0, [], {global,armitage}); via -> gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, [], - {via, dummy_via, armitage}); + ?MODULE, [], state0, [], {via, dummy_via, armitage}); anon -> - gen_statem:enter_loop( - ?MODULE, [], CallbackMode, state0, []) + gen_statem:enter_loop(?MODULE, [], state0, []) end. @@ -1266,33 +1260,39 @@ init(stop_shutdown) -> {stop,shutdown}; init(sleep) -> ?t:sleep(1000), - {state_functions,idle,data}; + {ok,idle,data}; init(hiber) -> - {state_functions,hiber_idle,[]}; + {ok,hiber_idle,[]}; init(hiber_now) -> - {state_functions,hiber_idle,[],[hibernate]}; + {ok,hiber_idle,[],[hibernate]}; init({data, Data}) -> - {state_functions,idle,Data}; + {ok,idle,Data}; init({callback_mode,CallbackMode,Arg}) -> - case init(Arg) of - {_,State,Data,Ops} -> - {CallbackMode,State,Data,Ops}; - {_,State,Data} -> - {CallbackMode,State,Data}; - Other -> - Other - end; + ets:new(?MODULE, [named_table,private]), + ets:insert(?MODULE, {callback_mode,CallbackMode}), + init(Arg); init({map_statem,#{init := Init}=Machine}) -> + ets:new(?MODULE, [named_table,private]), + ets:insert(?MODULE, {callback_mode,handle_event_function}), case Init() of {ok,State,Data,Ops} -> - {handle_event_function,State,[Data|Machine],Ops}; + {ok,State,[Data|Machine],Ops}; {ok,State,Data} -> - {handle_event_function,State,[Data|Machine]}; + {ok,State,[Data|Machine]}; Other -> Other end; init([]) -> - {state_functions,idle,data}. + {ok,idle,data}. + +callback_mode() -> + try ets:lookup(?MODULE, callback_mode) of + [{callback_mode,CallbackMode}] -> + CallbackMode + catch + error:badarg -> + state_functions + end. terminate(_, _State, crash_terminate) -> exit({crash,terminate}); @@ -1568,7 +1568,12 @@ wrap_result(Result) -> code_change(OldVsn, State, Data, CallbackMode) -> - {CallbackMode,State,{OldVsn,Data,CallbackMode}}. + io:format( + "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), + ets:insert(?MODULE, {callback_mode,CallbackMode}), + io:format( + "code_change(~p, ~p, ~p, ~p)~n", [OldVsn,State,Data,CallbackMode]), + {ok,State,{OldVsn,Data,CallbackMode}}. format_status(terminate, [_Pdict,State,Data]) -> {formatted,State,Data}; 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/syntax_tools/doc/specs/.gitignore b/lib/syntax_tools/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/syntax_tools/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/syntax_tools/doc/src/Makefile b/lib/syntax_tools/doc/src/Makefile index ff4f3f78ff..e55222e59c 100644 --- a/lib/syntax_tools/doc/src/Makefile +++ b/lib/syntax_tools/doc/src/Makefile @@ -81,10 +81,15 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) + +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- XML_FLAGS += +SPECS_FLAGS = -I../../include DVIPS_FLAGS += # ---------------------------------------------------- @@ -93,7 +98,7 @@ DVIPS_FLAGS += $(HTMLDIR)/%.gif: %.gif $(INSTALL_DATA) $< $@ -docs: pdf html man +docs: man pdf html $(TOP_PDF_FILE): $(XML_FILES) @@ -120,6 +125,7 @@ clean clean_docs: rm -f $(MAN3DIR)/* rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECDIR)/* rm -f errs core *~ # ---------------------------------------------------- diff --git a/lib/syntax_tools/doc/src/specs.xml b/lib/syntax_tools/doc/src/specs.xml new file mode 100644 index 0000000000..04c3a494e7 --- /dev/null +++ b/lib/syntax_tools/doc/src/specs.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_epp_dodger.xml"/> + <xi:include href="../specs/specs_erl_comment_scan.xml"/> + <xi:include href="../specs/specs_erl_prettypr.xml"/> + <xi:include href="../specs/specs_erl_recomment.xml"/> + <xi:include href="../specs/specs_erl_syntax.xml"/> + <xi:include href="../specs/specs_erl_syntax_lib.xml"/> + <xi:include href="../specs/specs_erl_tidy.xml"/> + <xi:include href="../specs/specs_igor.xml"/> + <xi:include href="../specs/specs_merl.xml"/> + <xi:include href="../specs/specs_merl_transform.xml"/> + <xi:include href="../specs/specs_prettypr.xml"/> +</specs> diff --git a/lib/syntax_tools/src/erl_comment_scan.erl b/lib/syntax_tools/src/erl_comment_scan.erl index 03429d4d42..b5ac564146 100644 --- a/lib/syntax_tools/src/erl_comment_scan.erl +++ b/lib/syntax_tools/src/erl_comment_scan.erl @@ -30,8 +30,14 @@ %% ===================================================================== --type comment() :: {integer(), integer(), integer(), [string()]}. --type commentLine() :: {integer(), integer(), integer(), string()}. +-type comment() :: {Line:: integer(), + Column:: integer(), + Indentation :: integer(), + Text :: [string()]}. +-type commentLine() :: {Line :: integer(), + Column :: integer(), + Indent :: integer(), + Text :: string()}. %% ===================================================================== %% @spec file(FileName::file:filename()) -> [Comment] diff --git a/lib/syntax_tools/src/erl_prettypr.erl b/lib/syntax_tools/src/erl_prettypr.erl index f1615b2610..df0d78031c 100644 --- a/lib/syntax_tools/src/erl_prettypr.erl +++ b/lib/syntax_tools/src/erl_prettypr.erl @@ -195,10 +195,16 @@ format(Node) -> %% ===================================================================== %% @spec format(Tree::syntaxTree(), Options::[term()]) -> string() -%% syntaxTree() = erl_syntax:syntaxTree() %% -%% @type hook() = (syntaxTree(), context(), Continuation) -> document() -%% Continuation = (syntaxTree(), context()) -> document(). +%% @type syntaxTree() = erl_syntax:syntaxTree(). +%% +%% An abstract syntax tree. See the {@link erl_syntax} module for +%% details. +%% +%% @type hook() = (syntaxTree(), context(), Continuation) -> +%% prettypr:document() +%% Continuation = (syntaxTree(), context()) -> +%% prettypr:document(). %% %% A call-back function for user-controlled formatting. See {@link %% format/2}. @@ -277,7 +283,7 @@ format(Node, Options) -> %% ===================================================================== -%% @spec best(Tree::syntaxTree()) -> empty | document() +%% @spec best(Tree::syntaxTree()) -> empty | prettypr:document() %% @equiv best(Tree, []) -spec best(erl_syntax:syntaxTree()) -> 'empty' | prettypr:document(). @@ -288,7 +294,7 @@ best(Node) -> %% ===================================================================== %% @spec best(Tree::syntaxTree(), Options::[term()]) -> -%% empty | document() +%% empty | prettypr:document() %% %% @doc Creates a fixed "best" abstract layout for a syntax tree. This %% is similar to the `layout/2' function, except that here, the final @@ -310,7 +316,7 @@ best(Node, Options) -> %% ===================================================================== -%% @spec layout(Tree::syntaxTree()) -> document() +%% @spec layout(Tree::syntaxTree()) -> prettypr:document() %% @equiv layout(Tree, []) -spec layout(erl_syntax:syntaxTree()) -> prettypr:document(). @@ -320,8 +326,7 @@ layout(Node) -> %% ===================================================================== -%% @spec layout(Tree::syntaxTree(), Options::[term()]) -> document() -%% document() = prettypr:document() +%% @spec layout(Tree::syntaxTree(), Options::[term()]) -> prettypr:document() %% %% @doc Creates an abstract document layout for a syntax tree. The %% result represents a set of possible layouts (cf. module `prettypr'). diff --git a/lib/syntax_tools/src/erl_recomment.erl b/lib/syntax_tools/src/erl_recomment.erl index c1141b2bc6..1d23034991 100644 --- a/lib/syntax_tools/src/erl_recomment.erl +++ b/lib/syntax_tools/src/erl_recomment.erl @@ -30,6 +30,9 @@ -export([recomment_forms/2, quick_recomment_forms/2, recomment_tree/2]). +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. + %% ===================================================================== %% @spec quick_recomment_forms(Forms, Comments::[Comment]) -> %% syntaxTree() @@ -55,7 +58,6 @@ quick_recomment_forms(Tree, Cs) -> %% ===================================================================== %% @spec recomment_forms(Forms, Comments::[Comment]) -> syntaxTree() %% -%% syntaxTree() = erl_syntax:syntaxTree() %% Forms = syntaxTree() | [syntaxTree()] %% Comment = {Line, Column, Indentation, Text} %% Line = integer() diff --git a/lib/syntax_tools/src/erl_syntax_lib.erl b/lib/syntax_tools/src/erl_syntax_lib.erl index 9815559779..5aecf5d774 100644 --- a/lib/syntax_tools/src/erl_syntax_lib.erl +++ b/lib/syntax_tools/src/erl_syntax_lib.erl @@ -280,7 +280,7 @@ mapfoldl(_, S, []) -> %% ===================================================================== %% @spec variables(syntaxTree()) -> set(atom()) %% -%% set(T) = //stdlib/sets:set(T) +%% @type set(T) = //stdlib/sets:set(T) %% %% @doc Returns the names of variables occurring in a syntax tree, The %% result is a set of variable names represented by atoms. Macro names @@ -1955,7 +1955,7 @@ analyze_application(Node) -> %% ===================================================================== -%% @spec analyze_type_application(Node::syntaxTree()) -> typeName() +%% @spec analyze_type_application(Node::syntaxTree()) -> TypeName %% %% TypeName = {atom(), integer()} %% | {ModuleName, {atom(), integer()}} diff --git a/lib/syntax_tools/src/erl_tidy.erl b/lib/syntax_tools/src/erl_tidy.erl index f2de12b410..5d3fc6f062 100644 --- a/lib/syntax_tools/src/erl_tidy.erl +++ b/lib/syntax_tools/src/erl_tidy.erl @@ -36,6 +36,11 @@ %% been reasonably well tested, but the possibility of errors remains. %% Keep backups of your original code safely stored, until you feel %% confident that the new, modified code can be trusted. +%% +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. +%% +%% @type filename() = file:filename(). -module(erl_tidy). @@ -79,7 +84,6 @@ dir(Dir) -> %% ===================================================================== %% @spec dir(Directory::filename(), Options::[term()]) -> ok -%% filename() = file:filename() %% %% @doc Tidies Erlang source files in a directory and its %% subdirectories. @@ -414,7 +418,7 @@ write_module(Tree, Name, Opts) -> print_module(Tree, Opts) -> Printer = proplists:get_value(printer, Opts), - io:format(Printer(Tree, Opts)). + io:put_chars(Printer(Tree, Opts)). output(FD, Printer, Tree, Opts) -> io:put_chars(FD, Printer(Tree, Opts)), @@ -513,7 +517,6 @@ module(Forms) -> %% @spec module(Forms, Options::[term()]) -> syntaxTree() %% %% Forms = syntaxTree() | [syntaxTree()] -%% syntaxTree() = erl_syntax:syntaxTree() %% %% @doc Tidies a syntax tree representation of a module %% definition. The given `Forms' may be either a single diff --git a/lib/syntax_tools/src/igor.erl b/lib/syntax_tools/src/igor.erl index 1d14bd7c3a..943250e5cd 100644 --- a/lib/syntax_tools/src/igor.erl +++ b/lib/syntax_tools/src/igor.erl @@ -151,7 +151,8 @@ default_printer(Tree, Options) -> %% @spec parse_transform(Forms::[syntaxTree()], Options::[term()]) -> %% [syntaxTree()] %% -%% syntaxTree() = erl_syntax:syntaxTree() +%% @type syntaxTree() = erl_syntax:syntaxTree(). An abstract syntax +%% tree. See the {@link erl_syntax} module for details. %% %% @doc Allows Igor to work as a component of the Erlang compiler. %% Including the term `{parse_transform, igor}' in the @@ -212,7 +213,7 @@ merge(Name, Files) -> %% @spec merge(Name::atom(), Files::[filename()], Options::[term()]) -> %% [filename()] %% -%% filename() = file:filename() +%% @type filename() = file:filename() %% %% @doc Merges source code files to a single file. `Name' %% specifies the name of the resulting module - not the name of the @@ -367,6 +368,7 @@ merge_files(Name, Files, Options) -> %% @spec merge_files(Name::atom(), Sources::[Forms], %% Files::[filename()], Options::[term()]) -> %% {syntaxTree(), [stubDescriptor()]} +%% %% Forms = syntaxTree() | [syntaxTree()] %% %% @doc Merges source code files and syntax trees to a single syntax diff --git a/lib/syntax_tools/src/merl_tests.erl b/lib/syntax_tools/src/merl_tests.erl index c1aae3100e..27db594050 100644 --- a/lib/syntax_tools/src/merl_tests.erl +++ b/lib/syntax_tools/src/merl_tests.erl @@ -48,6 +48,21 @@ parse_error_test_() -> f(merl:quote("{"))) ]. +transform_parse_error_test_() -> + [?_assertEqual("merl:quote(\"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(\"{\")")], []))), + ?_assertEqual("merl:quote(2, \"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(2, \"{\")")], []))), + ?_assertEqual("merl:qquote(\"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(\"{\", [{var, V}])")], []))), + ?_assertEqual("merl:qquote(2, \"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(2, \"{\", [{var, V}])")], []))) + ]. + term_test_() -> [?_assertEqual(tuple, erl_syntax:type(merl:term({}))), ?_assertEqual("{foo, 42}", f(merl:term({foo, 42}))) diff --git a/lib/syntax_tools/src/merl_transform.erl b/lib/syntax_tools/src/merl_transform.erl index fe58b6a122..497baddd0a 100644 --- a/lib/syntax_tools/src/merl_transform.erl +++ b/lib/syntax_tools/src/merl_transform.erl @@ -104,10 +104,15 @@ expand_qquote([Text, Env], T, Line) -> case erl_syntax:is_literal(Text) of true -> As = [Line, erl_syntax:concrete(Text)], - %% expand further if possible - expand(merl:qquote(Line, "merl:subst(_@tree, _@env)", - [{tree, eval_call(Line, quote, As, T)}, - {env, Env}])); + case eval_call(Line, quote, As, failed) of + failed -> + T; + T1 -> + %% expand further if possible + expand(merl:qquote(Line, "merl:subst(_@tree, _@env)", + [{tree, T1}, + {env, Env}])) + end; false -> T end; diff --git a/lib/syntax_tools/test/merl_SUITE.erl b/lib/syntax_tools/test/merl_SUITE.erl index 945972d405..52bbd9b3b8 100644 --- a/lib/syntax_tools/test/merl_SUITE.erl +++ b/lib/syntax_tools/test/merl_SUITE.erl @@ -29,12 +29,14 @@ init_per_group/2,end_per_group/2]). %% Test cases --export([merl_smoke_test/1]). +-export([merl_smoke_test/1, + transform_parse_error_test/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> - [merl_smoke_test]. + [merl_smoke_test, + transform_parse_error_test]. groups() -> []. @@ -84,6 +86,21 @@ merl_smoke_test(Config) when is_list(Config) -> end)), ok. +transform_parse_error_test(_Config) -> + ?assertEqual("merl:quote(\"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(\"{\")")], []))), + ?assertEqual("merl:quote(2, \"{\")", + f(merl_transform:parse_transform( + [?Q("merl:quote(2, \"{\")")], []))), + ?assertEqual("merl:qquote(\"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(\"{\", [{var, V}])")], []))), + ?assertEqual("merl:qquote(2, \"{\", [{var, V}])", + f(merl_transform:parse_transform( + [?Q("merl:qquote(2, \"{\", [{var, V}])")], []))), + ok. + %% utilities f(Ts) when is_list(Ts) -> diff --git a/lib/syntax_tools/test/syntax_tools_SUITE.erl b/lib/syntax_tools/test/syntax_tools_SUITE.erl index b935d42bb7..43c17e9f1f 100644 --- a/lib/syntax_tools/test/syntax_tools_SUITE.erl +++ b/lib/syntax_tools/test/syntax_tools_SUITE.erl @@ -27,14 +27,14 @@ %% Test cases -export([app_test/1,appup_test/1,smoke_test/1,revert/1,revert_map/1, t_abstract_type/1,t_erl_parse_type/1,t_epp_dodger/1, - t_comment_scan/1,t_igor/1]). + t_comment_scan/1,t_igor/1,t_erl_tidy/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. all() -> [app_test,appup_test,smoke_test,revert,revert_map, t_abstract_type,t_erl_parse_type,t_epp_dodger, - t_comment_scan,t_igor]. + t_comment_scan,t_igor,t_erl_tidy]. groups() -> []. @@ -237,6 +237,12 @@ t_igor(Config) when is_list(Config) -> ok. +t_erl_tidy(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + File = filename:join(DataDir,"erl_tidy_tilde.erl"), + ok = erl_tidy:file(File, [{stdout, true}]), + ok. + test_comment_scan([],_) -> ok; test_comment_scan([File|Files],DataDir) -> Filename = filename:join(DataDir,File), diff --git a/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl b/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl new file mode 100644 index 0000000000..888264bad6 --- /dev/null +++ b/lib/syntax_tools/test/syntax_tools_SUITE_data/erl_tidy_tilde.erl @@ -0,0 +1,13 @@ +%% +%% File: erl_tidy_tilde.erl +%% Author: Mark Bucciarelli +%% Created: 2016-06-05 +%% + +-module(erl_tidy_tilde). + +-export([start/0]). + +start() -> + io:put_chars("tilde characters ('~')in source were " + "breaking erl_tidy\n"). diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml index 00cf5196b4..7fef74813b 100644 --- a/lib/tools/doc/src/erlang_mode.xml +++ b/lib/tools/doc/src/erlang_mode.xml @@ -252,7 +252,14 @@ behavior</item> <item>gen_event - skeleton for the OTP gen_event behavior</item> <item>gen_fsm - skeleton for the OTP gen_fsm behavior</item> - <item>gen_statem - skeleton for the OTP gen_statem behavior</item> + <item> + gen_statem (StateName/3) - skeleton for the OTP gen_statem behavior + using state name functions + </item> + <item> + gen_statem (handle_event/4) - skeleton for the OTP gen_statem behavior + using one state function + </item> <item>Library module - skeleton for a module that does not implement a process.</item> <item>Corba callback - skeleton for a Corba callback module.</item> diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml index 0b8a2be715..a0a817c0f2 100644 --- a/lib/tools/doc/src/notes.xml +++ b/lib/tools/doc/src/notes.xml @@ -31,6 +31,21 @@ </header> <p>This document describes the changes made to the Tools application.</p> +<section><title>Tools 2.8.5</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p>Correct a bug when adding multiple modules to an Xref + server. The bug was introduced in OTP-19.0. </p> + <p> + Own Id: OTP-13708 Aux Id: ERL-173 </p> + </item> + </list> + </section> + +</section> + <section><title>Tools 2.8.4</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index ce26c83295..0284c9d686 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -56,8 +56,10 @@ erlang-skel-gen-event erlang-skel-header) ("gen_fsm" "gen-fsm" erlang-skel-gen-fsm erlang-skel-header) - ("gen_statem" "gen-statem" - erlang-skel-gen-statem erlang-skel-header) + ("gen_statem (StateName/3)" "gen-statem-StateName" + erlang-skel-gen-statem-StateName erlang-skel-header) + ("gen_statem (handle_event/4)" "gen-statem-handle-event" + erlang-skel-gen-statem-handle-event erlang-skel-header) ("wx_object" "wx-object" erlang-skel-wx-object erlang-skel-header) ("Library module" "gen-lib" @@ -497,6 +499,7 @@ Please see the function `tempo-define-template'.") "%% {stop, Reason}" n (erlang-skel-separator-end 2) "init([]) ->" n> + "process_flag(trap_exit, true)," n> "{ok, #state{}}." n n (erlang-skel-separator-start 2) @@ -740,6 +743,7 @@ Please see the function `tempo-define-template'.") "%% {stop, StopReason}" n (erlang-skel-separator-end 2) "init([]) ->" n> + "process_flag(trap_exit, true)," n> "{ok, state_name, #state{}}." n n (erlang-skel-separator-start 2) @@ -860,7 +864,7 @@ Please see the function `tempo-define-template'.") "*The template of a gen_fsm. Please see the function `tempo-define-template'.") -(defvar erlang-skel-gen-statem +(defvar erlang-skel-gen-statem-StateName '((erlang-skel-include erlang-skel-large-header) "-behaviour(gen_statem)." n n @@ -868,9 +872,8 @@ Please see the function `tempo-define-template'.") "-export([start_link/0])." n n "%% gen_statem callbacks" n - "-export([init/1, terminate/3, code_change/4])." n + "-export([callback_mode/0, init/1, terminate/3, code_change/4])." n "-export([state_name/3])." n - "-export([handle_event/4])." n n "-define(SERVER, ?MODULE)." n n @@ -899,48 +902,151 @@ Please see the function `tempo-define-template'.") (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n + "%% Define the callback_mode() for this callback module." n + (erlang-skel-separator-end 2) + "-spec callback_mode() -> gen_statem:callback_mode()." n + "callback_mode() -> state_functions." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n "%% Whenever a gen_statem is started using gen_statem:start/[3,4] or" n "%% 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> - "{gen_statem:callback_mode()," n> - "State :: term(), Data :: term()} |" n> - "{gen_statem:callback_mode()," n> - "State :: term(), Data :: term()," n> + "-spec init(Args :: term()) ->" n> + "{ok, State :: term(), Data :: term()} |" n> + "{ok, State :: term(), Data :: term()," n> "[gen_statem:action()] | gen_statem:action()} |" n> "ignore |" n> "{stop, Reason :: term()}." n "init([]) ->" n> - "{state_functions, state_name, #data{}}." n + "process_flag(trap_exit, true)," n> + "{ok, state_name, #data{}}." n n (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n - "%% If the gen_statem runs with CallbackMode =:= state_functions" n - "%% there should be one instance of this function for each possible" n - "%% state name. Whenever a gen_statem receives an event," n - "%% the instance of this function with the same name" n - "%% as the current state name StateName is called to" n - "%% handle the event." n + "%% There should be one function like this for each state name." n + "%% Whenever a gen_statem receives an event, the function " n + "%% with the name of the current state (StateName) " n + "%% is called to handle the event." n + "%%" n + "%% NOTE: If there is an exported function handle_event/4, it is called" n + "%% instead of StateName/3 functions like this!" n (erlang-skel-separator-end 2) "-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 (erlang-skel-separator-start 2) "%% @private" n "%% @doc" n - "%% If the gen_statem runs with CallbackMode =:= handle_event_function" n - "%% this function is called for every event a gen_statem receives." n + "%% This function is called by a gen_statem when it is about to" n + "%% terminate. It should be the opposite of Module:init/1 and do any" n + "%% necessary cleaning up. When it returns, the gen_statem terminates with" n + "%% Reason. The return value is ignored." n + (erlang-skel-separator-end 2) + "-spec terminate(Reason :: term(), State :: term(), Data :: term()) ->" n> + "any()." n + "terminate(_Reason, _State, _Data) ->" n> + "void." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Convert process state when code is changed" n + (erlang-skel-separator-end 2) + "-spec code_change(" n> + "OldVsn :: term() | {down,term()}," n> + "State :: term(), Data :: term(), Extra :: term()) ->" n> + "{ok, NewState :: term(), NewData :: term()} |" n> + "(Reason :: term())." n + "code_change(_OldVsn, State, Data, _Extra) ->" n> + "{ok, State, Data}." n + n + (erlang-skel-double-separator-start 3) + "%%% Internal functions" n + (erlang-skel-double-separator-end 3) + ) + "*The template of a gen_statem (StateName/3). +Please see the function `tempo-define-template'.") + +(defvar erlang-skel-gen-statem-handle-event + '((erlang-skel-include erlang-skel-large-header) + "-behaviour(gen_statem)." n n + + "%% API" n + "-export([start_link/0])." n + n + "%% gen_statem callbacks" n + "-export([callback_mode/0, init/1, terminate/3, code_change/4])." n + "-export([handle_event/4])." n + n + "-define(SERVER, ?MODULE)." n + n + "-record(data, {})." n + n + (erlang-skel-double-separator-start 3) + "%%% API" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @doc" n + "%% Creates a gen_statem process which calls Module:init/1 to" n + "%% initialize. To ensure a synchronized start-up procedure, this" n + "%% function does not return until Module:init/1 has returned." n + "%%" n + (erlang-skel-separator-end 2) + "-spec start_link() ->" n> + "{ok, Pid :: pid()} |" n> + "ignore |" n> + "{error, Error :: term()}." n + "start_link() ->" n> + "gen_statem:start_link({local, ?SERVER}, ?MODULE, [], [])." n + n + (erlang-skel-double-separator-start 3) + "%%% gen_statem callbacks" n + (erlang-skel-double-separator-end 3) n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Define the callback_mode() for this callback module." n + (erlang-skel-separator-end 2) + "-spec callback_mode() -> gen_statem:callback_mode()." n + "callback_mode() -> handle_event_function." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% Whenever a gen_statem is started using gen_statem:start/[3,4] or" n + "%% 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> + "{ok, State :: term(), Data :: term()} |" n> + "{ok, State :: term(), Data :: term()," n> + "[gen_statem:action()] | gen_statem:action()} |" n> + "ignore |" n> + "{stop, Reason :: term()}." n + "init([]) ->" n> + "process_flag(trap_exit, true)," n> + "{ok, state_name, #data{}}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" n + "%% This function is called for every event a gen_statem receives." n + "%%" n + "%% NOTE: If there is no exported function handle_event/4," n + "%% StateName/3 functions are called instead!" n (erlang-skel-separator-end 2) "-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,7 +1071,8 @@ 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 + "{ok, NewState :: term(), NewData :: term()} |" n> + "(Reason :: term())." n "code_change(_OldVsn, State, Data, _Extra) ->" n> "{ok, State, Data}." n n @@ -973,7 +1080,7 @@ Please see the function `tempo-define-template'.") "%%% Internal functions" n (erlang-skel-double-separator-end 3) ) - "*The template of a gen_statem. + "*The template of a gen_statem (handle_event/4). Please see the function `tempo-define-template'.") (defvar erlang-skel-wx-object 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/tools/src/xref_base.erl b/lib/tools/src/xref_base.erl index bb9815f9b0..f298a1ce81 100644 --- a/lib/tools/src/xref_base.erl +++ b/lib/tools/src/xref_base.erl @@ -746,7 +746,7 @@ read_a_module({Dir, BaseName}, AppName, Builtins, Verbose, Warnings, Mode) -> message(Warnings, no_debug_info, [File]), no; {ok, M, Data, UnresCalls0} -> - message(Verbose, done, [File]), + message(Verbose, done_file, [File]), %% Remove duplicates. Identical unresolved calls on the %% same line are counted as _one_ unresolved call. UnresCalls = usort(UnresCalls0), @@ -1842,6 +1842,8 @@ message(true, What, Arg) -> set_up -> io:format("Setting up...", Arg); done -> + io:format("done~n", Arg); + done_file -> io:format("done reading ~ts~n", Arg); error -> io:format("error~n", Arg); diff --git a/lib/tools/test/cover_SUITE.erl b/lib/tools/test/cover_SUITE.erl index b4c9264b30..90e113c178 100644 --- a/lib/tools/test/cover_SUITE.erl +++ b/lib/tools/test/cover_SUITE.erl @@ -1001,41 +1001,40 @@ otp_6115(Config) when is_list(Config) -> %% Cover compile f1, but not f2 {ok, f1} = cover:compile(f1), + %% This test used to ensure that a process containing a + %% fun refering to cover compiled code was killed. + %% check_process_code may however ignore funs as of ERTS + %% version 8.1. The test has therefore been rewritten to + %% check that a process with a direct reference (in this + %% case a return address) to the code is killed. + %% %% If f1 is cover compiled, a process P is started with a - %% reference to the fun created in start_fail/0, and cover:stop() is - %% called, then P should be killed. - %% This is because (the fun held by P) references the cover + %% direct reference to the f1, and cover:stop() is called, + %% then P should be killed. + %% This is because of the return address to the cover %% compiled code which should be *unloaded* when cover:stop() is %% called -- running cover compiled code when there is no cover %% server and thus no ets tables to bump counters in, makes no %% sense. - Pid1 = f1:start_a(), - Pid2 = f1:start_b(), + Pid = spawn(fun () -> f1:non_tail_call_f2_wait() end), %% Now stop cover cover:stop(), %% Ensure that f1 is loaded (and not cover compiled), and that - %% both Pid1 and Pid2 are dead. + %% both Pid is dead. case code:which(f1) of Beam when is_list(Beam) -> ok; Other -> ct:fail({"f1 is not reloaded", Other}) end, - case process_info(Pid1) of + case process_info(Pid) of undefined -> ok; - _PI1 -> - RefToOldP1 = erlang:check_process_code(Pid1, f1), - ct:fail({"Pid1 still alive", RefToOldP1}) - end, - case process_info(Pid2) of - undefined -> - ok; - _PI2 -> - RefToOldP2 = erlang:check_process_code(Pid1, f2), - ct:fail({"Pid2 still alive", RefToOldP2}) + _PI -> + RefToOldP = erlang:check_process_code(Pid, f1), + ct:fail({"Pid still alive", RefToOldP}) end, file:set_cwd(CWD), diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl index 5399b33f19..fc4a62e70e 100644 --- a/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl +++ b/lib/tools/test/cover_SUITE_data/otp_6115/f1.erl @@ -1,13 +1,6 @@ -module(f1). --export([start_a/0, start_b/0]). +-export([non_tail_call_f2_wait/0]). -start_a() -> - f2:start(fun() -> - ok - end). - -start_b() -> - f2:start(fun fun1/0). - -fun1() -> - ok. +non_tail_call_f2_wait() -> + f2:wait(), + im_back. diff --git a/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl index 72a6a64c4d..4bc88035c7 100644 --- a/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl +++ b/lib/tools/test/cover_SUITE_data/otp_6115/f2.erl @@ -1,13 +1,5 @@ -module(f2). --export([start/1]). +-export([wait/0]). -start(Fun) -> - spawn(fun() -> - wait(Fun) - end). - -wait(Fun) -> - receive - go -> - Fun() - end. +wait() -> + receive after infinity -> ok end. diff --git a/lib/tools/test/xref_SUITE.erl b/lib/tools/test/xref_SUITE.erl index ce30fb711a..01dbac6ecb 100644 --- a/lib/tools/test/xref_SUITE.erl +++ b/lib/tools/test/xref_SUITE.erl @@ -50,7 +50,7 @@ -export([analyze/1, basic/1, md/1, q/1, variables/1, unused_locals/1]). --export([format_error/1, otp_7423/1, otp_7831/1, otp_10192/1]). +-export([format_error/1, otp_7423/1, otp_7831/1, otp_10192/1, otp_13708/1]). -import(lists, [append/2, flatten/1, keysearch/3, member/2, sort/1, usort/1]). @@ -82,7 +82,7 @@ groups() -> fun_mfa_r14, fun_mfa_vars, qlc]}, {analyses, [], [analyze, basic, md, q, variables, unused_locals]}, - {misc, [], [format_error, otp_7423, otp_7831, otp_10192]}]. + {misc, [], [format_error, otp_7423, otp_7831, otp_10192, otp_13708]}]. init_per_suite(Conf) when is_list(Conf) -> @@ -2393,6 +2393,19 @@ otp_10192(Conf) when is_list(Conf) -> xref:stop(s), ok. +%% OTP-10192. Allow filenames with character codes greater than 126. +otp_13708(Conf) when is_list(Conf) -> + {ok, _} = start(s), + ok = xref:set_default(s, [{verbose, true}]), + {ok, []} = xref:q(s,"E"), + xref:stop(s), + + CopyDir = ?copydir, + Dir = fname(CopyDir,"lib_test"), + {ok, _} = start(s), + ok = xref:set_library_path(s, [Dir], [{verbose, true}]), + xref:stop(s). + %%% %%% Utilities %%% diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk index 8c889cbe4e..e6287b0430 100644 --- a/lib/tools/vsn.mk +++ b/lib/tools/vsn.mk @@ -1 +1 @@ -TOOLS_VSN = 2.8.4 +TOOLS_VSN = 2.8.5 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/doc/specs/.gitignore b/lib/wx/doc/specs/.gitignore new file mode 100644 index 0000000000..322eebcb06 --- /dev/null +++ b/lib/wx/doc/specs/.gitignore @@ -0,0 +1 @@ +specs_*.xml diff --git a/lib/wx/doc/src/Makefile b/lib/wx/doc/src/Makefile index cae2f9fe4e..23890f47f0 100644 --- a/lib/wx/doc/src/Makefile +++ b/lib/wx/doc/src/Makefile @@ -63,10 +63,15 @@ HTML_REF_MAN_FILE = $(HTMLDIR)/index.html TOP_PDF_FILE = $(PDFDIR)/$(APPLICATION)-$(VSN).pdf +SPECS_FILES = $(XML_REF3_FILES:%.xml=$(SPECDIR)/specs_%.xml) + +TOP_SPECS_FILE = specs.xml + # ---------------------------------------------------- # FLAGS # ---------------------------------------------------- XML_FLAGS += +SPECS_FLAGS = -I../../include -I../../src DVIPS_FLAGS += # ---------------------------------------------------- @@ -111,6 +116,7 @@ clean clean_docs: rm -rf $(HTMLDIR)/* rm -f $(MAN3DIR)/* rm -f $(TOP_PDF_FILE) $(TOP_PDF_FILE:%.pdf=%.fo) + rm -f $(SPECDIR)/* rm -f errs core *~ ../html/edoc-info rm -f $(XML_REF3_FILES) $(XML_CHAPTER_FILES) *.html diff --git a/lib/wx/doc/src/specs.xml b/lib/wx/doc/src/specs.xml new file mode 100644 index 0000000000..2e19baafc4 --- /dev/null +++ b/lib/wx/doc/src/specs.xml @@ -0,0 +1,232 @@ +<?xml version="1.0" encoding="utf-8" ?> +<specs xmlns:xi="http://www.w3.org/2001/XInclude"> + <xi:include href="../specs/specs_wx.xml"/> + <xi:include href="../specs/specs_wx_object.xml"/> + <xi:include href="../specs/specs_wxAcceleratorEntry.xml"/> + <xi:include href="../specs/specs_wxAcceleratorTable.xml"/> + <xi:include href="../specs/specs_wxActivateEvent.xml"/> + <xi:include href="../specs/specs_wxArtProvider.xml"/> + <xi:include href="../specs/specs_wxAuiDockArt.xml"/> + <xi:include href="../specs/specs_wxAuiManager.xml"/> + <xi:include href="../specs/specs_wxAuiManagerEvent.xml"/> + <xi:include href="../specs/specs_wxAuiNotebook.xml"/> + <xi:include href="../specs/specs_wxAuiNotebookEvent.xml"/> + <xi:include href="../specs/specs_wxAuiPaneInfo.xml"/> + <xi:include href="../specs/specs_wxAuiSimpleTabArt.xml"/> + <xi:include href="../specs/specs_wxAuiTabArt.xml"/> + <xi:include href="../specs/specs_wxBitmapButton.xml"/> + <xi:include href="../specs/specs_wxBitmapDataObject.xml"/> + <xi:include href="../specs/specs_wxBitmap.xml"/> + <xi:include href="../specs/specs_wxBoxSizer.xml"/> + <xi:include href="../specs/specs_wxBrush.xml"/> + <xi:include href="../specs/specs_wxBufferedDC.xml"/> + <xi:include href="../specs/specs_wxBufferedPaintDC.xml"/> + <xi:include href="../specs/specs_wxButton.xml"/> + <xi:include href="../specs/specs_wxCalendarCtrl.xml"/> + <xi:include href="../specs/specs_wxCalendarDateAttr.xml"/> + <xi:include href="../specs/specs_wxCalendarEvent.xml"/> + <xi:include href="../specs/specs_wxCaret.xml"/> + <xi:include href="../specs/specs_wxCheckBox.xml"/> + <xi:include href="../specs/specs_wxCheckListBox.xml"/> + <xi:include href="../specs/specs_wxChildFocusEvent.xml"/> + <xi:include href="../specs/specs_wxChoicebook.xml"/> + <xi:include href="../specs/specs_wxChoice.xml"/> + <xi:include href="../specs/specs_wxClientDC.xml"/> + <xi:include href="../specs/specs_wxClipboard.xml"/> + <xi:include href="../specs/specs_wxClipboardTextEvent.xml"/> + <xi:include href="../specs/specs_wxCloseEvent.xml"/> + <xi:include href="../specs/specs_wxColourData.xml"/> + <xi:include href="../specs/specs_wxColourDialog.xml"/> + <xi:include href="../specs/specs_wxColourPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxColourPickerEvent.xml"/> + <xi:include href="../specs/specs_wxComboBox.xml"/> + <xi:include href="../specs/specs_wxCommandEvent.xml"/> + <xi:include href="../specs/specs_wxContextMenuEvent.xml"/> + <xi:include href="../specs/specs_wxControl.xml"/> + <xi:include href="../specs/specs_wxControlWithItems.xml"/> + <xi:include href="../specs/specs_wxCursor.xml"/> + <xi:include href="../specs/specs_wxDataObject.xml"/> + <xi:include href="../specs/specs_wxDateEvent.xml"/> + <xi:include href="../specs/specs_wxDatePickerCtrl.xml"/> + <xi:include href="../specs/specs_wxDC.xml"/> + <xi:include href="../specs/specs_wxDCOverlay.xml"/> + <xi:include href="../specs/specs_wxDialog.xml"/> + <xi:include href="../specs/specs_wxDirDialog.xml"/> + <xi:include href="../specs/specs_wxDirPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxDisplayChangedEvent.xml"/> + <xi:include href="../specs/specs_wxEraseEvent.xml"/> + <xi:include href="../specs/specs_wxEvent.xml"/> + <xi:include href="../specs/specs_wxEvtHandler.xml"/> + <xi:include href="../specs/specs_wxFileDataObject.xml"/> + <xi:include href="../specs/specs_wxFileDialog.xml"/> + <xi:include href="../specs/specs_wxFileDirPickerEvent.xml"/> + <xi:include href="../specs/specs_wxFilePickerCtrl.xml"/> + <xi:include href="../specs/specs_wxFindReplaceData.xml"/> + <xi:include href="../specs/specs_wxFindReplaceDialog.xml"/> + <xi:include href="../specs/specs_wxFlexGridSizer.xml"/> + <xi:include href="../specs/specs_wxFocusEvent.xml"/> + <xi:include href="../specs/specs_wxFontData.xml"/> + <xi:include href="../specs/specs_wxFontDialog.xml"/> + <xi:include href="../specs/specs_wxFont.xml"/> + <xi:include href="../specs/specs_wxFontPickerCtrl.xml"/> + <xi:include href="../specs/specs_wxFontPickerEvent.xml"/> + <xi:include href="../specs/specs_wxFrame.xml"/> + <xi:include href="../specs/specs_wxGauge.xml"/> + <xi:include href="../specs/specs_wxGBSizerItem.xml"/> + <xi:include href="../specs/specs_wxGenericDirCtrl.xml"/> + <xi:include href="../specs/specs_wxGLCanvas.xml"/> + <xi:include href="../specs/specs_wxGraphicsBrush.xml"/> + <xi:include href="../specs/specs_wxGraphicsContext.xml"/> + <xi:include href="../specs/specs_wxGraphicsFont.xml"/> + <xi:include href="../specs/specs_wxGraphicsMatrix.xml"/> + <xi:include href="../specs/specs_wxGraphicsObject.xml"/> + <xi:include href="../specs/specs_wxGraphicsPath.xml"/> + <xi:include href="../specs/specs_wxGraphicsPen.xml"/> + <xi:include href="../specs/specs_wxGraphicsRenderer.xml"/> + <xi:include href="../specs/specs_wxGridBagSizer.xml"/> + <xi:include href="../specs/specs_wxGridCellAttr.xml"/> + <xi:include href="../specs/specs_wxGridCellBoolEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellBoolRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellChoiceEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellFloatEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellFloatRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellNumberEditor.xml"/> + <xi:include href="../specs/specs_wxGridCellNumberRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellStringRenderer.xml"/> + <xi:include href="../specs/specs_wxGridCellTextEditor.xml"/> + <xi:include href="../specs/specs_wxGrid.xml"/> + <xi:include href="../specs/specs_wxGridEvent.xml"/> + <xi:include href="../specs/specs_wxGridSizer.xml"/> + <xi:include href="../specs/specs_wxHelpEvent.xml"/> + <xi:include href="../specs/specs_wxHtmlEasyPrinting.xml"/> + <xi:include href="../specs/specs_wxHtmlLinkEvent.xml"/> + <xi:include href="../specs/specs_wxHtmlWindow.xml"/> + <xi:include href="../specs/specs_wxIconBundle.xml"/> + <xi:include href="../specs/specs_wxIcon.xml"/> + <xi:include href="../specs/specs_wxIconizeEvent.xml"/> + <xi:include href="../specs/specs_wxIdleEvent.xml"/> + <xi:include href="../specs/specs_wxImage.xml"/> + <xi:include href="../specs/specs_wxImageList.xml"/> + <xi:include href="../specs/specs_wxInitDialogEvent.xml"/> + <xi:include href="../specs/specs_wxJoystickEvent.xml"/> + <xi:include href="../specs/specs_wxKeyEvent.xml"/> + <xi:include href="../specs/specs_wxLayoutAlgorithm.xml"/> + <xi:include href="../specs/specs_wxListbook.xml"/> + <xi:include href="../specs/specs_wxListBox.xml"/> + <xi:include href="../specs/specs_wxListCtrl.xml"/> + <xi:include href="../specs/specs_wxListEvent.xml"/> + <xi:include href="../specs/specs_wxListItemAttr.xml"/> + <xi:include href="../specs/specs_wxListItem.xml"/> + <xi:include href="../specs/specs_wxListView.xml"/> + <xi:include href="../specs/specs_wxLocale.xml"/> + <xi:include href="../specs/specs_wxLogNull.xml"/> + <xi:include href="../specs/specs_wxMask.xml"/> + <xi:include href="../specs/specs_wxMaximizeEvent.xml"/> + <xi:include href="../specs/specs_wxMDIChildFrame.xml"/> + <xi:include href="../specs/specs_wxMDIClientWindow.xml"/> + <xi:include href="../specs/specs_wxMDIParentFrame.xml"/> + <xi:include href="../specs/specs_wxMemoryDC.xml"/> + <xi:include href="../specs/specs_wxMenuBar.xml"/> + <xi:include href="../specs/specs_wxMenu.xml"/> + <xi:include href="../specs/specs_wxMenuEvent.xml"/> + <xi:include href="../specs/specs_wxMenuItem.xml"/> + <xi:include href="../specs/specs_wxMessageDialog.xml"/> + <xi:include href="../specs/specs_wxMiniFrame.xml"/> + <xi:include href="../specs/specs_wxMirrorDC.xml"/> + <xi:include href="../specs/specs_wxMouseCaptureChangedEvent.xml"/> + <xi:include href="../specs/specs_wxMouseCaptureLostEvent.xml"/> + <xi:include href="../specs/specs_wxMouseEvent.xml"/> + <xi:include href="../specs/specs_wxMoveEvent.xml"/> + <xi:include href="../specs/specs_wxMultiChoiceDialog.xml"/> + <xi:include href="../specs/specs_wxNavigationKeyEvent.xml"/> + <xi:include href="../specs/specs_wxNotebook.xml"/> + <xi:include href="../specs/specs_wxNotebookEvent.xml"/> + <xi:include href="../specs/specs_wxNotifyEvent.xml"/> + <xi:include href="../specs/specs_wxOverlay.xml"/> + <xi:include href="../specs/specs_wxPageSetupDialogData.xml"/> + <xi:include href="../specs/specs_wxPageSetupDialog.xml"/> + <xi:include href="../specs/specs_wxPaintDC.xml"/> + <xi:include href="../specs/specs_wxPaintEvent.xml"/> + <xi:include href="../specs/specs_wxPaletteChangedEvent.xml"/> + <xi:include href="../specs/specs_wxPalette.xml"/> + <xi:include href="../specs/specs_wxPanel.xml"/> + <xi:include href="../specs/specs_wxPasswordEntryDialog.xml"/> + <xi:include href="../specs/specs_wxPen.xml"/> + <xi:include href="../specs/specs_wxPickerBase.xml"/> + <xi:include href="../specs/specs_wxPopupTransientWindow.xml"/> + <xi:include href="../specs/specs_wxPopupWindow.xml"/> + <xi:include href="../specs/specs_wxPostScriptDC.xml"/> + <xi:include href="../specs/specs_wxPreviewCanvas.xml"/> + <xi:include href="../specs/specs_wxPreviewControlBar.xml"/> + <xi:include href="../specs/specs_wxPreviewFrame.xml"/> + <xi:include href="../specs/specs_wxPrintData.xml"/> + <xi:include href="../specs/specs_wxPrintDialogData.xml"/> + <xi:include href="../specs/specs_wxPrintDialog.xml"/> + <xi:include href="../specs/specs_wxPrinter.xml"/> + <xi:include href="../specs/specs_wxPrintout.xml"/> + <xi:include href="../specs/specs_wxPrintPreview.xml"/> + <xi:include href="../specs/specs_wxProgressDialog.xml"/> + <xi:include href="../specs/specs_wxQueryNewPaletteEvent.xml"/> + <xi:include href="../specs/specs_wxRadioBox.xml"/> + <xi:include href="../specs/specs_wxRadioButton.xml"/> + <xi:include href="../specs/specs_wxRegion.xml"/> + <xi:include href="../specs/specs_wxSashEvent.xml"/> + <xi:include href="../specs/specs_wxSashLayoutWindow.xml"/> + <xi:include href="../specs/specs_wxSashWindow.xml"/> + <xi:include href="../specs/specs_wxScreenDC.xml"/> + <xi:include href="../specs/specs_wxScrollBar.xml"/> + <xi:include href="../specs/specs_wxScrolledWindow.xml"/> + <xi:include href="../specs/specs_wxScrollEvent.xml"/> + <xi:include href="../specs/specs_wxScrollWinEvent.xml"/> + <xi:include href="../specs/specs_wxSetCursorEvent.xml"/> + <xi:include href="../specs/specs_wxShowEvent.xml"/> + <xi:include href="../specs/specs_wxSingleChoiceDialog.xml"/> + <xi:include href="../specs/specs_wxSizeEvent.xml"/> + <xi:include href="../specs/specs_wxSizer.xml"/> + <xi:include href="../specs/specs_wxSizerFlags.xml"/> + <xi:include href="../specs/specs_wxSizerItem.xml"/> + <xi:include href="../specs/specs_wxSlider.xml"/> + <xi:include href="../specs/specs_wxSpinButton.xml"/> + <xi:include href="../specs/specs_wxSpinCtrl.xml"/> + <xi:include href="../specs/specs_wxSpinEvent.xml"/> + <xi:include href="../specs/specs_wxSplashScreen.xml"/> + <xi:include href="../specs/specs_wxSplitterEvent.xml"/> + <xi:include href="../specs/specs_wxSplitterWindow.xml"/> + <xi:include href="../specs/specs_wxStaticBitmap.xml"/> + <xi:include href="../specs/specs_wxStaticBox.xml"/> + <xi:include href="../specs/specs_wxStaticBoxSizer.xml"/> + <xi:include href="../specs/specs_wxStaticLine.xml"/> + <xi:include href="../specs/specs_wxStaticText.xml"/> + <xi:include href="../specs/specs_wxStatusBar.xml"/> + <xi:include href="../specs/specs_wxStdDialogButtonSizer.xml"/> + <xi:include href="../specs/specs_wxStyledTextCtrl.xml"/> + <xi:include href="../specs/specs_wxStyledTextEvent.xml"/> + <xi:include href="../specs/specs_wxSysColourChangedEvent.xml"/> + <xi:include href="../specs/specs_wxSystemOptions.xml"/> + <xi:include href="../specs/specs_wxSystemSettings.xml"/> + <xi:include href="../specs/specs_wxTaskBarIcon.xml"/> + <xi:include href="../specs/specs_wxTaskBarIconEvent.xml"/> + <xi:include href="../specs/specs_wxTextAttr.xml"/> + <xi:include href="../specs/specs_wxTextCtrl.xml"/> + <xi:include href="../specs/specs_wxTextDataObject.xml"/> + <xi:include href="../specs/specs_wxTextEntryDialog.xml"/> + <xi:include href="../specs/specs_wxToggleButton.xml"/> + <xi:include href="../specs/specs_wxToolBar.xml"/> + <xi:include href="../specs/specs_wxToolbook.xml"/> + <xi:include href="../specs/specs_wxToolTip.xml"/> + <xi:include href="../specs/specs_wxTopLevelWindow.xml"/> + <xi:include href="../specs/specs_wxTreebook.xml"/> + <xi:include href="../specs/specs_wxTreeCtrl.xml"/> + <xi:include href="../specs/specs_wxTreeEvent.xml"/> + <xi:include href="../specs/specs_wxUpdateUIEvent.xml"/> + <xi:include href="../specs/specs_wxWindowCreateEvent.xml"/> + <xi:include href="../specs/specs_wxWindowDC.xml"/> + <xi:include href="../specs/specs_wxWindowDestroyEvent.xml"/> + <xi:include href="../specs/specs_wxWindow.xml"/> + <xi:include href="../specs/specs_wxXmlResource.xml"/> + <xi:include href="../specs/specs_wx_misc.xml"/> + <xi:include href="../specs/specs_glu.xml"/> + <xi:include href="../specs/specs_gl.xml"/> +</specs> 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}. |