diff options
Diffstat (limited to 'lib')
132 files changed, 6763 insertions, 3433 deletions
diff --git a/lib/common_test/test_server/ts_run.erl b/lib/common_test/test_server/ts_run.erl index a856fc6f8d..66db1ff9a7 100644 --- a/lib/common_test/test_server/ts_run.erl +++ b/lib/common_test/test_server/ts_run.erl @@ -261,7 +261,7 @@ run_batch(Vars, _Spec, State) -> Command = State#state.command ++ " -noinput -s erlang halt", ts_lib:progress(Vars, 1, "Command: ~ts~n", [Command]), io:format(user, "Command: ~ts~n",[Command]), - Port = open_port({spawn, Command}, [stream, in, eof]), + Port = open_port({spawn, Command}, [stream, in, eof, exit_status]), Timeout = 30000 * case os:getenv("TS_RUN_VALGRIND") of false -> 1; _ -> 100 @@ -284,7 +284,16 @@ tricky_print_data(Port, Timeout) -> ok after 1 -> % force context switch ok - end + end, + receive + {Port, {exit_status, 0}} -> + ok; + {Port, {exit_status, N}} -> + io:format(user, "Test run exited with status ~p~n", [N]) + after 1 -> + %% This shouldn't happen, but better safe then hanging + ok + end after Timeout -> case erl_epmd:names() of {ok,Names} -> diff --git a/lib/compiler/src/beam_asm.erl b/lib/compiler/src/beam_asm.erl index 9a81537006..f6ca7a0afb 100644 --- a/lib/compiler/src/beam_asm.erl +++ b/lib/compiler/src/beam_asm.erl @@ -230,9 +230,7 @@ build_attributes(Opts, SourceFile, Attr, MD5) -> [_|_] -> [{source,SourceFile}] end, Misc = case member(slim, Opts) of - false -> - {{Y,Mo,D},{H,Mi,S}} = erlang:universaltime(), - [{time,{Y,Mo,D,H,Mi,S}}|Misc0]; + false -> Misc0; true -> [] end, Compile = [{options,Opts},{version,?COMPILER_VSN}|Misc], diff --git a/lib/compiler/src/cerl.erl b/lib/compiler/src/cerl.erl index d033050d3c..37ec4e97c9 100644 --- a/lib/compiler/src/cerl.erl +++ b/lib/compiler/src/cerl.erl @@ -126,6 +126,7 @@ %% keep map exports here for now c_map_pattern/1, is_c_map/1, + is_c_map_pattern/1, map_es/1, map_arg/1, update_c_map/3, @@ -1636,6 +1637,11 @@ is_c_map_empty(#c_map{ es=[] }) -> true; is_c_map_empty(#c_literal{val=M}) when is_map(M),map_size(M) =:= 0 -> true; is_c_map_empty(_) -> false. +-spec is_c_map_pattern(c_map()) -> boolean(). + +is_c_map_pattern(#c_map{is_pat=IsPat}) -> + IsPat. + -spec ann_c_map([term()], [c_map_pair()]) -> c_map() | c_literal(). ann_c_map(As, Es) -> diff --git a/lib/compiler/src/cerl_trees.erl b/lib/compiler/src/cerl_trees.erl index 6d38748964..b3decbec1f 100644 --- a/lib/compiler/src/cerl_trees.erl +++ b/lib/compiler/src/cerl_trees.erl @@ -61,6 +61,7 @@ map_arg/1, map_es/1, ann_c_map/3, update_c_map/3, + is_c_map_pattern/1, ann_c_map_pattern/2, map_pair_key/1,map_pair_val/1,map_pair_op/1, ann_c_map_pair/4, update_c_map_pair/4 @@ -752,10 +753,17 @@ label(T, N, Env) -> {As, N2} = label_ann(T, N1), {ann_c_tuple_skel(As, Ts), N2}; map -> - {M, N1} = label(map_arg(T), N, Env), - {Ts, N2} = label_list(map_es(T), N1, Env), - {As, N3} = label_ann(T, N2), - {ann_c_map(As, M, Ts), N3}; + case is_c_map_pattern(T) of + false -> + {M, N1} = label(map_arg(T), N, Env), + {Ts, N2} = label_list(map_es(T), N1, Env), + {As, N3} = label_ann(T, N2), + {ann_c_map(As, M, Ts), N3}; + true -> + {Ts, N1} = label_list(map_es(T), N, Env), + {As, N2} = label_ann(T, N1), + {ann_c_map_pattern(As, Ts), N2} + end; map_pair -> {Op, N1} = label(map_pair_op(T), N, Env), {Key, N2} = label(map_pair_key(T), N1, Env), diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index daf6521236..149086152a 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -1317,7 +1317,7 @@ generate_key(String) when is_list(String) -> encrypt({des3_cbc=Type,Key,IVec,BlockSize}, Bin0) -> Bin1 = case byte_size(Bin0) rem BlockSize of 0 -> Bin0; - N -> list_to_binary([Bin0,crypto:rand_bytes(BlockSize-N)]) + N -> list_to_binary([Bin0,crypto:strong_rand_bytes(BlockSize-N)]) end, Bin = crypto:block_encrypt(Type, Key, IVec, Bin1), TypeString = atom_to_list(Type), diff --git a/lib/compiler/src/core_parse.yrl b/lib/compiler/src/core_parse.yrl index 315324e906..8028aa99bb 100644 --- a/lib/compiler/src/core_parse.yrl +++ b/lib/compiler/src/core_parse.yrl @@ -47,12 +47,14 @@ receive_expr timeout try_expr sequence catch_expr variable clause clause_pattern -map_expr map_pairs map_pair map_pair_assoc map_pair_exact +map_expr anno_map_expr map_pairs anno_map_pair map_pair map_pair_assoc map_pair_exact map_pattern map_pair_patterns map_pair_pattern -annotation anno_fun anno_expression anno_expressions +annotation anno_atom anno_fun anno_expression anno_expressions anno_variable anno_variables anno_pattern anno_patterns anno_function_name +anno_literal +anno_segment anno_segment_pattern anno_clause anno_clauses. Terminals @@ -90,7 +92,7 @@ module_definition -> module_definition -> '(' 'module' atom module_export module_attribute module_defs 'end' '-|' annotation ')' : - #c_module{anno='$9',name=tok_val('$3'),exports='$4', + #c_module{anno='$9',name=#c_literal{val=tok_val('$3')},exports='$4', attrs='$5',defs='$6'}. module_export -> '[' ']' : []. @@ -99,7 +101,7 @@ module_export -> '[' exported_names ']' : '$2'. exported_names -> exported_name ',' exported_names : ['$1' | '$3']. exported_names -> exported_name : ['$1']. -exported_name -> function_name : '$1'. +exported_name -> anno_function_name : '$1'. module_attribute -> 'attributes' '[' ']' : []. module_attribute -> 'attributes' '[' attribute_list ']' : '$3'. @@ -107,8 +109,16 @@ module_attribute -> 'attributes' '[' attribute_list ']' : '$3'. attribute_list -> attribute ',' attribute_list : ['$1' | '$3']. attribute_list -> attribute : ['$1']. -attribute -> atom '=' literal : - {#c_literal{val=tok_val('$1')},'$3'}. +attribute -> anno_atom '=' anno_literal : + {'$1','$3'}. + +anno_atom -> atom : + cerl:c_atom(tok_val('$1')). +anno_atom -> '(' atom '-|' annotation ')' : + cerl:ann_c_atom('$4', tok_val('$2')). + +anno_literal -> literal : '$1'. +anno_literal -> '(' literal '-|' annotation ')' : cerl:set_ann('$2', '$4'). module_defs -> function_definitions : '$1'. @@ -186,7 +196,9 @@ tuple_pattern -> '{' anno_patterns '}' : c_tuple('$2'). map_pattern -> '~' '{' '}' '~' : c_map_pattern([]). map_pattern -> '~' '{' map_pair_patterns '}' '~' : - c_map_pattern(lists:sort('$3')). + c_map_pattern('$3'). +map_pattern -> '~' '{' map_pair_patterns '|' anno_map_expr '}' '~' : + ann_c_map_pattern('$5', '$3'). map_pair_patterns -> map_pair_pattern : ['$1']. map_pair_patterns -> map_pair_pattern ',' map_pair_patterns : ['$1' | '$3']. @@ -194,6 +206,9 @@ map_pair_patterns -> map_pair_pattern ',' map_pair_patterns : ['$1' | '$3']. map_pair_pattern -> anno_expression ':=' anno_pattern : #c_map_pair{op=#c_literal{val=exact}, key='$1',val='$3'}. +map_pair_pattern -> '(' anno_expression ':=' anno_pattern '-|' annotation ')' : + #c_map_pair{anno='$6',op=#c_literal{val=exact}, + key='$2',val='$4'}. cons_pattern -> '[' anno_pattern tail_pattern : c_cons('$2', '$3'). @@ -206,8 +221,12 @@ tail_pattern -> ',' anno_pattern tail_pattern : binary_pattern -> '#' '{' '}' '#' : #c_binary{segments=[]}. binary_pattern -> '#' '{' segment_patterns '}' '#' : #c_binary{segments='$3'}. -segment_patterns -> segment_pattern ',' segment_patterns : ['$1' | '$3']. -segment_patterns -> segment_pattern : ['$1']. +segment_patterns -> anno_segment_pattern ',' segment_patterns : ['$1' | '$3']. +segment_patterns -> anno_segment_pattern : ['$1']. + +anno_segment_pattern -> segment_pattern : '$1'. +anno_segment_pattern -> '(' segment_pattern '-|' annotation ')' : + cerl:set_ann('$2', '$4'). segment_pattern -> '#' '<' anno_pattern '>' '(' anno_expressions ')': case '$6' of @@ -289,11 +308,17 @@ tuple -> '{' anno_expressions '}' : c_tuple('$2'). map_expr -> '~' '{' '}' '~' : c_map([]). map_expr -> '~' '{' map_pairs '}' '~' : c_map('$3'). -map_expr -> '~' '{' map_pairs '|' variable '}' '~' : ann_c_map([], '$5', '$3'). -map_expr -> '~' '{' map_pairs '|' map_expr '}' '~' : ann_c_map([], '$5', '$3'). +map_expr -> '~' '{' map_pairs '|' anno_variable '}' '~' : ann_c_map([], '$5', '$3'). +map_expr -> '~' '{' map_pairs '|' anno_map_expr '}' '~' : ann_c_map([], '$5', '$3'). -map_pairs -> map_pair : ['$1']. -map_pairs -> map_pair ',' map_pairs : ['$1' | '$3']. +anno_map_expr -> map_expr : '$1'. +anno_map_expr -> '(' map_expr '-|' annotation ')' : cerl:set_ann('$2', '$4'). + +map_pairs -> anno_map_pair : ['$1']. +map_pairs -> anno_map_pair ',' map_pairs : ['$1' | '$3']. + +anno_map_pair -> map_pair : '$1'. +anno_map_pair -> '(' map_pair '-|' annotation ')' : cerl:set_ann('$2', '$4'). map_pair -> map_pair_assoc : '$1'. map_pair -> map_pair_exact : '$1'. @@ -312,8 +337,11 @@ tail -> ',' anno_expression tail : c_cons('$2', '$3'). binary -> '#' '{' '}' '#' : #c_literal{val = <<>>}. binary -> '#' '{' segments '}' '#' : make_binary('$3'). -segments -> segment ',' segments : ['$1' | '$3']. -segments -> segment : ['$1']. +segments -> anno_segment ',' segments : ['$1' | '$3']. +segments -> anno_segment : ['$1']. + +anno_segment -> segment : '$1'. +anno_segment -> '(' segment '-|' annotation ')' : cerl:set_ann('$2', '$4'). segment -> '#' '<' anno_expression '>' '(' anno_expressions ')': case '$6' of @@ -413,7 +441,8 @@ Erlang code. -include("core_parse.hrl"). --import(cerl, [ann_c_map/3,c_cons/2,c_map/1,c_map_pattern/1,c_tuple/1]). +-import(cerl, [ann_c_map/3,ann_c_map_pattern/2,c_cons/2,c_map/1, + c_map_pattern/1,c_tuple/1]). tok_val(T) -> element(3, T). tok_line(T) -> element(2, T). diff --git a/lib/compiler/src/core_pp.erl b/lib/compiler/src/core_pp.erl index 78a081e9ca..88275998be 100644 --- a/lib/compiler/src/core_pp.erl +++ b/lib/compiler/src/core_pp.erl @@ -21,7 +21,7 @@ -module(core_pp). --export([format/1]). +-export([format/1,format_all/1]). -include("core_parse.hrl"). @@ -38,20 +38,30 @@ item_indent = 2 :: integer(), body_indent = 4 :: integer(), tab_width = 8 :: non_neg_integer(), - line = 0 :: integer()}). + line = 0 :: integer(), + clean = true :: boolean()}). -spec format(cerl:cerl()) -> iolist(). format(Node) -> format(Node, #ctxt{}). -maybe_anno(Node, Fun, Ctxt) -> +-spec format_all(cerl:cerl()) -> iolist(). + +format_all(Node) -> + format(Node, #ctxt{clean=false}). + +maybe_anno(Node, Fun, #ctxt{clean=false}=Ctxt) -> As = cerl:get_ann(Node), - case get_line(As) of + maybe_anno(Node, Fun, Ctxt, As); +maybe_anno(Node, Fun, #ctxt{clean=true}=Ctxt) -> + As0 = cerl:get_ann(Node), + case get_line(As0) of none -> - maybe_anno(Node, Fun, Ctxt, As); + maybe_anno(Node, Fun, Ctxt, As0); Line -> - if Line > Ctxt#ctxt.line -> + As = strip_line(As0), + if Line > Ctxt#ctxt.line -> [io_lib:format("%% Line ~w",[Line]), nl_indent(Ctxt), maybe_anno(Node, Fun, Ctxt#ctxt{line = Line}, As) @@ -61,22 +71,22 @@ maybe_anno(Node, Fun, Ctxt) -> end end. -maybe_anno(Node, Fun, Ctxt, As) -> - case strip_line(As) of - [] -> - Fun(Node, Ctxt); - List -> - Ctxt1 = add_indent(Ctxt, 2), - Ctxt2 = add_indent(Ctxt1, 3), - ["( ", - Fun(Node, Ctxt1), - nl_indent(Ctxt1), - "-| ",format_anno(List, Ctxt2)," )" - ] - end. +maybe_anno(Node, Fun, Ctxt, []) -> + Fun(Node, Ctxt); +maybe_anno(Node, Fun, Ctxt, List) -> + Ctxt1 = add_indent(Ctxt, 2), + Ctxt2 = add_indent(Ctxt1, 3), + ["( ", + Fun(Node, Ctxt1), + nl_indent(Ctxt1), + "-| ",format_anno(List, Ctxt2)," )" + ]. format_anno([_|_]=List, Ctxt) -> [$[,format_anno_list(List, Ctxt),$]]; +format_anno({file,Name}, _Ctxt) -> + %% Optimization: Reduces file size considerably. + io_lib:format("{'file',~p}", [Name]); format_anno(Tuple, Ctxt) when is_tuple(Tuple) -> [${,format_anno_list(tuple_to_list(Tuple), Ctxt),$}]; format_anno(Val, Ctxt) when is_atom(Val) -> @@ -172,7 +182,8 @@ format_1(#c_tuple{es=Es}, Ctxt) -> format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), $} ]; -format_1(#c_map{arg=#c_literal{val=M},es=Es}, Ctxt) when is_map(M),map_size(M)=:=0 -> +format_1(#c_map{arg=#c_literal{anno=[],val=M},es=Es}, Ctxt) + when is_map(M), map_size(M) =:= 0 -> ["~{", format_hseq(Es, ",", add_indent(Ctxt, 1), fun format/2), "}~" @@ -195,9 +206,16 @@ format_1(#c_values{es=Es}, Ctxt) -> format_1(#c_alias{var=V,pat=P}, Ctxt) -> Txt = [format(V, Ctxt)|" = "], [Txt|format(P, add_indent(Ctxt, width(Txt, Ctxt)))]; -format_1(#c_let{vars=Vs0,arg=A,body=B}, Ctxt) -> - Vs = [cerl:set_ann(V, []) || V <- Vs0], - case is_simple_term(A) of +format_1(#c_let{anno=Anno0,vars=Vs0,arg=A0,body=B}, #ctxt{clean=Clean}=Ctxt) -> + {Vs,A,Anno} = case Clean of + false -> + {Vs0,A0,Anno0}; + true -> + {[cerl:set_ann(V, []) || V <- Vs0], + cerl:set_ann(A0, []), + []} + end, + case is_simple_term(A) andalso Anno =:= [] of false -> Ctxt1 = add_indent(Ctxt, Ctxt#ctxt.body_indent), ["let ", @@ -214,7 +232,7 @@ format_1(#c_let{vars=Vs0,arg=A,body=B}, Ctxt) -> ["let ", format_values(Vs, add_indent(Ctxt, 4)), " = ", - format(cerl:set_ann(A, []), Ctxt1), + format(A, Ctxt1), nl_indent(Ctxt), "in " | format(B, add_indent(Ctxt, 4)) @@ -362,7 +380,10 @@ format_values(Vs, Ctxt) -> format_hseq(Vs, ",", add_indent(Ctxt, 1), fun format/2), $>]. -format_bitstr(#c_bitstr{val=V,size=S,unit=U,type=T,flags=Fs}, Ctxt0) -> +format_bitstr(Node, Ctxt) -> + maybe_anno(Node, fun do_format_bitstr/2, Ctxt). + +do_format_bitstr(#c_bitstr{val=V,size=S,unit=U,type=T,flags=Fs}, Ctxt0) -> Vs = [S, U, T, Fs], Ctxt1 = add_indent(Ctxt0, 2), Val = format(V, Ctxt1), @@ -387,7 +408,7 @@ format_clause_1(#c_clause{pats=Ps,guard=G,body=B}, Ctxt) -> width(Ptxt, Ctxt) + 6))]; false -> [nl_indent(Ctxt2), "when ", - format_guard(G, add_indent(Ctxt2, 2))] + format_guard(G, add_indent(set_class(Ctxt2, expr), 2))] end++ " ->", nl_indent(Ctxt2) diff --git a/lib/compiler/src/v3_core.erl b/lib/compiler/src/v3_core.erl index de775097e3..3299149457 100644 --- a/lib/compiler/src/v3_core.erl +++ b/lib/compiler/src/v3_core.erl @@ -784,7 +784,7 @@ badmap_term(_Map, #core{in_guard=true}) -> %% since it is not user-visible. #c_literal{val=badmap}; badmap_term(Map, #core{in_guard=false}) -> - #c_tuple{es=[#c_literal{val=badmap},Map]}. + c_tuple([#c_literal{val=badmap},Map]). map_build_pairs(Map, Es0, Ann, St0) -> {Es,Pre,St1} = map_build_pairs_1(Es0, St0), diff --git a/lib/compiler/test/beam_block_SUITE.erl b/lib/compiler/test/beam_block_SUITE.erl index a9bbe31b59..81f8d10687 100644 --- a/lib/compiler/test/beam_block_SUITE.erl +++ b/lib/compiler/test/beam_block_SUITE.erl @@ -21,7 +21,11 @@ -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]). + get_map_elements/1,otp_7345/1]). + +%% The only test for the following functions is that +%% the code compiles and is accepted by beam_validator. +-export([encode_wildcards3/4,find_operands/4]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -64,3 +68,114 @@ get_map_elements([{Pred,Var}|Left], Map, Acc) -> end; get_map_elements([], _Map, Acc) -> Acc. + +%% The following code +%% +%% {get_list,{x,2},{x,0},{x,1}}. +%% {gc_bif,length,{f,0},1,[{x,0}],{x,0}}. +%% {move,{x,0},{x,1}}. +%% +%% was incorrectly optimized to +%% +%% {get_list,{x,2},{x,0},{y,0}}. +%% {gc_bif,length,{f,0},3,[{x,0}],{x,1}}. +%% +%% because beam_block:is_transparent({x,1}, +%% {gc_bif,length,{f,0},3,[{x,0}],{x,1}} +%% incorrectly returned true. + +-record(contextId,{cid,device_type,contextRef}). +-record(dpRef,{cid,tlli,ms_device_context_id}). +-record(qosProfileBssgp,{peak_bit_rate_msb, + peak_bit_rate_lsb, + t_a_precedence}). +-record(llUnitdataReq,{sapi, + l3_pdu_length, + pdu_life}). +-record(ptmsi,{value}). + +otp_7345(_Config) -> + #llUnitdataReq{l3_pdu_length=3,pdu_life=4} = + otp_7345(#contextId{}, 0, [[1,2,3],4,5]). + + +otp_7345(ObjRef, _RdEnv, Args) -> + Cid = ObjRef#contextId.cid, + _ = #dpRef{cid = Cid, + ms_device_context_id = cid_id, + tlli = #ptmsi{value = 0}}, + _ = #qosProfileBssgp{peak_bit_rate_msb = 0, + peak_bit_rate_lsb = 80, + t_a_precedence = 49}, + [Cpdu|_] = Args, + LlUnitdataReq = + #llUnitdataReq{sapi = 7, + l3_pdu_length = length(Cpdu), + pdu_life = + id(42) + div + 10}, + id(LlUnitdataReq). + +%%% +%%% The only test of the following code is that it compiles. +%%% + +%% Slightly simplifed from megaco_binary_term_id_gen. +%% beam_block failed to note that the {gc_bif,'-'...} instruction could +%% fail, and that therefore {y,0} need to be initialized. +%% {allocate,8,6}. +%% %% {init,{y,0}} needed here. +%% {get_list,{x,1},{x,6},{x,7}}. +%% {'catch',{y,7},{f,3}}. +%% {move,{x,4},{y,1}}. +%% {move,{x,3},{y,2}}. +%% {move,{x,2},{y,3}}. +%% {move,{x,5},{y,4}}. +%% {move,{x,7},{y,5}}. +%% {move,{x,6},{y,6}}. +%% {gc_bif,'-',{f,0},8,[{x,3},{x,6}],{x,0}}. +%% {move,{x,0},{y,0}}. + +encode_wildcards3([],[],_,_) -> []; +encode_wildcards3([Level|Levels],[BitsInLevel|BitsRest],LevelNo,TotSize) -> + case (catch ?MODULE:encode_wildcard(Level,BitsInLevel,TotSize-BitsInLevel, + length(Levels))) of + {'EXIT',{Reason,Info}} -> + exit({Reason,{LevelNo,Info}}); + + no_wildcard -> + encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel); + + {level,Wl} -> + [Wl| + encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel)]; + + {recursive,Wr} -> + [Wr] + end. + +%% Slightly simplified code from hipe_rtl_ssapre. +%% beam_block used to do the following incorrect optimization: +%% +%% {gc_bif,length,{f,0},1,[{x,0}],{x,3}}. +%% ^^^^^ Was {x,0} - changing to {x,3} is not safe. +%% {gc_bif,'+',{f,0},0,[{y,2},{integer,1}],{x,0}}. +%% ^^^ Only one register live +%% . . . +%% {call_last,4,{f,2},4}. %% beam_validator noted that {x,3} wasn't live. + +find_operands(Cfg,XsiGraph,[],_Count) -> + {Cfg,XsiGraph}; +find_operands(Cfg,XsiGraph,ActiveList,Count) -> + {NewCfg,TempActiveList}=?MODULE:find_operands_for_active_list(Cfg,XsiGraph, + ActiveList,[]), + NewActiveList=lists:reverse(TempActiveList), + [Count+1, length(NewActiveList), length(digraph:vertices(XsiGraph))], + find_operands(NewCfg,XsiGraph,NewActiveList,Count+1). + +%%% +%%% Common functions. +%%% + +id(I) -> I. diff --git a/lib/compiler/test/beam_utils_SUITE.erl b/lib/compiler/test/beam_utils_SUITE.erl index 70c00f163c..6353ed3242 100644 --- a/lib/compiler/test/beam_utils_SUITE.erl +++ b/lib/compiler/test/beam_utils_SUITE.erl @@ -23,7 +23,7 @@ init_per_group/2,end_per_group/2, apply_fun/1,apply_mf/1,bs_init/1,bs_save/1, is_not_killed/1,is_not_used_at/1, - select/1,y_catch/1]). + select/1,y_catch/1,otp_8949_b/1,liveopt/1]). -export([id/1]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -41,7 +41,9 @@ groups() -> is_not_killed, is_not_used_at, select, - y_catch + y_catch, + otp_8949_b, + liveopt ]}]. init_per_suite(Config) -> @@ -232,6 +234,40 @@ do_y_catch_1(<<_,_/binary>>, _) -> do_y_catch_2(_) -> {a,b,c}. +otp_8949_b(_Config) -> + self() ! something, + value = otp_8949_b([], false), + {'EXIT',_} = (catch otp_8949_b([], true)), + ok. + +%% Would cause an endless loop in beam_utils. +otp_8949_b(A, B) -> + Var = id(value), + if + A == [], B == false -> + ok + end, + receive + something -> + id(Var) + end. + +-record(alarmInfo, {type,cause,origin}). + +liveopt(_Config) -> + F = liveopt_fun(42, pebkac, user), + void = F(42, #alarmInfo{type=sctp,cause=pebkac,origin=user}), + ok. + +liveopt_fun(Peer, Cause, Origin) -> + fun(PeerNo, AlarmInfo) + when PeerNo == Peer andalso + AlarmInfo == #alarmInfo{type=sctp, + cause=Cause, + origin=Origin} -> + void + end. + %% The identity function. id(I) -> I. diff --git a/lib/compiler/test/bs_match_SUITE.erl b/lib/compiler/test/bs_match_SUITE.erl index c2d146a0b1..224abf6c29 100644 --- a/lib/compiler/test/bs_match_SUITE.erl +++ b/lib/compiler/test/bs_match_SUITE.erl @@ -24,11 +24,11 @@ -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, - fun_shadow/1,int_float/1,otp_5269/1,null_fields/1,wiger/1, + size_shadow/1,int_float/1,otp_5269/1,null_fields/1,wiger/1, bin_tail/1,save_restore/1, partitioned_bs_match/1,function_clause/1, unit/1,shared_sub_bins/1,bin_and_float/1, - dec_subidentifiers/1,skip_optional_tag/1, + dec_subidentifiers/1,skip_optional_tag/1,decode_integer/1, wfbm/1,degenerated_match/1,bs_sum/1,coverage/1, multiple_uses/1,zero_label/1,followed_by_catch/1, matching_meets_construction/1,simon/1,matching_and_andalso/1, @@ -38,7 +38,7 @@ no_partition/1,calling_a_binary/1,binary_in_map/1, match_string_opt/1,select_on_integer/1, map_and_binary/1,unsafe_branch_caching/1, - bad_literals/1,good_literals/1]). + bad_literals/1,good_literals/1,constant_propagation/1]). -export([coverage_id/1,coverage_external_ignore/2]). @@ -56,11 +56,11 @@ all() -> groups() -> [{p,[parallel], - [fun_shadow,int_float,otp_5269,null_fields,wiger, + [size_shadow,int_float,otp_5269,null_fields,wiger, bin_tail,save_restore, partitioned_bs_match,function_clause,unit, shared_sub_bins,bin_and_float,dec_subidentifiers, - skip_optional_tag,wfbm,degenerated_match,bs_sum, + skip_optional_tag,decode_integer,wfbm,degenerated_match,bs_sum, coverage,multiple_uses,zero_label,followed_by_catch, matching_meets_construction,simon, matching_and_andalso,otp_7188,otp_7233,otp_7240, @@ -69,7 +69,7 @@ groups() -> no_partition,calling_a_binary,binary_in_map, match_string_opt,select_on_integer, map_and_binary,unsafe_branch_caching, - bad_literals,good_literals]}]. + bad_literals,good_literals,constant_propagation]}]. init_per_suite(Config) -> @@ -91,33 +91,54 @@ init_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> end_per_testcase(Case, Config) when is_atom(Case), is_list(Config) -> ok. -fun_shadow(Config) when is_list(Config) -> - %% OTP-5270 - 7 = fun_shadow_1(), - 7 = fun_shadow_2(8), - 7 = fun_shadow_3(), - no = fun_shadow_4(8), +size_shadow(Config) when is_list(Config) -> + %% Originally OTP-5270. + 7 = size_shadow_1(), + 7 = size_shadow_2(8), + 7 = size_shadow_3(), + no = size_shadow_4(8), + Any = {any,term,goes}, + {2577,Any,-175,whatever} = + (size_shadow_5(Any, 12))(<<2577:12>>, -175, whatever), + {7777,Any,42,whatever} = + (size_shadow_6(Any, 13))(42, <<7777:13>>, whatever), + {<<45>>,<<>>} = size_shadow_7({int,1}, <<1:16,45>>), + {'EXIT',{function_clause,_}} = + (catch size_shadow_7({int,42}, <<1:16,45>>)), ok. -fun_shadow_1() -> +size_shadow_1() -> L = 8, F = fun(<<L:L,B:L>>) -> B end, F(<<16:8, 7:16>>). -fun_shadow_2(L) -> +size_shadow_2(L) -> F = fun(<<L:L,B:L>>) -> B end, F(<<16:8, 7:16>>). -fun_shadow_3() -> +size_shadow_3() -> L = 8, F = fun(<<L:L,B:L,L:L>>) -> B end, F(<<16:8, 7:16,16:16>>). -fun_shadow_4(L) -> +size_shadow_4(L) -> F = fun(<<L:L,B:L,L:L>>) -> B; (_) -> no end, F(<<16:8, 7:16,15:16>>). +size_shadow_5(X, Y) -> + fun (<< A:Y >>, Y, B) -> fum(A, X, Y, B) end. + +size_shadow_6(X, Y) -> + fun (Y, << A:Y >>, B) -> fum(A, X, Y, B) end. + +fum(A, B, C, D) -> + {A,B,C,D}. + +size_shadow_7({int,N}, <<N:16,B:N/binary,T/binary>>) -> + {B,T}. + + int_float(Config) when is_list(Config) -> %% OTP-5323 <<103133.0:64/float>> = <<103133:64/float>>, @@ -504,6 +525,23 @@ skip_optional_tag(<<Tag,RestTag/binary>>, <<Tag,Rest/binary>>) -> skip_optional_tag(RestTag, Rest); skip_optional_tag(_, _) -> missing. +decode_integer(_Config) -> + {10795,<<43>>,whatever} = decode_integer(1, <<42,43>>, whatever), + {-28909,<<19>>,whatever} = decode_integer(1, <<143,19>>, whatever), + ok. + +decode_integer(Len, <<B1:1,B2:7,Bs/binary>>, RemovedBytes) when B1 == 0 -> + Bin = <<_Skip:Len/unit:8, Buffer2/binary>> = <<B1:1,B2:7,Bs/binary>>, + Size = byte_size(Bin), + <<Int:Size/unit:8>> = Bin, + {Int,Buffer2,RemovedBytes}; +decode_integer(Len, <<B1:1,B2:7,Bs/binary>>, RemovedBytes) -> + Bin = <<_Skip:Len/unit:8,Buffer2/binary>> = <<B1:1,B2:7,Bs/binary>>, + Size = byte_size(Bin), + <<N:Size/unit:8>> = <<B2,Bs/binary>>, + Int = N - (1 bsl (8 * size(Bin) -1)), + {Int,Buffer2,RemovedBytes}. + -define(DATELEN, 16). wfbm(Config) when is_list(Config) -> @@ -1387,6 +1425,32 @@ good_literals(_Config) -> <<16#cafebeef:4/unit:8>> = id(<<16#cafebeef:32>>), ok. +constant_propagation(_Config) -> + <<5>> = constant_propagation_a(a, <<5>>), + {'EXIT',{{case_clause,b},_}} = (catch constant_propagation_a(b, <<5>>)), + 258 = constant_propagation_b(<<1,2>>), + F = constant_propagation_c(), + 259 = F(<<1,3>>), + ok. + +constant_propagation_a(X, Y) -> + case X of + a -> Y2 = 8 + end, + <<5:Y2>> = Y. + +constant_propagation_b(B) -> + Sz = 16, + <<X:Sz/integer>> = B, + X. + +constant_propagation_c() -> + Size = 16, + fun(Bin) -> + <<X:Size/integer>> = Bin, + X + end. + check(F, R) -> R = F(). diff --git a/lib/compiler/test/compilation_SUITE.erl b/lib/compiler/test/compilation_SUITE.erl index 6961e625fd..cd1bc099e9 100644 --- a/lib/compiler/test/compilation_SUITE.erl +++ b/lib/compiler/test/compilation_SUITE.erl @@ -19,11 +19,48 @@ %%% Purpose : Compiles various modules with tough code -module(compilation_SUITE). +-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1, + init_per_group/2,end_per_group/2, + beam_compiler_4/1, + beam_compiler_6/1, + beam_compiler_7/1, + beam_compiler_8/1, + beam_compiler_9/1, + beam_compiler_10/1, + beam_compiler_11/1, + compiler_1/1, + const_list_256/1, + convopts/1, + live_var/1, + on_load/1, + on_load_inline/1, + opt_crash/1, + otp_2330/1, + otp_2380/1, + otp_4790/1, + otp_5151/1, + otp_5235/1, + otp_5404/1, + otp_5436/1, + otp_5481/1, + otp_5553/1, + otp_5632/1, + otp_5714/1, + otp_5872/1, + otp_6121/1, + otp_7202/1, + otp_8949_a/1, + redundant_case/1, + self_compile/1, + self_compile_old_inliner/1, + split_cases/1, + string_table/1, + vsn_1/1, + vsn_2/1, + vsn_3/1]). -include_lib("common_test/include/ct.hrl"). --compile(export_all). - suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,10}}]. @@ -37,23 +74,18 @@ groups() -> [{vsn,[parallel],[vsn_1,vsn_2,vsn_3]}, {p,test_lib:parallel(), [compiler_1, - compiler_3,compiler_5,beam_compiler_1, - beam_compiler_2,beam_compiler_3,beam_compiler_4, - beam_compiler_5,beam_compiler_6,beam_compiler_7, + beam_compiler_4,beam_compiler_6,beam_compiler_7, beam_compiler_8,beam_compiler_9,beam_compiler_10, - beam_compiler_11,beam_compiler_12, - nested_tuples_in_case_expr,otp_2330,guards, - {group,vsn},otp_2380,otp_2141,otp_2173,otp_4790, - const_list_256,bin_syntax_1,bin_syntax_2, - bin_syntax_3,bin_syntax_4,bin_syntax_5,bin_syntax_6, - live_var,convopts, - catch_in_catch,redundant_case,long_string,otp_5076, - complex_guard,otp_5092,otp_5151,otp_5235,otp_5244, - trycatch_4,opt_crash,otp_5404,otp_5436,otp_5481, + beam_compiler_11, + otp_2330, + {group,vsn},otp_2380,otp_4790, + const_list_256,live_var,convopts, + redundant_case, + otp_5151,otp_5235, + opt_crash,otp_5404,otp_5436,otp_5481, otp_5553,otp_5632,otp_5714,otp_5872,otp_6121, - otp_6121a,otp_6121b,otp_7202,otp_7345,on_load, - string_table,otp_8949_a,otp_8949_b,split_cases, - beam_utils_liveopt]}]. + otp_7202,on_load,on_load_inline, + string_table,otp_8949_a,split_cases]}]. init_per_suite(Config) -> Config. @@ -70,85 +102,25 @@ end_per_group(_GroupName, Config) -> -define(comp(N), N(Config) when is_list(Config) -> try_it(N, Config)). --define(comp_fail(N), - N(Config) when is_list(Config) -> failure(N, Config)). - ?comp(compiler_1). -?comp(compiler_3). -?comp(compiler_4). -?comp(compiler_5). -?comp(beam_compiler_1). -?comp(beam_compiler_2). -?comp(beam_compiler_3). ?comp(beam_compiler_4). -?comp(beam_compiler_5). ?comp(beam_compiler_6). ?comp(beam_compiler_8). ?comp(beam_compiler_9). ?comp(beam_compiler_10). ?comp(beam_compiler_11). -?comp(beam_compiler_12). -?comp(beam_compiler_13). - -?comp(nested_tuples_in_case_expr). ?comp(otp_2330). ?comp(otp_2380). -?comp(otp_2141). -?comp(otp_2173). ?comp(otp_4790). ?comp(otp_5235). -?comp(otp_5244). - -?comp(guards). - -?comp(pattern_expr). - ?comp(const_list_256). -?comp(bin_syntax_1). -?comp(bin_syntax_2). -?comp(bin_syntax_3). -?comp(bin_syntax_4). - -?comp(bin_syntax_6). - -?comp(otp_5076). - -?comp(complex_guard). - -?comp(otp_5092). ?comp(otp_5151). -%%% By Per Gustafsson <[email protected]> - -bin_syntax_5(Config) when is_list(Config) -> - {<<45>>,<<>>} = split({int, 1}, <<1:16,45>>). - -split({int, N}, <<N:16,B:N/binary,T/binary>>) -> - {B,T}. - -%% This program works with the old version of the compiler -%% but, the core erlang that it produces have the same variable appearing -%% looks like this: -%% -%% split({int, N}, <<_core1:16, B:N/binary, T/binary>>) when _core1==N -%% -%% with my change it will look like this: -%% -%% split({int, N}, <<_core1:16, B:_core1/binary, T/binary>>) when _core1==N -%% -%% This means that everything worked fine as long as the pattern -%% matching order was left-to-right but on core erlang any order should be possible - ?comp(live_var). - -?comp(trycatch_4). - -?comp(catch_in_catch). - ?comp(opt_crash). ?comp(otp_5404). @@ -159,8 +131,6 @@ split({int, N}, <<N:16,B:N/binary,T/binary>>) -> ?comp(otp_5714). ?comp(otp_5872). ?comp(otp_6121). -?comp(otp_6121a). -?comp(otp_6121b). ?comp(convopts). ?comp(otp_7202). ?comp(on_load). @@ -193,91 +163,38 @@ redundant_case_1(3) -> d; redundant_case_1(4) -> d; redundant_case_1(_) -> d. -failure(Module, Conf) -> - Src = filename:join(proplists:get_value(data_dir, Conf), - atom_to_list(Module)), - Out = proplists:get_value(priv_dir, Conf), - io:format("Compiling: ~ts\n", [Src]), - CompRc = compile:file(Src, [{outdir,Out},return,time]), - io:format("Result: ~p\n",[CompRc]), - case CompRc of - error -> ok; - {error,Errors,_} -> check_errors(Errors); - _ -> ct:fail({no_error, CompRc}) - end, - ok. - -check_errors([{_,Eds}|T]) -> - check_error(Eds), - check_errors(T); -check_errors([]) -> ok. - -check_error([{_,Mod,Error}|T]) -> - check_error_1(Mod:format_error(Error)), - check_error(T); -check_error([{Mod,Error}|T]) -> - check_error_1(Mod:format_error(Error)), - check_error(T); -check_error([]) -> ok. - -check_error_1(Str0) -> - Str = lists:flatten(Str0), - io:format("~s\n", [Str]), - case Str of - "internal"++_=Str -> - ct:fail(internal_compiler_error); - _ -> - ok - end. - --define(TC(Body), tc(fun() -> Body end, ?LINE)). - try_it(Module, Conf) -> - %% Change 'false' to 'true' to start a new node for every module. - try_it(false, Module, Conf). - -try_it(StartNode, Module, Conf) -> - try_it(StartNode, Module, {minutes,10}, Conf). - -try_it(StartNode, Module, Timetrap, Conf) -> + Timetrap = {minutes,10}, OtherOpts = [], %Can be changed to [time] if needed Src = filename:join(proplists:get_value(data_dir, Conf), atom_to_list(Module)), Out = proplists:get_value(priv_dir,Conf), io:format("Compiling: ~s\n", [Src]), - CompRc0 = compile:file(Src, [clint,{outdir,Out},report, - bin_opt_info|OtherOpts]), + CompRc0 = compile:file(Src, [clint0,clint,{outdir,Out},report, + bin_opt_info|OtherOpts]), io:format("Result: ~p\n",[CompRc0]), {ok,_Mod} = CompRc0, - Node = case StartNode of - false -> - node(); - true -> - Pa = "-pa " ++ filename:dirname(code:which(?MODULE)), - {ok,Node0} = start_node(compiler, Pa), - Node0 - end, - - ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]), load_and_call(Out, Module), ct:timetrap(Timetrap), io:format("Compiling (without optimization): ~s\n", [Src]), CompRc1 = compile:file(Src, - [no_copt,no_postopt,{outdir,Out},report|OtherOpts]), + [no_copt,no_postopt, + {outdir,Out},report|OtherOpts]), io:format("Result: ~p\n",[CompRc1]), {ok,_Mod} = CompRc1, - ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]), + load_and_call(Out, Module), ct:timetrap(Timetrap), io:format("Compiling (with old inliner): ~s\n", [Src]), - CompRc2 = compile:file(Src, [{outdir,Out},report,bin_opt_info, - {inline,1000}|OtherOpts]), + CompRc2 = compile:file(Src, [clint, + {outdir,Out},report,bin_opt_info, + {inline,1000}|OtherOpts]), io:format("Result: ~p\n",[CompRc2]), {ok,_Mod} = CompRc2, - ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]), + load_and_call(Out, Module), ct:timetrap(Timetrap), io:format("Compiling (from assembly): ~s\n", [Src]), @@ -286,12 +203,8 @@ try_it(StartNode, Module, Timetrap, Conf) -> CompRc3 = compile:file(Asm, [from_asm,{outdir,Out},report|OtherOpts]), io:format("Result: ~p\n",[CompRc3]), {ok,_} = CompRc3, - ok = rpc:call(Node, ?MODULE, load_and_call, [Out, Module]), + load_and_call(Out, Module), - case StartNode of - false -> ok; - true -> test_server:stop_node(Node) - end, ok. load_and_call(Out, Module) -> @@ -319,24 +232,6 @@ load_and_call(Out, Module) -> ok. -tc(F, Line) -> - {Diff,Value} = timer:tc(erlang, apply, [F,[]]), - io:format("~p: ~p\n", [Line,Diff]), - Value. - -start_node(Name, Args) -> - case test_server:start_node(Name, slave, [{args, Args}]) of - {ok, Node} -> - {ok, Node}; - Error -> - ct:fail(Error) - end. - -from(H, [H | T]) -> T; -from(H, [_ | T]) -> from(H, T); -from(_, []) -> []. - - %% Test generation of 'vsn' attribute. vsn_1(Conf) when is_list(Conf) -> M = vsn_1, @@ -397,11 +292,6 @@ get_vsn(M) -> {vsn,V} = lists:keyfind(vsn, 1, M:module_info(attributes)), V. -long_string(Config) when is_list(Config) -> - %% The test must complete in one minute - it should be plenty of time. - try_it(false, long_string, {minutes,1}, Config), - ok. - compile_load(Module, Dir, Conf) -> Src = filename:join(Dir, atom_to_list(Module)), Out = proplists:get_value(priv_dir,Conf), @@ -465,6 +355,7 @@ compile_compiler(Files, OutDir, Version, InlineOpts) -> io:format("~ts", [code:which(compile)]), io:format("Compiling ~s into ~ts", [Version,OutDir]), Opts = [report, + clint0,clint, bin_opt_info, {outdir,OutDir}, {d,'COMPILER_VSN',"\""++Version++"\""}, @@ -480,10 +371,6 @@ compile_compiler(Files, OutDir, Version, InlineOpts) -> compiler_src() -> filelib:wildcard(filename:join([code:lib_dir(compiler), "src", "*.erl"])). -compiler_modules(Dir) -> - Files = filelib:wildcard(filename:join(Dir, "*.beam")), - [list_to_atom(filename:rootname(filename:basename(F))) || F <- Files]. - make_compiler_dir(Priv, Dir0) -> Dir = filename:join(Priv, Dir0), ok = file:make_dir(Dir), @@ -502,112 +389,6 @@ compare_compilers(ADir, BDir) -> ["beam_asm.beam"] = [filename:basename(A) || {A,_} <- D], ok. -%%% -%%% The only test of the following code is that it compiles. -%%% - -%% Slightly simplifed from megaco_binary_term_id_gen. -%% beam_block failed to note that the {gc_bif,'-'...} instruction could -%% fail, and that therefore {y,0} need to be initialized. -%% {allocate,8,6}. -%% %% {init,{y,0}} needed here. -%% {get_list,{x,1},{x,6},{x,7}}. -%% {'catch',{y,7},{f,3}}. -%% {move,{x,4},{y,1}}. -%% {move,{x,3},{y,2}}. -%% {move,{x,2},{y,3}}. -%% {move,{x,5},{y,4}}. -%% {move,{x,7},{y,5}}. -%% {move,{x,6},{y,6}}. -%% {gc_bif,'-',{f,0},8,[{x,3},{x,6}],{x,0}}. -%% {move,{x,0},{y,0}}. - -encode_wildcards3([],[],_,_) -> []; -encode_wildcards3([Level|Levels],[BitsInLevel|BitsRest],LevelNo,TotSize) -> - case (catch ?MODULE:encode_wildcard(Level,BitsInLevel,TotSize-BitsInLevel, - length(Levels))) of - {'EXIT',{Reason,Info}} -> - exit({Reason,{LevelNo,Info}}); - - no_wildcard -> - encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel); - - {level,Wl} -> - [Wl| - encode_wildcards3(Levels,BitsRest,LevelNo+1,TotSize-BitsInLevel)]; - - {recursive,Wr} -> - [Wr] - end. - -%% Slightly simplified code from hipe_rtl_ssapre. -%% beam_block used to do the following incorrect optimization: -%% -%% {gc_bif,length,{f,0},1,[{x,0}],{x,3}}. -%% ^^^^^ Was {x,0} - changing to {x,3} is not safe. -%% {gc_bif,'+',{f,0},0,[{y,2},{integer,1}],{x,0}}. -%% ^^^ Only one register live -%% . . . -%% {call_last,4,{f,2},4}. %% beam_validator noted that {x,3} wasn't live. - -find_operands(Cfg,XsiGraph,[],_Count) -> - {Cfg,XsiGraph}; -find_operands(Cfg,XsiGraph,ActiveList,Count) -> - {NewCfg,TempActiveList}=?MODULE:find_operands_for_active_list(Cfg,XsiGraph, - ActiveList,[]), - NewActiveList=lists:reverse(TempActiveList), - [Count+1, length(NewActiveList), length(digraph:vertices(XsiGraph))], - find_operands(NewCfg,XsiGraph,NewActiveList,Count+1). - - -%% The following code -%% -%% {get_list,{x,2},{x,0},{x,1}}. -%% {gc_bif,length,{f,0},1,[{x,0}],{x,0}}. -%% {move,{x,0},{x,1}}. -%% -%% was incorrectly optimized to -%% -%% {get_list,{x,2},{x,0},{y,0}}. -%% {gc_bif,length,{f,0},3,[{x,0}],{x,1}}. -%% -%% because beam_block:is_transparent({x,1}, -%% {gc_bif,length,{f,0},3,[{x,0}],{x,1}} -%% incorrectly returned true. - --record(contextId,{cid,device_type,contextRef}). --record(dpRef,{cid,tlli,ms_device_context_id}). --record(qosProfileBssgp,{peak_bit_rate_msb, - peak_bit_rate_lsb, - t_a_precedence}). --record(llUnitdataReq,{sapi, - l3_pdu_length, - pdu_life}). --record(ptmsi,{value}). - -otp_7345(Config) when is_list(Config) -> - #llUnitdataReq{l3_pdu_length=3,pdu_life=4} = - otp_7345(#contextId{}, 0, [[1,2,3],4,5]). - - -otp_7345(ObjRef, _RdEnv, Args) -> - Cid = ObjRef#contextId.cid, - _ = #dpRef{cid = Cid, - ms_device_context_id = cid_id, - tlli = #ptmsi{value = 0}}, - _ = #qosProfileBssgp{peak_bit_rate_msb = 0, - peak_bit_rate_lsb = 80, - t_a_precedence = 49}, - [Cpdu|_] = Args, - LlUnitdataReq = - #llUnitdataReq{sapi = 7, - l3_pdu_length = length(Cpdu), - pdu_life = - id(42) - div - 10}, - id(LlUnitdataReq). - %% Check the generation of the string table. string_table(Config) when is_list(Config) -> @@ -640,24 +421,6 @@ do_otp_8949_a() -> end end. -otp_8949_b(Config) when is_list(Config) -> - self() ! something, - value = otp_8949_b([], false), - {'EXIT',_} = (catch otp_8949_b([], true)), - ok. - -%% Would cause an endless loop in beam_utils. -otp_8949_b(A, B) -> - Var = id(value), - if - A == [], B == false -> - ok - end, - receive - something -> - id(Var) - end. - split_cases(_) -> dummy1 = do_split_cases(x), {'EXIT',{{badmatch,b},_}} = (catch do_split_cases(y)), @@ -673,21 +436,5 @@ do_split_cases(A) -> end, Z. --record(alarmInfo, {type,cause,origin}). - -beam_utils_liveopt(Config) -> - F = beam_utils_liveopt_fun(42, pebkac, user), - void = F(42, #alarmInfo{type=sctp,cause=pebkac,origin=user}), - ok. - -beam_utils_liveopt_fun(Peer, Cause, Origin) -> - fun(PeerNo, AlarmInfo) - when PeerNo == Peer andalso - AlarmInfo == #alarmInfo{type=sctp, - cause=Cause, - origin=Origin} -> - void - end. - id(I) -> I. diff --git a/lib/compiler/test/compilation_SUITE_data/beam_compiler_1.erl b/lib/compiler/test/compilation_SUITE_data/beam_compiler_1.erl deleted file mode 100644 index 3e2891a28d..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/beam_compiler_1.erl +++ /dev/null @@ -1,32 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(beam_compiler_1). --export([beam_compiler_1/0]). - -beam_compiler_1() -> - ok. - --record(foo,{a,b}). - -try_me() -> - X = #foo{}, - Y = #foo{}, - {X#foo.a == Y#foo.a,X#foo.b}. - diff --git a/lib/compiler/test/compilation_SUITE_data/beam_compiler_12.erl b/lib/compiler/test/compilation_SUITE_data/beam_compiler_12.erl deleted file mode 100644 index d34291159a..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/beam_compiler_12.erl +++ /dev/null @@ -1,30 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-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(beam_compiler_12). - --export([?MODULE/0,t/1]). - -?MODULE() -> - ok. - -t(Name) -> - {ok = {file_info,_,regular,_,AccTime1,ModTime1,_,_,_,_,_,_,_,_}} = - prim_file:read_file_info(Name). - diff --git a/lib/compiler/test/compilation_SUITE_data/beam_compiler_2.erl b/lib/compiler/test/compilation_SUITE_data/beam_compiler_2.erl deleted file mode 100644 index 473529bb58..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/beam_compiler_2.erl +++ /dev/null @@ -1,36 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(beam_compiler_2). --export([beam_compiler_2/0]). - -beam_compiler_2() -> - ok. - --record(foo,{a,b}). - -try_me() -> - try_me({foo,x,z},{foo,y,z}). - -try_me(X,Y) -> - f(X#foo.a =/= Y#foo.a,X#foo.b =/= X#foo.b). - -f(A,B) -> - A. - diff --git a/lib/compiler/test/compilation_SUITE_data/beam_compiler_3.erl b/lib/compiler/test/compilation_SUITE_data/beam_compiler_3.erl deleted file mode 100644 index bdc4ec06e5..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/beam_compiler_3.erl +++ /dev/null @@ -1,30 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(beam_compiler_3). --export([beam_compiler_3/0, f/0]). - -%% From Ulf Wiger. - -beam_compiler_3() -> - ok. - -f() -> - [_|T] = lists:reverse("xxx"), - T. diff --git a/lib/compiler/test/compilation_SUITE_data/beam_compiler_5.erl b/lib/compiler/test/compilation_SUITE_data/beam_compiler_5.erl deleted file mode 100644 index 7289d2b553..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/beam_compiler_5.erl +++ /dev/null @@ -1,29 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(beam_compiler_5). --export([beam_compiler_5/0]). - --compile(export_all). - -beam_compiler_5() -> - ok. - -t() -> - [_|_] = x. diff --git a/lib/compiler/test/compilation_SUITE_data/bin_syntax_1.erl b/lib/compiler/test/compilation_SUITE_data/bin_syntax_1.erl deleted file mode 100644 index 72f6a0cee6..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/bin_syntax_1.erl +++ /dev/null @@ -1,32 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-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(bin_syntax_1). - --export([f/2,?MODULE/0]). - -?MODULE() -> - ok. - -f(X, Y) -> - case X of - a -> - Y2 = 8 - end, - <<5:Y2>> = Y. diff --git a/lib/compiler/test/compilation_SUITE_data/bin_syntax_2.erl b/lib/compiler/test/compilation_SUITE_data/bin_syntax_2.erl deleted file mode 100644 index cd0c2a4b0e..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/bin_syntax_2.erl +++ /dev/null @@ -1,42 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-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(bin_syntax_2). - --export([?MODULE/0]). - -%% This module tests that constant propagation is done properly. - -?MODULE() -> - 258 = b(<<1,2>>), - F = c(), - 259 = F(<<1,3>>), - ok. - -b(B) -> - Sz = 16, - <<X:Sz/integer>> = B, - X. - -c() -> - Size = 16, - fun(Bin) -> - <<X:Size/integer>> = Bin, - X - end. diff --git a/lib/compiler/test/compilation_SUITE_data/bin_syntax_3.erl b/lib/compiler/test/compilation_SUITE_data/bin_syntax_3.erl deleted file mode 100644 index b3118e0adc..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/bin_syntax_3.erl +++ /dev/null @@ -1,36 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-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(bin_syntax_3). --export([?MODULE/0,decode_integer/3]). - -?MODULE() -> - ok. - -decode_integer(Len, <<B1:1,B2:7,Bs/binary>>, RemovedBytes) when B1 == 0 -> - Bin = <<Skip:Len/unit:8, Buffer2/binary>> = <<B1:1,B2:7,Bs/binary>>, - Size = size(Bin), - <<Int:Size/unit:8>> = Bin, - {Int,Buffer2,RemovedBytes}; -decode_integer(Len,<<B1:1,B2:7,Bs/binary>>,RemovedBytes) -> - Bin = <<Skip:Len/unit:8,Buffer2/binary>> = <<B1:1,B2:7,Bs/binary>>, - Size = size(Bin), - <<N:Size/unit:8>> = <<B2,Bs/binary>>, - Int = N - (1 bsl (8 * size(Bin) -1)), - {Int,Buffer2,RemovedBytes}. diff --git a/lib/compiler/test/compilation_SUITE_data/bin_syntax_4.erl b/lib/compiler/test/compilation_SUITE_data/bin_syntax_4.erl deleted file mode 100644 index 185d6ec3b0..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/bin_syntax_4.erl +++ /dev/null @@ -1,33 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2000-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(bin_syntax_4). --export([?MODULE/0,f4b/2,f4c/2]). - -?MODULE() -> - ok. - -f4b(X, Y) -> - fun (<< A:Y >>, Y, B) -> fum(A, X, Y, B) end. - -f4c(X, Y) -> - fun (Y, << A:Y >>, B) -> fum(A, X, Y, B) end. - -fum(A, B, C, D) -> - {A,B,C,D}. diff --git a/lib/compiler/test/compilation_SUITE_data/bin_syntax_6.erl b/lib/compiler/test/compilation_SUITE_data/bin_syntax_6.erl deleted file mode 100644 index 1841a2ee0a..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/bin_syntax_6.erl +++ /dev/null @@ -1,40 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-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(bin_syntax_6). --export([?MODULE/0,x/1,y/1]). - -?MODULE() -> - ok. - -x(X) -> - blurf(), - B = {X,"OK",<<>>}, - catch b({a,B}). - -y(X) -> - blurf(), - B = {X,"OK",<<42>>}, - catch b({a,B}). - -blurf() -> - ok. - -b(_) -> - ok. diff --git a/lib/compiler/test/compilation_SUITE_data/catch_in_catch.erl b/lib/compiler/test/compilation_SUITE_data/catch_in_catch.erl deleted file mode 100644 index 05ef45f105..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/catch_in_catch.erl +++ /dev/null @@ -1,52 +0,0 @@ -%% -%% %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(catch_in_catch). - --export([?MODULE/0,do_start/1]). - -?MODULE() -> - process_flag(trap_exit, true), - Pid = spawn_link(?MODULE, do_start, [x]), - receive - {'EXIT',Pid,good_exit} -> ok; - Other -> - io:format("Unexpected: ~p\n", [Other]), - error - after 32000 -> - io:format("No message received\n"), - error - end. - -do_start(Param) -> - init(Param), - exit(good_exit). - -init(Param) -> - process_flag(trap_exit, true), - %% The catches were improperly nested, causing a "No catch found" crash. - (catch begin - foo(Param), - (catch exit(bar)) - end - ), - ignore. - -foo(_) -> - ok. diff --git a/lib/compiler/test/compilation_SUITE_data/compiler_3.erl b/lib/compiler/test/compilation_SUITE_data/compiler_3.erl deleted file mode 100644 index 698a0f26f3..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/compiler_3.erl +++ /dev/null @@ -1,34 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(compiler_3). --export([compiler_3/0]). --record(rec,{a}). - -compiler_3() -> - guard_record(). - -guard_record() -> - 1=func(#rec{}), - {'EXIT',_} = (catch func({rec})), - ok. - -func(X) when record(X, -rec) -> - 1. diff --git a/lib/compiler/test/compilation_SUITE_data/compiler_5.erl b/lib/compiler/test/compilation_SUITE_data/compiler_5.erl deleted file mode 100644 index de3c2ec4ce..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/compiler_5.erl +++ /dev/null @@ -1,50 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(compiler_5). --export([compiler_5/0]). - -compiler_5() -> - f0(), - f1(), - f2(), - ok. - -%% compiler treats records with 1 and 2 fields differently... --record(nil, {}). --record(foo, {hello=1}). --record(bar, {hello=2,there=3}). - -f0() -> - R1 = #nil{}, - R2 = R1#nil{}, %% stupid code, but compiler shouldn't crash - R1 = R2, - ok. - -f1() -> - R1 = #foo{}, - R2 = R1#foo{}, %% stupid code, but compiler shouldn't crash - R1 = R2, - ok. - -f2() -> - R1 = #bar{}, - R2 = R1#bar{}, %% stupid code, but compiler shouldn't crash - R1 = R2, - ok. diff --git a/lib/compiler/test/compilation_SUITE_data/complex_guard.erl b/lib/compiler/test/compilation_SUITE_data/complex_guard.erl deleted file mode 100644 index f1b88c2bd2..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/complex_guard.erl +++ /dev/null @@ -1,32 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-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(complex_guard). - --compile(export_all). - -?MODULE() -> - ok. - -f(X1,Y1,Z1) -> - if - ((X1 =:= 4) or (X1 =:= 5)) and ((Y1 =:= 4) or (Y1 =:= 5)) and ((Z1 =:= 4) or (Z1 =:= 5)) or ((X1 =:= 1) or (X1 =:= 2) or (X1 =:= 3)) and ((Y1 =:= 1) or (Y1 =:= 2) or (Y1 =:= 3)) and ((Z1 =:= 1) or (Z1 =:= 2) or (Z1 =:= 3)) -> - true - end. - diff --git a/lib/compiler/test/compilation_SUITE_data/guards.erl b/lib/compiler/test/compilation_SUITE_data/guards.erl deleted file mode 100644 index a507add790..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/guards.erl +++ /dev/null @@ -1,107 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-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(guards). - --export([guards/0]). - -guards() -> - ok = t(), - ok = f(), - ok = ct(1), - ok = multi(1), - ok = multi(2), - ok = multi(3). - -%% The following tests are always true. -t() when integer(42) -> - ok; -t() when float(2.0) -> - ok; -t() when number(7) -> - ok; -t() when number(3.14) -> - ok; -t() when atom(error) -> - ok; -t() when list([a]) -> - ok; -t() when tuple({}) -> - ok; -t() when tuple({1, 2}) -> - ok. - -%% The following tests are always false. -f() when integer(a) -> - ok; -f() when float(b) -> - ok; -f() when number(c) -> - ok; -f() when atom(42) -> - ok; -f() when list(33) -> - ok; -f() when list({}) -> - ok; -f() when list({1, 2}) -> - ok; -f() when tuple(33) -> - ok; -f() when tuple([a]) -> - ok; -f() when tuple([]) -> - ok; -f() when tuple(35) -> - ok; -f() -> - ok. - -%% The following tests are always true. -ct(X) -> - case X of - Y when integer(42) -> - ok; - Y when float(2.0) -> - ok; - Y when number(7) -> - ok; - Y when number(3.14) -> - ok; - Y when atom(error) -> - ok; - Y when list([a]) -> - ok; - Y when tuple({}) -> - ok; - Y when tuple({1, 2}) -> - ok - end. - -multi(X) -> - case X of - Y when float(Y) ; integer(Y) -> - ok; - Y when Y > 1, Y < 10 ; atom(Y) -> - ok; - Y when Y == 4, number(Y) ; list(Y) -> - pannkaka; - Y when Y==3 ; Y==5 ; Y==6 -> - ok - end. diff --git a/lib/compiler/test/compilation_SUITE_data/long_string.erl b/lib/compiler/test/compilation_SUITE_data/long_string.erl deleted file mode 100644 index fe109c8e4f..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/long_string.erl +++ /dev/null @@ -1,671 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-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(long_string). - --export([?MODULE/0]). - -?MODULE() -> - Options = "some stupid long string", - 49252 = length(generate(Options, "348927432097sfkjfkljf329")), - ok. - -generate(Options, Glurf) -> - "asdhfaslfdjhhwleirsk e4kjhr430usduy fdk;///s llsjkf;laskjfsdfkjasdfkj -sdkljflasdfkjasldkfjasd" ++ Options ++ -"CSAgICAgICBWZXJzaW9uIDIsIEp1bmUgMTk5MQoKIENvcHlyaWdodCAoQykgMTk4OSwgMTk5MSBG -cmVlIFNvZnR3YXJlIEZvdW5kYXRpb24sIEluYy4KICAgICAgICAgICAgICAgICAgICAgICA1OSBU -ZW1wbGUgUGxhY2UsIFN1aXRlIDMzMCwgQm9zdG9uLCBNQSAgMDIxMTEtMTMwNyAgVVNBCiBFdmVy -eW9uZSBpcyBwZXJtaXR0ZWQgdG8gY29weSBhbmQgZGlzdHJpYnV0ZSB2ZXJiYXRpbSBjb3BpZXMK -IG9mIHRoaXMgbGljZW5zZSBkb2N1bWVudCwgYnV0IGNoYW5naW5nIGl0IGlzIG5vdCBhbGxvd2Vk -LgoKCQkJICAgIFByZWFtYmxlCgogIFRoZSBsaWNlbnNlcyBmb3IgbW9zdCBzb2Z0d2FyZSBhcmUg -ZGVzaWduZWQgdG8gdGFrZSBhd2F5IHlvdXIKZnJlZWRvbSB0byBzaGFyZSBhbmQgY2hhbmdlIGl0 -LiAgQnkgY29udHJhc3QsIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMKTGljZW5zZSBpcyBpbnRlbmRl -ZCB0byBndWFyYW50ZWUgeW91ciBmcmVlZG9tIHRvIHNoYXJlIGFuZCBjaGFuZ2UgZnJlZQpzb2Z0 -d2FyZS0tdG8gbWFrZSBzdXJlIHRoZSBzb2Z0d2FyZSBpcyBmcmVlIGZvciBhbGwgaXRzIHVzZXJz -LiAgVGhpcwpHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFwcGxpZXMgdG8gbW9zdCBvZiB0aGUgRnJl -ZSBTb2Z0d2FyZQpGb3VuZGF0aW9uJ3Mgc29mdHdhcmUgYW5kIHRvIGFueSBvdGhlciBwcm9ncmFt -IHdob3NlIGF1dGhvcnMgY29tbWl0IHRvCnVzaW5nIGl0LiAgKFNvbWUgb3RoZXIgRnJlZSBTb2Z0 -d2FyZSBGb3VuZGF0aW9uIHNvZnR3YXJlIGlzIGNvdmVyZWQgYnkKdGhlIEdOVSBMaWJyYXJ5IEdl -bmVyYWwgUHVibGljIExpY2Vuc2UgaW5zdGVhZC4pICBZb3UgY2FuIGFwcGx5IGl0IHRvCnlvdXIg -cHJvZ3JhbXMsIHRvby4KCiAgV2hlbiB3ZSBzcGVhayBvZiBmcmVlIHNvZnR3YXJlLCB3ZSBhcmUg -cmVmZXJyaW5nIHRvIGZyZWVkb20sIG5vdApwcmljZS4gIE91ciBHZW5lcmFsIFB1YmxpYyBMaWNl -bnNlcyBhcmUgZGVzaWduZWQgdG8gbWFrZSBzdXJlIHRoYXQgeW91CmhhdmUgdGhlIGZyZWVkb20g -dG8gZGlzdHJpYnV0ZSBjb3BpZXMgb2YgZnJlZSBzb2Z0d2FyZSAoYW5kIGNoYXJnZSBmb3IKdGhp -cyBzZXJ2aWNlIGlmIHlvdSB3aXNoKSwgdGhhdCB5b3UgcmVjZWl2ZSBzb3VyY2UgY29kZSBvciBj -YW4gZ2V0IGl0CmlmIHlvdSB3YW50IGl0LCB0aGF0IHlvdSBjYW4gY2hhbmdlIHRoZSBzb2Z0d2Fy -ZSBvciB1c2UgcGllY2VzIG9mIGl0CmluIG5ldyBmcmVlIHByb2dyYW1zOyBhbmQgdGhhdCB5b3Ug -a25vdyB5b3UgY2FuIGRvIHRoZXNlIHRoaW5ncy4KCiAgVG8gcHJvdGVjdCB5b3VyIHJpZ2h0cywg -d2UgbmVlZCB0byBtYWtlIHJlc3RyaWN0aW9ucyB0aGF0IGZvcmJpZAphbnlvbmUgdG8gZGVueSB5 -b3UgdGhlc2UgcmlnaHRzIG9yIHRvIGFzayB5b3UgdG8gc3VycmVuZGVyIHRoZSByaWdodHMuClRo -ZXNlIHJlc3RyaWN0aW9ucyB0cmFuc2xhdGUgdG8gY2VydGFpbiByZXNwb25zaWJpbGl0aWVzIGZv -ciB5b3UgaWYgeW91CmRpc3RyaWJ1dGUgY29waWVzIG9mIHRoZSBzb2Z0d2FyZSwgb3IgaWYgeW91 -IG1vZGlmeSBpdC4KCiAgRm9yIGV4YW1wbGUsIGlmIHlvdSBkaXN0cmlidXRlIGNvcGllcyBvZiBz -dWNoIGEgcHJvZ3JhbSwgd2hldGhlcgpncmF0aXMgb3IgZm9yIGEgZmVlLCB5b3UgbXVzdCBnaXZl -IHRoZSByZWNpcGllbnRzIGFsbCB0aGUgcmlnaHRzIHRoYXQKeW91IGhhdmUuICBZb3UgbXVzdCBt -YWtlIHN1cmUgdGhhdCB0aGV5LCB0b28sIHJlY2VpdmUgb3IgY2FuIGdldCB0aGUKc291cmNlIGNv -ZGUuICBBbmQgeW91IG11c3Qgc2hvdyB0aGVtIHRoZXNlIHRlcm1zIHNvIHRoZXkga25vdyB0aGVp -cgpyaWdodHMuCgogIFdlIHByb3RlY3QgeW91ciByaWdodHMgd2l0aCB0d28gc3RlcHM6ICgxKSBj -b3B5cmlnaHQgdGhlIHNvZnR3YXJlLCBhbmQKKDIpIG9mZmVyIHlvdSB0aGlzIGxpY2Vuc2Ugd2hp -Y2ggZ2l2ZXMgeW91IGxlZ2FsIHBlcm1pc3Npb24gdG8gY29weSwKZGlzdHJpYnV0ZSBhbmQvb3Ig -bW9kaWZ5IHRoZSBzb2Z0d2FyZS4KCiAgQWxzbywgZm9yIGVhY2ggYXV0aG9yJ3MgcHJvdGVjdGlv -biBhbmQgb3Vycywgd2Ugd2FudCB0byBtYWtlIGNlcnRhaW4KdGhhdCBldmVyeW9uZSB1bmRlcnN0 -YW5kcyB0aGF0IHRoZXJlIGlzIG5vIHdhcnJhbnR5IGZvciB0aGlzIGZyZWUKc29mdHdhcmUuICBJ -ZiB0aGUgc29mdHdhcmUgaXMgbW9kaWZpZWQgYnkgc29tZW9uZSBlbHNlIGFuZCBwYXNzZWQgb24s -IHdlCndhbnQgaXRzIHJlY2lwaWVudHMgdG8ga25vdyB0aGF0IHdoYXQgdGhleSBoYXZlIGlzIG5v -dCB0aGUgb3JpZ2luYWwsIHNvCnRoYXQgYW55IHByb2JsZW1zIGludHJvZHVjZWQgYnkgb3RoZXJz -IHdpbGwgbm90IHJlZmxlY3Qgb24gdGhlIG9yaWdpbmFsCmF1dGhvcnMnIHJlcHV0YXRpb25zLgoK -ICBGaW5hbGx5LCBhbnkgZnJlZSBwcm9ncmFtIGlzIHRocmVhdGVuZWQgY29uc3RhbnRseSBieSBz -b2Z0d2FyZQpwYXRlbnRzLiAgV2Ugd2lzaCB0byBhdm9pZCB0aGUgZGFuZ2VyIHRoYXQgcmVkaXN0 -cmlidXRvcnMgb2YgYSBmcmVlCnByb2dyYW0gd2lsbCBpbmRpdmlkdWFsbHkgb2J0YWluIHBhdGVu -dCBsaWNlbnNlcywgaW4gZWZmZWN0IG1ha2luZyB0aGUKcHJvZ3JhbSBwcm9wcmlldGFyeS4gIFRv -IHByZXZlbnQgdGhpcywgd2UgaGF2ZSBtYWRlIGl0IGNsZWFyIHRoYXQgYW55CnBhdGVudCBtdXN0 -IGJlIGxpY2Vuc2VkIGZvciBldmVyeW9uZSdzIGZyZWUgdXNlIG9yIG5vdCBsaWNlbnNlZCBhdCBh -bGwuCgogIFRoZSBwcmVjaXNlIHRlcm1zIGFuZCBjb25kaXRpb25zIGZvciBjb3B5aW5nLCBkaXN0 -cmlidXRpb24gYW5kCm1vZGlmaWNhdGlvbiBmb2xsb3cuCgwKCQkgICAgR05VIEdFTkVSQUwgUFVC -TElDIExJQ0VOU0UKICAgVEVSTVMgQU5EIENPTkRJVElPTlMgRk9SIENPUFlJTkcsIERJU1RSSUJV -VElPTiBBTkQgTU9ESUZJQ0FUSU9OCgogIDAuIFRoaXMgTGljZW5zZSBhcHBsaWVzIHRvIGFueSBw -cm9ncmFtIG9yIG90aGVyIHdvcmsgd2hpY2ggY29udGFpbnMKYSBub3RpY2UgcGxhY2VkIGJ5IHRo -ZSBjb3B5cmlnaHQgaG9sZGVyIHNheWluZyBpdCBtYXkgYmUgZGlzdHJpYnV0ZWQKdW5kZXIgdGhl -IHRlcm1zIG9mIHRoaXMgR2VuZXJhbCBQdWJsaWMgTGljZW5zZS4gIFRoZSAiUHJvZ3JhbSIsIGJl -bG93LApyZWZlcnMgdG8gYW55IHN1Y2ggcHJvZ3JhbSBvciB3b3JrLCBhbmQgYSAid29yayBiYXNl -ZCBvbiB0aGUgUHJvZ3JhbSIKbWVhbnMgZWl0aGVyIHRoZSBQcm9ncmFtIG9yIGFueSBkZXJpdmF0 -aXZlIHdvcmsgdW5kZXIgY29weXJpZ2h0IGxhdzoKdGhhdCBpcyB0byBzYXksIGEgd29yayBjb250 -YWluaW5nIHRoZSBQcm9ncmFtIG9yIGEgcG9ydGlvbiBvZiBpdCwKZWl0aGVyIHZlcmJhdGltIG9y -IHdpdGggbW9kaWZpY2F0aW9ucyBhbmQvb3IgdHJhbnNsYXRlZCBpbnRvIGFub3RoZXIKbGFuZ3Vh -Z2UuICAoSGVyZWluYWZ0ZXIsIHRyYW5zbGF0aW9uIGlzIGluY2x1ZGVkIHdpdGhvdXQgbGltaXRh -dGlvbiBpbgp0aGUgdGVybSAibW9kaWZpY2F0aW9uIi4pICBFYWNoIGxpY2Vuc2VlIGlzIGFkZHJl -c3NlZCBhcyAieW91Ii4KCkFjdGl2aXRpZXMgb3RoZXIgdGhhbiBjb3B5aW5nLCBkaXN0cmlidXRp -b24gYW5kIG1vZGlmaWNhdGlvbiBhcmUgbm90CmNvdmVyZWQgYnkgdGhpcyBMaWNlbnNlOyB0aGV5 -IGFyZSBvdXRzaWRlIGl0cyBzY29wZS4gIFRoZSBhY3Qgb2YKcnVubmluZyB0aGUgUHJvZ3JhbSBp -cyBub3QgcmVzdHJpY3RlZCwgYW5kIHRoZSBvdXRwdXQgZnJvbSB0aGUgUHJvZ3JhbQppcyBjb3Zl -cmVkIG9ubHkgaWYgaXRzIGNvbnRlbnRzIGNvbnN0aXR1dGUgYSB3b3JrIGJhc2VkIG9uIHRoZQpQ -cm9ncmFtIChpbmRlcGVuZGVudCBvZiBoYXZpbmcgYmVlbiBtYWRlIGJ5IHJ1bm5pbmcgdGhlIFBy -b2dyYW0pLgpXaGV0aGVyIHRoYXQgaXMgdHJ1ZSBkZXBlbmRzIG9uIHdoYXQgdGhlIFByb2dyYW0g -ZG9lcy4KCiAgMS4gWW91IG1heSBjb3B5IGFuZCBkaXN0cmlidXRlIHZlcmJhdGltIGNvcGllcyBv -ZiB0aGUgUHJvZ3JhbSdzCnNvdXJjZSBjb2RlIGFzIHlvdSByZWNlaXZlIGl0LCBpbiBhbnkgbWVk -aXVtLCBwcm92aWRlZCB0aGF0IHlvdQpjb25zcGljdW91c2x5IGFuZCBhcHByb3ByaWF0ZWx5IHB1 -Ymxpc2ggb24gZWFjaCBjb3B5IGFuIGFwcHJvcHJpYXRlCmNvcHlyaWdodCBub3RpY2UgYW5kIGRp -c2NsYWltZXIgb2Ygd2FycmFudHk7IGtlZXAgaW50YWN0IGFsbCB0aGUKbm90aWNlcyB0aGF0IHJl -ZmVyIHRvIHRoaXMgTGljZW5zZSBhbmQgdG8gdGhlIGFic2VuY2Ugb2YgYW55IHdhcnJhbnR5Owph -bmQgZ2l2ZSBhbnkgb3RoZXIgcmVjaXBpZW50cyBvZiB0aGUgUHJvZ3JhbSBhIGNvcHkgb2YgdGhp -cyBMaWNlbnNlCmFsb25nIHdpdGggdGhlIFByb2dyYW0uCgpZb3UgbWF5IGNoYXJnZSBhIGZlZSBm -b3IgdGhlIHBoeXNpY2FsIGFjdCBvZiB0cmFuc2ZlcnJpbmcgYSBjb3B5LCBhbmQKeW91IG1heSBh -dCB5b3VyIG9wdGlvbiBvZmZlciB3YXJyYW50eSBwcm90ZWN0aW9uIGluIGV4Y2hhbmdlIGZvciBh -IGZlZS4KCiAgMi4gWW91IG1heSBtb2RpZnkgeW91ciBjb3B5IG9yIGNvcGllcyBvZiB0aGUgUHJv -Z3JhbSBvciBhbnkgcG9ydGlvbgpvZiBpdCwgdGh1cyBmb3JtaW5nIGEgd29yayBiYXNlZCBvbiB0 -aGUgUHJvZ3JhbSwgYW5kIGNvcHkgYW5kCmRpc3RyaWJ1dGUgc3VjaCBtb2RpZmljYXRpb25zIG9y -IHdvcmsgdW5kZXIgdGhlIHRlcm1zIG9mIFNlY3Rpb24gMQphYm92ZSwgcHJvdmlkZWQgdGhhdCB5 -b3UgYWxzbyBtZWV0IGFsbCBvZiB0aGVzZSBjb25kaXRpb25zOgoKICAgIGEpIFlvdSBtdXN0IGNh -dXNlIHRoZSBtb2RpZmllZCBmaWxlcyB0byBjYXJyeSBwcm9taW5lbnQgbm90aWNlcwogICAgc3Rh -dGluZyB0aGF0IHlvdSBjaGFuZ2VkIHRoZSBmaWxlcyBhbmQgdGhlIGRhdGUgb2YgYW55IGNoYW5n -ZS4KCiAgICBiKSBZb3UgbXVzdCBjYXVzZSBhbnkgd29yayB0aGF0IHlvdSBkaXN0cmlidXRlIG9y -IHB1Ymxpc2gsIHRoYXQgaW4KICAgIHdob2xlIG9yIGluIHBhcnQgY29udGFpbnMgb3IgaXMgZGVy -aXZlZCBmcm9tIHRoZSBQcm9ncmFtIG9yIGFueQogICAgcGFydCB0aGVyZW9mLCB0byBiZSBsaWNl -bnNlZCBhcyBhIHdob2xlIGF0IG5vIGNoYXJnZSB0byBhbGwgdGhpcmQKICAgIHBhcnRpZXMgdW5k -ZXIgdGhlIHRlcm1zIG9mIHRoaXMgTGljZW5zZS4KCiAgICBjKSBJZiB0aGUgbW9kaWZpZWQgcHJv -Z3JhbSBub3JtYWxseSByZWFkcyBjb21tYW5kcyBpbnRlcmFjdGl2ZWx5CiAgICB3aGVuIHJ1biwg -eW91IG11c3QgY2F1c2UgaXQsIHdoZW4gc3RhcnRlZCBydW5uaW5nIGZvciBzdWNoCiAgICBpbnRl -cmFjdGl2ZSB1c2UgaW4gdGhlIG1vc3Qgb3JkaW5hcnkgd2F5LCB0byBwcmludCBvciBkaXNwbGF5 -IGFuCiAgICBhbm5vdW5jZW1lbnQgaW5jbHVkaW5nIGFuIGFwcHJvcHJpYXRlIGNvcHlyaWdodCBu -b3RpY2UgYW5kIGEKICAgIG5vdGljZSB0aGF0IHRoZXJlIGlzIG5vIHdhcnJhbnR5IChvciBlbHNl -LCBzYXlpbmcgdGhhdCB5b3UgcHJvdmlkZQogICAgYSB3YXJyYW50eSkgYW5kIHRoYXQgdXNlcnMg -bWF5IHJlZGlzdHJpYnV0ZSB0aGUgcHJvZ3JhbSB1bmRlcgogICAgdGhlc2UgY29uZGl0aW9ucywg -YW5kIHRlbGxpbmcgdGhlIHVzZXIgaG93IHRvIHZpZXcgYSBjb3B5IG9mIHRoaXMKICAgIExpY2Vu -c2UuICAoRXhjZXB0aW9uOiBpZiB0aGUgUHJvZ3JhbSBpdHNlbGYgaXMgaW50ZXJhY3RpdmUgYnV0 -CiAgICBkb2VzIG5vdCBub3JtYWxseSBwcmludCBzdWNoIGFuIGFubm91bmNlbWVudCwgeW91ciB3 -b3JrIGJhc2VkIG9uCiAgICB0aGUgUHJvZ3JhbSBpcyBub3QgcmVxdWlyZWQgdG8gcHJpbnQgYW4g -YW5ub3VuY2VtZW50LikKDApUaGVzZSByZXF1aXJlbWVudHMgYXBwbHkgdG8gdGhlIG1vZGlmaWVk -IHdvcmsgYXMgYSB3aG9sZS4gIElmCmlkZW50aWZpYWJsZSBzZWN0aW9ucyBvZiB0aGF0IHdvcmsg -YXJlIG5vdCBkZXJpdmVkIGZyb20gdGhlIFByb2dyYW0sCmFuZCBjYW4gYmUgcmVhc29uYWJseSBj -b25zaWRlcmVkIGluZGVwZW5kZW50IGFuZCBzZXBhcmF0ZSB3b3JrcyBpbgp0aGVtc2VsdmVzLCB0 -aGVuIHRoaXMgTGljZW5zZSwgYW5kIGl0cyB0ZXJtcywgZG8gbm90IGFwcGx5IHRvIHRob3NlCnNl -Y3Rpb25zIHdoZW4geW91IGRpc3RyaWJ1dGUgdGhlbSBhcyBzZXBhcmF0ZSB3b3Jrcy4gIEJ1dCB3 -aGVuIHlvdQpkaXN0cmlidXRlIHRoZSBzYW1lIHNlY3Rpb25zIGFzIHBhcnQgb2YgYSB3aG9sZSB3 -aGljaCBpcyBhIHdvcmsgYmFzZWQKb24gdGhlIFByb2dyYW0sIHRoZSBkaXN0cmlidXRpb24gb2Yg -dGhlIHdob2xlIG11c3QgYmUgb24gdGhlIHRlcm1zIG9mCnRoaXMgTGljZW5zZSwgd2hvc2UgcGVy -bWlzc2lvbnMgZm9yIG90aGVyIGxpY2Vuc2VlcyBleHRlbmQgdG8gdGhlCmVudGlyZSB3aG9sZSwg -YW5kIHRodXMgdG8gZWFjaCBhbmQgZXZlcnkgcGFydCByZWdhcmRsZXNzIG9mIHdobyB3cm90ZSBp -dC4KClRodXMsIGl0IGlzIG5vdCB0aGUgaW50ZW50IG9mIHRoaXMgc2VjdGlvbiB0byBjbGFpbSBy -aWdodHMgb3IgY29udGVzdAp5b3VyIHJpZ2h0cyB0byB3b3JrIHdyaXR0ZW4gZW50aXJlbHkgYnkg -eW91OyByYXRoZXIsIHRoZSBpbnRlbnQgaXMgdG8KZXhlcmNpc2UgdGhlIHJpZ2h0IHRvIGNvbnRy -b2wgdGhlIGRpc3RyaWJ1dGlvbiBvZiBkZXJpdmF0aXZlIG9yCmNvbGxlY3RpdmUgd29ya3MgYmFz -ZWQgb24gdGhlIFByb2dyYW0uCgpJbiBhZGRpdGlvbiwgbWVyZSBhZ2dyZWdhdGlvbiBvZiBhbm90 -aGVyIHdvcmsgbm90IGJhc2VkIG9uIHRoZSBQcm9ncmFtCndpdGggdGhlIFByb2dyYW0gKG9yIHdp -dGggYSB3b3JrIGJhc2VkIG9uIHRoZSBQcm9ncmFtKSBvbiBhIHZvbHVtZSBvZgphIHN0b3JhZ2Ug -b3IgZGlzdHJpYnV0aW9uIG1lZGl1bSBkb2VzIG5vdCBicmluZyB0aGUgb3RoZXIgd29yayB1bmRl -cgp0aGUgc2NvcGUgb2YgdGhpcyBMaWNlbnNlLgoKICAzLiBZb3UgbWF5IGNvcHkgYW5kIGRpc3Ry -aWJ1dGUgdGhlIFByb2dyYW0gKG9yIGEgd29yayBiYXNlZCBvbiBpdCwKdW5kZXIgU2VjdGlvbiAy -KSBpbiBvYmplY3QgY29kZSBvciBleGVjdXRhYmxlIGZvcm0gdW5kZXIgdGhlIHRlcm1zIG9mClNl -Y3Rpb25zIDEgYW5kIDIgYWJvdmUgcHJvdmlkZWQgdGhhdCB5b3UgYWxzbyBkbyBvbmUgb2YgdGhl -IGZvbGxvd2luZzoKCiAgICBhKSBBY2NvbXBhbnkgaXQgd2l0aCB0aGUgY29tcGxldGUgY29ycmVz -cG9uZGluZyBtYWNoaW5lLXJlYWRhYmxlCiAgICBzb3VyY2UgY29kZSwgd2hpY2ggbXVzdCBiZSBk -aXN0cmlidXRlZCB1bmRlciB0aGUgdGVybXMgb2YgU2VjdGlvbnMKICAgIDEgYW5kIDIgYWJvdmUg -b24gYSBtZWRpdW0gY3VzdG9tYXJpbHkgdXNlZCBmb3Igc29mdHdhcmUgaW50ZXJjaGFuZ2U7IG9y -LAoKICAgIGIpIEFjY29tcGFueSBpdCB3aXRoIGEgd3JpdHRlbiBvZmZlciwgdmFsaWQgZm9yIGF0 -IGxlYXN0IHRocmVlCiAgICB5ZWFycywgdG8gZ2l2ZSBhbnkgdGhpcmQgcGFydHksIGZvciBhIGNo -YXJnZSBubyBtb3JlIHRoYW4geW91cgogICAgY29zdCBvZiBwaHlzaWNhbGx5IHBlcmZvcm1pbmcg -c291cmNlIGRpc3RyaWJ1dGlvbiwgYSBjb21wbGV0ZQogICAgbWFjaGluZS1yZWFkYWJsZSBjb3B5 -IG9mIHRoZSBjb3JyZXNwb25kaW5nIHNvdXJjZSBjb2RlLCB0byBiZQogICAgZGlzdHJpYnV0ZWQg -dW5kZXIgdGhlIHRlcm1zIG9mIFNlY3Rpb25zIDEgYW5kIDIgYWJvdmUgb24gYSBtZWRpdW0KICAg -IGN1c3RvbWFyaWx5IHVzZWQgZm9yIHNvZnR3YXJlIGludGVyY2hhbmdlOyBvciwKCiAgICBjKSBB -Y2NvbXBhbnkgaXQgd2l0aCB0aGUgaW5mb3JtYXRpb24geW91IHJlY2VpdmVkIGFzIHRvIHRoZSBv -ZmZlcgogICAgdG8gZGlzdHJpYnV0ZSBjb3JyZXNwb25kaW5nIHNvdXJjZSBjb2RlLiAgKFRoaXMg -YWx0ZXJuYXRpdmUgaXMKICAgIGFsbG93ZWQgb25seSBmb3Igbm9uY29tbWVyY2lhbCBkaXN0cmli -dXRpb24gYW5kIG9ubHkgaWYgeW91CiAgICByZWNlaXZlZCB0aGUgcHJvZ3JhbSBpbiBvYmplY3Qg -Y29kZSBvciBleGVjdXRhYmxlIGZvcm0gd2l0aCBzdWNoCiAgICBhbiBvZmZlciwgaW4gYWNjb3Jk -IHdpdGggU3Vic2VjdGlvbiBiIGFib3ZlLikKClRoZSBzb3VyY2UgY29kZSBmb3IgYSB3b3JrIG1l -YW5zIHRoZSBwcmVmZXJyZWQgZm9ybSBvZiB0aGUgd29yayBmb3IKbWFraW5nIG1vZGlmaWNhdGlv -bnMgdG8gaXQuICBGb3IgYW4gZXhlY3V0YWJsZSB3b3JrLCBjb21wbGV0ZSBzb3VyY2UKY29kZSBt -ZWFucyBhbGwgdGhlIHNvdXJjZSBjb2RlIGZvciBhbGwgbW9kdWxlcyBpdCBjb250YWlucywgcGx1 -cyBhbnkKYXNzb2NpYXRlZCBpbnRlcmZhY2UgZGVmaW5pdGlvbiBmaWxlcywgcGx1cyB0aGUgc2Ny -aXB0cyB1c2VkIHRvCmNvbnRyb2wgY29tcGlsYXRpb24gYW5kIGluc3RhbGxhdGlvbiBvZiB0aGUg -ZXhlY3V0YWJsZS4gIEhvd2V2ZXIsIGFzIGEKc3BlY2lhbCBleGNlcHRpb24sIHRoZSBzb3VyY2Ug -Y29kZSBkaXN0cmlidXRlZCBuZWVkIG5vdCBpbmNsdWRlCmFueXRoaW5nIHRoYXQgaXMgbm9ybWFs -bHkgZGlzdHJpYnV0ZWQgKGluIGVpdGhlciBzb3VyY2Ugb3IgYmluYXJ5CmZvcm0pIHdpdGggdGhl -IG1ham9yIGNvbXBvbmVudHMgKGNvbXBpbGVyLCBrZXJuZWwsIGFuZCBzbyBvbikgb2YgdGhlCm9w -ZXJhdGluZyBzeXN0ZW0gb24gd2hpY2ggdGhlIGV4ZWN1dGFibGUgcnVucywgdW5sZXNzIHRoYXQg -Y29tcG9uZW50Cml0c2VsZiBhY2NvbXBhbmllcyB0aGUgZXhlY3V0YWJsZS4KCklmIGRpc3RyaWJ1 -dGlvbiBvZiBleGVjdXRhYmxlIG9yIG9iamVjdCBjb2RlIGlzIG1hZGUgYnkgb2ZmZXJpbmcKYWNj -ZXNzIHRvIGNvcHkgZnJvbSBhIGRlc2lnbmF0ZWQgcGxhY2UsIHRoZW4gb2ZmZXJpbmcgZXF1aXZh -bGVudAphY2Nlc3MgdG8gY29weSB0aGUgc291cmNlIGNvZGUgZnJvbSB0aGUgc2FtZSBwbGFjZSBj -b3VudHMgYXMKZGlzdHJpYnV0aW9uIG9mIHRoZSBzb3VyY2UgY29kZSwgZXZlbiB0aG91Z2ggdGhp -cmQgcGFydGllcyBhcmUgbm90CmNvbXBlbGxlZCB0byBjb3B5IHRoZSBzb3VyY2UgYWxvbmcgd2l0 -aCB0aGUgb2JqZWN0IGNvZGUuCgwKICA0LiBZb3UgbWF5IG5vdCBjb3B5LCBtb2RpZnksIHN1Ymxp -Y2Vuc2UsIG9yIGRpc3RyaWJ1dGUgdGhlIFByb2dyYW0KZXhjZXB0IGFzIGV4cHJlc3NseSBwcm92 -aWRlZCB1bmRlciB0aGlzIExpY2Vuc2UuICBBbnkgYXR0ZW1wdApvdGhlcndpc2UgdG8gY29weSwg -bW9kaWZ5LCBzdWJsaWNlbnNlIG9yIGRpc3RyaWJ1dGUgdGhlIFByb2dyYW0gaXMKdm9pZCwgYW5k -IHdpbGwgYXV0b21hdGljYWxseSB0ZXJtaW5hdGUgeW91ciByaWdodHMgdW5kZXIgdGhpcyBMaWNl -bnNlLgpIb3dldmVyLCBwYXJ0aWVzIHdobyBoYXZlIHJlY2VpdmVkIGNvcGllcywgb3IgcmlnaHRz -LCBmcm9tIHlvdSB1bmRlcgp0aGlzIExpY2Vuc2Ugd2lsbCBub3QgaGF2ZSB0aGVpciBsaWNlbnNl -cyB0ZXJtaW5hdGVkIHNvIGxvbmcgYXMgc3VjaApwYXJ0aWVzIHJlbWFpbiBpbiBmdWxsIGNvbXBs -aWFuY2UuCgogIDUuIFlvdSBhcmUgbm90IHJlcXVpcmVkIHRvIGFjY2VwdCB0aGlzIExpY2Vuc2Us -IHNpbmNlIHlvdSBoYXZlIG5vdApzaWduZWQgaXQuICBIb3dldmVyLCBub3RoaW5nIGVsc2UgZ3Jh -bnRzIHlvdSBwZXJtaXNzaW9uIHRvIG1vZGlmeSBvcgpkaXN0cmlidXRlIHRoZSBQcm9ncmFtIG9y -IGl0cyBkZXJpdmF0aXZlIHdvcmtzLiAgVGhlc2UgYWN0aW9ucyBhcmUKcHJvaGliaXRlZCBieSBs -YXcgaWYgeW91IGRvIG5vdCBhY2NlcHQgdGhpcyBMaWNlbnNlLiAgVGhlcmVmb3JlLCBieQptb2Rp -Znlpbmcgb3IgZGlzdHJpYnV0aW5nIHRoZSBQcm9ncmFtIChvciBhbnkgd29yayBiYXNlZCBvbiB0 -aGUKUHJvZ3JhbSksIHlvdSBpbmRpY2F0ZSB5b3VyIGFjY2VwdGFuY2Ugb2YgdGhpcyBMaWNlbnNl -IHRvIGRvIHNvLCBhbmQKYWxsIGl0cyB0ZXJtcyBhbmQgY29uZGl0aW9ucyBmb3IgY29weWluZywg -ZGlzdHJpYnV0aW5nIG9yIG1vZGlmeWluZwp0aGUgUHJvZ3JhbSBvciB3b3JrcyBiYXNlZCBvbiBp -dC4KCiAgNi4gRWFjaCB0aW1lIHlvdSByZWRpc3RyaWJ1dGUgdGhlIFByb2dyYW0gKG9yIGFueSB3 -b3JrIGJhc2VkIG9uIHRoZQpQcm9ncmFtKSwgdGhlIHJlY2lwaWVudCBhdXRvbWF0aWNhbGx5IHJl -Y2VpdmVzIGEgbGljZW5zZSBmcm9tIHRoZQpvcmlnaW5hbCBsaWNlbnNvciB0byBjb3B5LCBkaXN0 -cmlidXRlIG9yIG1vZGlmeSB0aGUgUHJvZ3JhbSBzdWJqZWN0IHRvCnRoZXNlIHRlcm1zIGFuZCBj -b25kaXRpb25zLiAgWW91IG1heSBub3QgaW1wb3NlIGFueSBmdXJ0aGVyCnJlc3RyaWN0aW9ucyBv -biB0aGUgcmVjaXBpZW50cycgZXhlcmNpc2Ugb2YgdGhlIHJpZ2h0cyBncmFudGVkIGhlcmVpbi4K -WW91IGFyZSBub3QgcmVzcG9uc2libGUgZm9yIGVuZm9yY2luZyBjb21wbGlhbmNlIGJ5IHRoaXJk -IHBhcnRpZXMgdG8KdGhpcyBMaWNlbnNlLgoKICA3LiBJZiwgYXMgYSBjb25zZXF1ZW5jZSBvZiBh -IGNvdXJ0IGp1ZGdtZW50IG9yIGFsbGVnYXRpb24gb2YgcGF0ZW50CmluZnJpbmdlbWVudCBvciBm -b3IgYW55IG90aGVyIHJlYXNvbiAobm90IGxpbWl0ZWQgdG8gcGF0ZW50IGlzc3VlcyksCmNvbmRp -dGlvbnMgYXJlIGltcG9zZWQgb24geW91ICh3aGV0aGVyIGJ5IGNvdXJ0IG9yZGVyLCBhZ3JlZW1l -bnQgb3IKb3RoZXJ3aXNlKSB0aGF0IGNvbnRyYWRpY3QgdGhlIGNvbmRpdGlvbnMgb2YgdGhpcyBM -aWNlbnNlLCB0aGV5IGRvIG5vdApleGN1c2UgeW91IGZyb20gdGhlIGNvbmRpdGlvbnMgb2YgdGhp -cyBMaWNlbnNlLiAgSWYgeW91IGNhbm5vdApkaXN0cmlidXRlIHNvIGFzIHRvIHNhdGlzZnkgc2lt -dWx0YW5lb3VzbHkgeW91ciBvYmxpZ2F0aW9ucyB1bmRlciB0aGlzCkxpY2Vuc2UgYW5kIGFueSBv -dGhlciBwZXJ0aW5lbnQgb2JsaWdhdGlvbnMsIHRoZW4gYXMgYSBjb25zZXF1ZW5jZSB5b3UKbWF5 -IG5vdCBkaXN0cmlidXRlIHRoZSBQcm9ncmFtIGF0IGFsbC4gIEZvciBleGFtcGxlLCBpZiBhIHBh -dGVudApsaWNlbnNlIHdvdWxkIG5vdCBwZXJtaXQgcm95YWx0eS1mcmVlIHJlZGlzdHJpYnV0aW9u -IG9mIHRoZSBQcm9ncmFtIGJ5CmFsbCB0aG9zZSB3aG8gcmVjZWl2ZSBjb3BpZXMgZGlyZWN0bHkg -b3IgaW5kaXJlY3RseSB0aHJvdWdoIHlvdSwgdGhlbgp0aGUgb25seSB3YXkgeW91IGNvdWxkIHNh -dGlzZnkgYm90aCBpdCBhbmQgdGhpcyBMaWNlbnNlIHdvdWxkIGJlIHRvCnJlZnJhaW4gZW50aXJl -bHkgZnJvbSBkaXN0cmlidXRpb24gb2YgdGhlIFByb2dyYW0uCgpJZiBhbnkgcG9ydGlvbiBvZiB0 -aGlzIHNlY3Rpb24gaXMgaGVsZCBpbnZhbGlkIG9yIHVuZW5mb3JjZWFibGUgdW5kZXIKYW55IHBh -cnRpY3VsYXIgY2lyY3Vtc3RhbmNlLCB0aGUgYmFsYW5jZSBvZiB0aGUgc2VjdGlvbiBpcyBpbnRl -bmRlZCB0bwphcHBseSBhbmQgdGhlIHNlY3Rpb24gYXMgYSB3aG9sZSBpcyBpbnRlbmRlZCB0byBh -cHBseSBpbiBvdGhlcgpjaXJjdW1zdGFuY2VzLgoKSXQgaXMgbm90IHRoZSBwdXJwb3NlIG9mIHRo -aXMgc2VjdGlvbiB0byBpbmR1Y2UgeW91IHRvIGluZnJpbmdlIGFueQpwYXRlbnRzIG9yIG90aGVy -IHByb3BlcnR5IHJpZ2h0IGNsYWltcyBvciB0byBjb250ZXN0IHZhbGlkaXR5IG9mIGFueQpzdWNo -IGNsYWltczsgdGhpcyBzZWN0aW9uIGhhcyB0aGUgc29sZSBwdXJwb3NlIG9mIHByb3RlY3Rpbmcg -dGhlCmludGVncml0eSBvZiB0aGUgZnJlZSBzb2Z0d2FyZSBkaXN0cmlidXRpb24gc3lzdGVtLCB3 -aGljaCBpcwppbXBsZW1lbnRlZCBieSBwdWJsaWMgbGljZW5zZSBwcmFjdGljZXMuICBNYW55IHBl -b3BsZSBoYXZlIG1hZGUKZ2VuZXJvdXMgY29udHJpYnV0aW9ucyB0byB0aGUgd2lkZSByYW5nZSBv -ZiBzb2Z0d2FyZSBkaXN0cmlidXRlZAp0aHJvdWdoIHRoYXQgc3lzdGVtIGluIHJlbGlhbmNlIG9u -IGNvbnNpc3RlbnQgYXBwbGljYXRpb24gb2YgdGhhdApzeXN0ZW07IGl0IGlzIHVwIHRvIHRoZSBh -dXRob3IvZG9ub3IgdG8gZGVjaWRlIGlmIGhlIG9yIHNoZSBpcyB3aWxsaW5nCnRvIGRpc3RyaWJ1 -dGUgc29mdHdhcmUgdGhyb3VnaCBhbnkgb3RoZXIgc3lzdGVtIGFuZCBhIGxpY2Vuc2VlIGNhbm5v -dAppbXBvc2UgdGhhdCBjaG9pY2UuCgpUaGlzIHNlY3Rpb24gaXMgaW50ZW5kZWQgdG8gbWFrZSB0 -aG9yb3VnaGx5IGNsZWFyIHdoYXQgaXMgYmVsaWV2ZWQgdG8KYmUgYSBjb25zZXF1ZW5jZSBvZiB0 -aGUgcmVzdCBvZiB0aGlzIExpY2Vuc2UuCgwKICA4LiBJZiB0aGUgZGlzdHJpYnV0aW9uIGFuZC9v -ciB1c2Ugb2YgdGhlIFByb2dyYW0gaXMgcmVzdHJpY3RlZCBpbgpjZXJ0YWluIGNvdW50cmllcyBl -aXRoZXIgYnkgcGF0ZW50cyBvciBieSBjb3B5cmlnaHRlZCBpbnRlcmZhY2VzLCB0aGUKb3JpZ2lu -YWwgY29weXJpZ2h0IGhvbGRlciB3aG8gcGxhY2VzIHRoZSBQcm9ncmFtIHVuZGVyIHRoaXMgTGlj -ZW5zZQptYXkgYWRkIGFuIGV4cGxpY2l0IGdlb2dyYXBoaWNhbCBkaXN0cmlidXRpb24gbGltaXRh -dGlvbiBleGNsdWRpbmcKdGhvc2UgY291bnRyaWVzLCBzbyB0aGF0IGRpc3RyaWJ1dGlvbiBpcyBw -ZXJtaXR0ZWQgb25seSBpbiBvciBhbW9uZwpjb3VudHJpZXMgbm90IHRodXMgZXhjbHVkZWQuICBJ -biBzdWNoIGNhc2UsIHRoaXMgTGljZW5zZSBpbmNvcnBvcmF0ZXMKdGhlIGxpbWl0YXRpb24gYXMg -aWYgd3JpdHRlbiBpbiB0aGUgYm9keSBvZiB0aGlzIExpY2Vuc2UuCgogIDkuIFRoZSBGcmVlIFNv -ZnR3YXJlIEZvdW5kYXRpb24gbWF5IHB1Ymxpc2ggcmV2aXNlZCBhbmQvb3IgbmV3IHZlcnNpb25z -Cm9mIHRoZSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZyb20gdGltZSB0byB0aW1lLiAgU3VjaCBu -ZXcgdmVyc2lvbnMgd2lsbApiZSBzaW1pbGFyIGluIHNwaXJpdCB0byB0aGUgcHJlc2VudCB2ZXJz -aW9uLCBidXQgbWF5IGRpZmZlciBpbiBkZXRhaWwgdG8KYWRkcmVzcyBuZXcgcHJvYmxlbXMgb3Ig -Y29uY2VybnMuCgpFYWNoIHZlcnNpb24gaXMgZ2l2ZW4gYSBkaXN0aW5ndWlzaGluZyB2ZXJzaW9u -IG51bWJlci4gIElmIHRoZSBQcm9ncmFtCnNwZWNpZmllcyBhIHZlcnNpb24gbnVtYmVyIG9mIHRo -aXMgTGljZW5zZSB3aGljaCBhcHBsaWVzIHRvIGl0IGFuZCAiYW55CmxhdGVyIHZlcnNpb24iLCB5 -b3UgaGF2ZSB0aGUgb3B0aW9uIG9mIGZvbGxvd2luZyB0aGUgdGVybXMgYW5kIGNvbmRpdGlvbnMK -ZWl0aGVyIG9mIHRoYXQgdmVyc2lvbiBvciBvZiBhbnkgbGF0ZXIgdmVyc2lvbiBwdWJsaXNoZWQg -YnkgdGhlIEZyZWUKU29mdHdhcmUgRm91bmRhdGlvbi4gIElmIHRoZSBQcm9ncmFtIGRvZXMgbm90 -IHNwZWNpZnkgYSB2ZXJzaW9uIG51bWJlciBvZgp0aGlzIExpY2Vuc2UsIHlvdSBtYXkgY2hvb3Nl -IGFueSB2ZXJzaW9uIGV2ZXIgcHVibGlzaGVkIGJ5IHRoZSBGcmVlIFNvZnR3YXJlCkZvdW5kYXRp -b24uCgogIDEwLiBJZiB5b3Ugd2lzaCB0byBpbmNvcnBvcmF0ZSBwYXJ0cyBvZiB0aGUgUHJvZ3Jh -bSBpbnRvIG90aGVyIGZyZWUKcHJvZ3JhbXMgd2hvc2UgZGlzdHJpYnV0aW9uIGNvbmRpdGlvbnMg -YXJlIGRpZmZlcmVudCwgd3JpdGUgdG8gdGhlIGF1dGhvcgp0byBhc2sgZm9yIHBlcm1pc3Npb24u -ICBGb3Igc29mdHdhcmUgd2hpY2ggaXMgY29weXJpZ2h0ZWQgYnkgdGhlIEZyZWUKU29mdHdhcmUg -Rm91bmRhdGlvbiwgd3JpdGUgdG8gdGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbjsgd2Ugc29t -ZXRpbWVzCm1ha2UgZXhjZXB0aW9ucyBmb3IgdGhpcy4gIE91ciBkZWNpc2lvbiB3aWxsIGJlIGd1 -aWRlZCBieSB0aGUgdHdvIGdvYWxzCm9mIHByZXNlcnZpbmcgdGhlIGZyZWUgc3RhdHVzIG9mIGFs -bCBkZXJpdmF0aXZlcyBvZiBvdXIgZnJlZSBzb2Z0d2FyZSBhbmQKb2YgcHJvbW90aW5nIHRoZSBz -aGFyaW5nIGFuZCByZXVzZSBvZiBzb2Z0d2FyZSBnZW5lcmFsbHkuCgoJCQkgICAgTk8gV0FSUkFO -VFkKCiAgMTEuIEJFQ0FVU0UgVEhFIFBST0dSQU0gSVMgTElDRU5TRUQgRlJFRSBPRiBDSEFSR0Us -IFRIRVJFIElTIE5PIFdBUlJBTlRZCkZPUiBUSEUgUFJPR1JBTSwgVE8gVEhFIEVYVEVOVCBQRVJN -SVRURUQgQlkgQVBQTElDQUJMRSBMQVcuICBFWENFUFQgV0hFTgpPVEhFUldJU0UgU1RBVEVEIElO -IFdSSVRJTkcgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORC9PUiBPVEhFUiBQQVJUSUVTClBST1ZJ -REUgVEhFIFBST0dSQU0gIkFTIElTIiBXSVRIT1VUIFdBUlJBTlRZIE9GIEFOWSBLSU5ELCBFSVRI -RVIgRVhQUkVTU0VECk9SIElNUExJRUQsIElOQ0xVRElORywgQlVUIE5PVCBMSU1JVEVEIFRPLCBU -SEUgSU1QTElFRCBXQVJSQU5USUVTIE9GCk1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1Ig -QSBQQVJUSUNVTEFSIFBVUlBPU0UuICBUSEUgRU5USVJFIFJJU0sgQVMKVE8gVEhFIFFVQUxJVFkg -QU5EIFBFUkZPUk1BTkNFIE9GIFRIRSBQUk9HUkFNIElTIFdJVEggWU9VLiAgU0hPVUxEIFRIRQpQ -Uk9HUkFNIFBST1ZFIERFRkVDVElWRSwgWU9VIEFTU1VNRSBUSEUgQ09TVCBPRiBBTEwgTkVDRVNT -QVJZIFNFUlZJQ0lORywKUkVQQUlSIE9SIENPUlJFQ1RJT04uCgogIDEyLiBJTiBOTyBFVkVOVCBV -TkxFU1MgUkVRVUlSRUQgQlkgQVBQTElDQUJMRSBMQVcgT1IgQUdSRUVEIFRPIElOIFdSSVRJTkcK -V0lMTCBBTlkgQ09QWVJJR0hUIEhPTERFUiwgT1IgQU5ZIE9USEVSIFBBUlRZIFdITyBNQVkgTU9E -SUZZIEFORC9PUgpSRURJU1RSSUJVVEUgVEhFIFBST0dSQU0gQVMgUEVSTUlUVEVEIEFCT1ZFLCBC -RSBMSUFCTEUgVE8gWU9VIEZPUiBEQU1BR0VTLApJTkNMVURJTkcgQU5ZIEdFTkVSQUwsIFNQRUNJ -QUwsIElOQ0lERU5UQUwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIEFSSVNJTkcKT1VUIE9GIFRI -RSBVU0UgT1IgSU5BQklMSVRZIFRPIFVTRSBUSEUgUFJPR1JBTSAoSU5DTFVESU5HIEJVVCBOT1Qg -TElNSVRFRApUTyBMT1NTIE9GIERBVEEgT1IgREFUQSBCRUlORyBSRU5ERVJFRCBJTkFDQ1VSQVRF -IE9SIExPU1NFUyBTVVNUQUlORUQgQlkKWU9VIE9SIFRISVJEIFBBUlRJRVMgT1IgQSBGQUlMVVJF -IE9GIFRIRSBQUk9HUkFNIFRPIE9QRVJBVEUgV0lUSCBBTlkgT1RIRVIKUFJPR1JBTVMpLCBFVkVO -IElGIFNVQ0ggSE9MREVSIE9SIE9USEVSIFBBUlRZIEhBUyBCRUVOIEFEVklTRUQgT0YgVEhFClBP -U1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFUy4KCgkJICAgICBFTkQgT0YgVEVSTVMgQU5EIENPTkRJ -VElPTlMKDAoJICAgIEhvdyB0byBBcHBseSBUaGVzZSBUZXJtcyB0byBZb3VyIE5ldyBQcm9ncmFt -cwoKICBJZiB5b3UgZGV2ZWxvcCBhIG5ldyBwcm9ncmFtLCBhbmQgeW91IHdhbnQgaXQgdG8gYmUg -b2YgdGhlIGdyZWF0ZXN0CnBvc3NpYmxlIHVzZSB0byB0aGUgcHVibGljLCB0aGUgYmVzdCB3YXkg -dG8gYWNoaWV2ZSB0aGlzIGlzIHRvIG1ha2UgaXQKZnJlZSBzb2Z0d2FyZSB3aGljaCBldmVyeW9u -ZSBjYW4gcmVkaXN0cmlidXRlIGFuZCBjaGFuZ2UgdW5kZXIgdGhlc2UgdGVybXMuCgogIFRvIGRv -IHNvLCBhdHRhY2ggdGhlIGZvbGxvd2luZyBub3RpY2VzIHRvIHRoZSBwcm9ncmFtLiAgSXQgaXMg -c2FmZXN0CnRvIGF0dGFjaCB0aGVtIHRvIHRoZSBzdGFydCBvZiBlYWNoIHNvdXJjZSBmaWxlIHRv -IG1vc3QgZWZmZWN0aXZlbHkKY29udmV5IHRoZSBleGNsdXNpb24gb2Ygd2FycmFudHk7IGFuZCBl -YWNoIGZpbGUgc2hvdWxkIGhhdmUgYXQgbGVhc3QKdGhlICJjb3B5cmlnaHQiIGxpbmUgYW5kIGEg -cG9pbnRlciB0byB3aGVyZSB0aGUgZnVsbCBub3RpY2UgaXMgZm91bmQuCgogICAgPG9uZSBsaW5l -IHRvIGdpdmUgdGhlIHByb2dyYW0ncyBuYW1lIGFuZCBhIGJyaWVmIGlkZWEgb2Ygd2hhdCBpdCBk -b2VzLj4KICAgIENvcHlyaWdodCAoQykgPHllYXI+ICA8bmFtZSBvZiBhdXRob3I+CgogICAgVGhp -cyBwcm9ncmFtIGlzIGZyZWUgc29mdHdhcmU7IHlvdSBjYW4gcmVkaXN0cmlidXRlIGl0IGFuZC9v -ciBtb2RpZnkKICAgIGl0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGlj -IExpY2Vuc2UgYXMgcHVibGlzaGVkIGJ5CiAgICB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9u -OyBlaXRoZXIgdmVyc2lvbiAyIG9mIHRoZSBMaWNlbnNlLCBvcgogICAgKGF0IHlvdXIgb3B0aW9u -KSBhbnkgbGF0ZXIgdmVyc2lvbi4KCiAgICBUaGlzIHByb2dyYW0gaXMgZGlzdHJpYnV0ZWQgaW4g -dGhlIGhvcGUgdGhhdCBpdCB3aWxsIGJlIHVzZWZ1bCwKICAgIGJ1dCBXSVRIT1VUIEFOWSBXQVJS -QU5UWTsgd2l0aG91dCBldmVuIHRoZSBpbXBsaWVkIHdhcnJhbnR5IG9mCiAgICBNRVJDSEFOVEFC -SUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlCiAgICBH -TlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBmb3IgbW9yZSBkZXRhaWxzLgoKICAgIFlvdSBzaG91 -bGQgaGF2ZSByZWNlaXZlZCBhIGNvcHkgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNl -CiAgICBhbG9uZyB3aXRoIHRoaXMgcHJvZ3JhbTsgaWYgbm90LCB3cml0ZSB0byB0aGUgRnJlZSBT -b2Z0d2FyZQogICAgRm91bmRhdGlvbiwgSW5jLiwgNTkgVGVtcGxlIFBsYWNlLCBTdWl0ZSAzMzAs -IEJvc3RvbiwgTUEgIDAyMTExLTEzMDcgIFVTQQoKCkFsc28gYWRkIGluZm9ybWF0aW9uIG9uIGhv -dyB0byBjb250YWN0IHlvdSBieSBlbGVjdHJvbmljIGFuZCBwYXBlciBtYWlsLgoKSWYgdGhlIHBy -b2dyYW0gaXMgaW50ZXJhY3RpdmUsIG1ha2UgaXQgb3V0cHV0IGEgc2hvcnQgbm90aWNlIGxpa2Ug -dGhpcwp3aGVuIGl0IHN0YXJ0cyBpbiBhbiBpbnRlcmFjdGl2ZSBtb2RlOgoKICAgIEdub21vdmlz -aW9uIHZlcnNpb24gNjksIENvcHlyaWdodCAoQykgeWVhciBuYW1lIG9mIGF1dGhvcgogICAgR25v -bW92aXNpb24gY29tZXMgd2l0aCBBQlNPTFVURUxZIE5PIFdBUlJBTlRZOyBmb3IgZGV0YWlscyB0 -eXBlIGBzaG93IHcnLgogICAgVGhpcyBpcyBmcmVlIHNvZnR3YXJlLCBhbmQgeW91IGFyZSB3ZWxj -b21lIHRvIHJlZGlzdHJpYnV0ZSBpdAogICAgdW5kZXIgY2VydGFpbiBjb25kaXRpb25zOyB0eXBl -IGBzaG93IGMnIGZvciBkZXRhaWxzLgoKVGhlIGh5cG90aGV0aWNhbCBjb21tYW5kcyBgc2hvdyB3 -JyBhbmQgYHNob3cgYycgc2hvdWxkIHNob3cgdGhlIGFwcHJvcHJpYXRlCnBhcnRzIG9mIHRoZSBH -ZW5lcmFsIFB1YmxpYyBMaWNlbnNlLiAgT2YgY291cnNlLCB0aGUgY29tbWFuZHMgeW91IHVzZSBt -YXkKYmUgY2FsbGVkIHNvbWV0aGluZyBvdGhlciB0aGFuIGBzaG93IHcnIGFuZCBgc2hvdyBjJzsg -dGhleSBjb3VsZCBldmVuIGJlCm1vdXNlLWNsaWNrcyBvciBtZW51IGl0ZW1zLS13aGF0ZXZlciBz -dWl0cyB5b3VyIHByb2dyYW0uCgpZb3Ugc2hvdWxkIGFsc28gZ2V0IHlvdXIgZW1wbG95ZXIgKGlm -IHlvdSB3b3JrIGFzIGEgcHJvZ3JhbW1lcikgb3IgeW91cgpzY2hvb2wsIGlmIGFueSwgdG8gc2ln -biBhICJjb3B5cmlnaHQgZGlzY2xhaW1lciIgZm9yIHRoZSBwcm9ncmFtLCBpZgpuZWNlc3Nhcnku -ICBIZXJlIGlzIGEgc2FtcGxlOyBhbHRlciB0aGUgbmFtZXM6CgogIFlveW9keW5lLCBJbmMuLCBo -ZXJlYnkgZGlzY2xhaW1zIGFsbCBjb3B5cmlnaHQgaW50ZXJlc3QgaW4gdGhlIHByb2dyYW0KICBg -R25vbW92aXNpb24nICh3aGljaCBtYWtlcyBwYXNzZXMgYXQgY29tcGlsZXJzKSB3cml0dGVuIGJ5 -IEphbWVzIEhhY2tlci4KCiAgPHNpZ25hdHVyZSBvZiBUeSBDb29uPiwgMSBBcHJpbCAxOTg5CiAg -VHkgQ29vbiwgUHJlc2lkZW50IG9mIFZpY2UKClRoaXMgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBk -b2VzIG5vdCBwZXJtaXQgaW5jb3Jwb3JhdGluZyB5b3VyIHByb2dyYW0gaW50bwpwcm9wcmlldGFy -eSBwcm9ncmFtcy4gIElmIHlvdXIgcHJvZ3JhbSBpcyBhIHN1YnJvdXRpbmUgbGlicmFyeSwgeW91 -IG1heQpjb25zaWRlciBpdCBtb3JlIHVzZWZ1bCB0byBwZXJtaXQgbGlua2luZyBwcm9wcmlldGFy -eSBhcHBsaWNhdGlvbnMgd2l0aCB0aGUKbGlicmFyeS4gIElmIHRoaXMgaXMgd2hhdCB5b3Ugd2Fu -dCB0byBkbywgdXNlIHRoZSBHTlUgTGlicmFyeSBHZW5lcmFsClB1YmxpYyBMaWNlbnNlIGluc3Rl -YWQgb2YgdGhpcyBMaWNlbnNlLgo= -ClxjaGFwdGVye1RoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZX0KClxiZWdpbntjZW50ZXJ9 -CntccGFyaW5kZW50IDBpbgoKVmVyc2lvbiAyLCBKdW5lIDE5OTEKCkNvcHlyaWdodCBcY29weXJp -Z2h0XCAxOTg5LCAxOTkxIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiwgSW5jLgoKXGJpZ3NraXAK -CjU5IFRlbXBsZSBQbGFjZSAtIFN1aXRlIDMzMCwgQm9zdG9uLCBNQSAgMDIxMTEtMTMwNywgVVNB -CgpcYmlnc2tpcAoKRXZlcnlvbmUgaXMgcGVybWl0dGVkIHRvIGNvcHkgYW5kIGRpc3RyaWJ1dGUg -dmVyYmF0aW0gY29waWVzCm9mIHRoaXMgbGljZW5zZSBkb2N1bWVudCwgYnV0IGNoYW5naW5nIGl0 -IGlzIG5vdCBhbGxvd2VkLgp9ClxlbmR7Y2VudGVyfQoKXGJlZ2lue2NlbnRlcn0Ke1xiZlxsYXJn -ZSBQcmVhbWJsZX0KXGVuZHtjZW50ZXJ9CgoKVGhlIGxpY2Vuc2VzIGZvciBtb3N0IHNvZnR3YXJl -IGFyZSBkZXNpZ25lZCB0byB0YWtlIGF3YXkgeW91ciBmcmVlZG9tIHRvCnNoYXJlIGFuZCBjaGFu -Z2UgaXQuICBCeSBjb250cmFzdCwgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGlzCmlu -dGVuZGVkIHRvIGd1YXJhbnRlZSB5b3VyIGZyZWVkb20gdG8gc2hhcmUgYW5kIGNoYW5nZSBmcmVl -IHNvZnR3YXJlLS0tdG8KbWFrZSBzdXJlIHRoZSBzb2Z0d2FyZSBpcyBmcmVlIGZvciBhbGwgaXRz -IHVzZXJzLiAgVGhpcyBHZW5lcmFsIFB1YmxpYwpMaWNlbnNlIGFwcGxpZXMgdG8gbW9zdCBvZiB0 -aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uJ3Mgc29mdHdhcmUgYW5kIHRvCmFueSBvdGhlciBw -cm9ncmFtIHdob3NlIGF1dGhvcnMgY29tbWl0IHRvIHVzaW5nIGl0LiAgKFNvbWUgb3RoZXIgRnJl -ZQpTb2Z0d2FyZSBGb3VuZGF0aW9uIHNvZnR3YXJlIGlzIGNvdmVyZWQgYnkgdGhlIEdOVSBMaWJy -YXJ5IEdlbmVyYWwgUHVibGljCkxpY2Vuc2UgaW5zdGVhZC4pICBZb3UgY2FuIGFwcGx5IGl0IHRv -IHlvdXIgcHJvZ3JhbXMsIHRvby4KCldoZW4gd2Ugc3BlYWsgb2YgZnJlZSBzb2Z0d2FyZSwgd2Ug -YXJlIHJlZmVycmluZyB0byBmcmVlZG9tLCBub3QgcHJpY2UuCk91ciBHZW5lcmFsIFB1YmxpYyBM -aWNlbnNlcyBhcmUgZGVzaWduZWQgdG8gbWFrZSBzdXJlIHRoYXQgeW91IGhhdmUgdGhlCmZyZWVk -b20gdG8gZGlzdHJpYnV0ZSBjb3BpZXMgb2YgZnJlZSBzb2Z0d2FyZSAoYW5kIGNoYXJnZSBmb3Ig -dGhpcyBzZXJ2aWNlCmlmIHlvdSB3aXNoKSwgdGhhdCB5b3UgcmVjZWl2ZSBzb3VyY2UgY29kZSBv -ciBjYW4gZ2V0IGl0IGlmIHlvdSB3YW50IGl0LAp0aGF0IHlvdSBjYW4gY2hhbmdlIHRoZSBzb2Z0 -d2FyZSBvciB1c2UgcGllY2VzIG9mIGl0IGluIG5ldyBmcmVlIHByb2dyYW1zOwphbmQgdGhhdCB5 -b3Uga25vdyB5b3UgY2FuIGRvIHRoZXNlIHRoaW5ncy4KClRvIHByb3RlY3QgeW91ciByaWdodHMs -IHdlIG5lZWQgdG8gbWFrZSByZXN0cmljdGlvbnMgdGhhdCBmb3JiaWQgYW55b25lIHRvCmRlbnkg -eW91IHRoZXNlIHJpZ2h0cyBvciB0byBhc2sgeW91IHRvIHN1cnJlbmRlciB0aGUgcmlnaHRzLiAg -VGhlc2UKcmVzdHJpY3Rpb25zIHRyYW5zbGF0ZSB0byBjZXJ0YWluIHJlc3BvbnNpYmlsaXRpZXMg -Zm9yIHlvdSBpZiB5b3UKZGlzdHJpYnV0ZSBjb3BpZXMgb2YgdGhlIHNvZnR3YXJlLCBvciBpZiB5 -b3UgbW9kaWZ5IGl0LgoKRm9yIGV4YW1wbGUsIGlmIHlvdSBkaXN0cmlidXRlIGNvcGllcyBvZiBz -dWNoIGEgcHJvZ3JhbSwgd2hldGhlciBncmF0aXMgb3IKZm9yIGEgZmVlLCB5b3UgbXVzdCBnaXZl -IHRoZSByZWNpcGllbnRzIGFsbCB0aGUgcmlnaHRzIHRoYXQgeW91IGhhdmUuICBZb3UKbXVzdCBt -YWtlIHN1cmUgdGhhdCB0aGV5LCB0b28sIHJlY2VpdmUgb3IgY2FuIGdldCB0aGUgc291cmNlIGNv -ZGUuICBBbmQKeW91IG11c3Qgc2hvdyB0aGVtIHRoZXNlIHRlcm1zIHNvIHRoZXkga25vdyB0aGVp -ciByaWdodHMuCgpXZSBwcm90ZWN0IHlvdXIgcmlnaHRzIHdpdGggdHdvIHN0ZXBzOiAoMSkgY29w -eXJpZ2h0IHRoZSBzb2Z0d2FyZSwgYW5kICgyKQpvZmZlciB5b3UgdGhpcyBsaWNlbnNlIHdoaWNo -IGdpdmVzIHlvdSBsZWdhbCBwZXJtaXNzaW9uIHRvIGNvcHksCmRpc3RyaWJ1dGUgYW5kL29yIG1v -ZGlmeSB0aGUgc29mdHdhcmUuCgpBbHNvLCBmb3IgZWFjaCBhdXRob3IncyBwcm90ZWN0aW9uIGFu -ZCBvdXJzLCB3ZSB3YW50IHRvIG1ha2UgY2VydGFpbiB0aGF0CmV2ZXJ5b25lIHVuZGVyc3RhbmRz -IHRoYXQgdGhlcmUgaXMgbm8gd2FycmFudHkgZm9yIHRoaXMgZnJlZSBzb2Z0d2FyZS4gIElmCnRo -ZSBzb2Z0d2FyZSBpcyBtb2RpZmllZCBieSBzb21lb25lIGVsc2UgYW5kIHBhc3NlZCBvbiwgd2Ug -d2FudCBpdHMKcmVjaXBpZW50cyB0byBrbm93IHRoYXQgd2hhdCB0aGV5IGhhdmUgaXMgbm90IHRo -ZSBvcmlnaW5hbCwgc28gdGhhdCBhbnkKcHJvYmxlbXMgaW50cm9kdWNlZCBieSBvdGhlcnMgd2ls -bCBub3QgcmVmbGVjdCBvbiB0aGUgb3JpZ2luYWwgYXV0aG9ycycKcmVwdXRhdGlvbnMuCgpGaW5h -bGx5LCBhbnkgZnJlZSBwcm9ncmFtIGlzIHRocmVhdGVuZWQgY29uc3RhbnRseSBieSBzb2Z0d2Fy -ZSBwYXRlbnRzLgpXZSB3aXNoIHRvIGF2b2lkIHRoZSBkYW5nZXIgdGhhdCByZWRpc3RyaWJ1dG9y -cyBvZiBhIGZyZWUgcHJvZ3JhbSB3aWxsCmluZGl2aWR1YWxseSBvYnRhaW4gcGF0ZW50IGxpY2Vu -c2VzLCBpbiBlZmZlY3QgbWFraW5nIHRoZSBwcm9ncmFtCnByb3ByaWV0YXJ5LiAgVG8gcHJldmVu -dCB0aGlzLCB3ZSBoYXZlIG1hZGUgaXQgY2xlYXIgdGhhdCBhbnkgcGF0ZW50IG11c3QKYmUgbGlj -ZW5zZWQgZm9yIGV2ZXJ5b25lJ3MgZnJlZSB1c2Ugb3Igbm90IGxpY2Vuc2VkIGF0IGFsbC4KClRo -ZSBwcmVjaXNlIHRlcm1zIGFuZCBjb25kaXRpb25zIGZvciBjb3B5aW5nLCBkaXN0cmlidXRpb24g -YW5kCm1vZGlmaWNhdGlvbiBmb2xsb3cuCgpcYmVnaW57Y2VudGVyfQp7XExhcmdlIFxzYyBUZXJt -cyBhbmQgQ29uZGl0aW9ucyBGb3IgQ29weWluZywgRGlzdHJpYnV0aW9uIGFuZAogIE1vZGlmaWNh -dGlvbn0KXGVuZHtjZW50ZXJ9CgoKJVxyZW5ld2NvbW1hbmR7XHRoZWVudW1pfXtcYWxwaGF7ZW51 -bWl9fQpcYmVnaW57ZW51bWVyYXRlfQoKXGFkZHRvY291bnRlcntlbnVtaX17LTF9CgpcaXRlbSAK -ClRoaXMgTGljZW5zZSBhcHBsaWVzIHRvIGFueSBwcm9ncmFtIG9yIG90aGVyIHdvcmsgd2hpY2gg -Y29udGFpbnMgYSBub3RpY2UKcGxhY2VkIGJ5IHRoZSBjb3B5cmlnaHQgaG9sZGVyIHNheWluZyBp -dCBtYXkgYmUgZGlzdHJpYnV0ZWQgdW5kZXIgdGhlCnRlcm1zIG9mIHRoaXMgR2VuZXJhbCBQdWJs -aWMgTGljZW5zZS4gIFRoZSBgYFByb2dyYW0nJywgYmVsb3csIHJlZmVycyB0bwphbnkgc3VjaCBw -cm9ncmFtIG9yIHdvcmssIGFuZCBhIGBgd29yayBiYXNlZCBvbiB0aGUgUHJvZ3JhbScnIG1lYW5z -IGVpdGhlcgp0aGUgUHJvZ3JhbSBvciBhbnkgZGVyaXZhdGl2ZSB3b3JrIHVuZGVyIGNvcHlyaWdo -dCBsYXc6IHRoYXQgaXMgdG8gc2F5LCBhCndvcmsgY29udGFpbmluZyB0aGUgUHJvZ3JhbSBvciBh -IHBvcnRpb24gb2YgaXQsIGVpdGhlciB2ZXJiYXRpbSBvciB3aXRoCm1vZGlmaWNhdGlvbnMgYW5k -L29yIHRyYW5zbGF0ZWQgaW50byBhbm90aGVyIGxhbmd1YWdlLiAgKEhlcmVpbmFmdGVyLAp0cmFu -c2xhdGlvbiBpcyBpbmNsdWRlZCB3aXRob3V0IGxpbWl0YXRpb24gaW4gdGhlIHRlcm0gYGBtb2Rp -ZmljYXRpb24nJy4pCkVhY2ggbGljZW5zZWUgaXMgYWRkcmVzc2VkIGFzIGBgeW91JycuCgpBY3Rp -dml0aWVzIG90aGVyIHRoYW4gY29weWluZywgZGlzdHJpYnV0aW9uIGFuZCBtb2RpZmljYXRpb24g -YXJlIG5vdApjb3ZlcmVkIGJ5IHRoaXMgTGljZW5zZTsgdGhleSBhcmUgb3V0c2lkZSBpdHMgc2Nv -cGUuICBUaGUgYWN0IG9mCnJ1bm5pbmcgdGhlIFByb2dyYW0gaXMgbm90IHJlc3RyaWN0ZWQsIGFu -ZCB0aGUgb3V0cHV0IGZyb20gdGhlIFByb2dyYW0KaXMgY292ZXJlZCBvbmx5IGlmIGl0cyBjb250 -ZW50cyBjb25zdGl0dXRlIGEgd29yayBiYXNlZCBvbiB0aGUKUHJvZ3JhbSAoaW5kZXBlbmRlbnQg -b2YgaGF2aW5nIGJlZW4gbWFkZSBieSBydW5uaW5nIHRoZSBQcm9ncmFtKS4KV2hldGhlciB0aGF0 -IGlzIHRydWUgZGVwZW5kcyBvbiB3aGF0IHRoZSBQcm9ncmFtIGRvZXMuCgpcaXRlbSBZb3UgbWF5 -IGNvcHkgYW5kIGRpc3RyaWJ1dGUgdmVyYmF0aW0gY29waWVzIG9mIHRoZSBQcm9ncmFtJ3Mgc291 -cmNlCiAgY29kZSBhcyB5b3UgcmVjZWl2ZSBpdCwgaW4gYW55IG1lZGl1bSwgcHJvdmlkZWQgdGhh -dCB5b3UgY29uc3BpY3VvdXNseQogIGFuZCBhcHByb3ByaWF0ZWx5IHB1Ymxpc2ggb24gZWFjaCBj -b3B5IGFuIGFwcHJvcHJpYXRlIGNvcHlyaWdodCBub3RpY2UKICBhbmQgZGlzY2xhaW1lciBvZiB3 -YXJyYW50eTsga2VlcCBpbnRhY3QgYWxsIHRoZSBub3RpY2VzIHRoYXQgcmVmZXIgdG8KICB0aGlz -IExpY2Vuc2UgYW5kIHRvIHRoZSBhYnNlbmNlIG9mIGFueSB3YXJyYW50eTsgYW5kIGdpdmUgYW55 -IG90aGVyCiAgcmVjaXBpZW50cyBvZiB0aGUgUHJvZ3JhbSBhIGNvcHkgb2YgdGhpcyBMaWNlbnNl -IGFsb25nIHdpdGggdGhlIFByb2dyYW0uCgpZb3UgbWF5IGNoYXJnZSBhIGZlZSBmb3IgdGhlIHBo -eXNpY2FsIGFjdCBvZiB0cmFuc2ZlcnJpbmcgYSBjb3B5LCBhbmQgeW91Cm1heSBhdCB5b3VyIG9w -dGlvbiBvZmZlciB3YXJyYW50eSBwcm90ZWN0aW9uIGluIGV4Y2hhbmdlIGZvciBhIGZlZS4KClxp -dGVtCgpZb3UgbWF5IG1vZGlmeSB5b3VyIGNvcHkgb3IgY29waWVzIG9mIHRoZSBQcm9ncmFtIG9y -IGFueSBwb3J0aW9uCm9mIGl0LCB0aHVzIGZvcm1pbmcgYSB3b3JrIGJhc2VkIG9uIHRoZSBQcm9n -cmFtLCBhbmQgY29weSBhbmQKZGlzdHJpYnV0ZSBzdWNoIG1vZGlmaWNhdGlvbnMgb3Igd29yayB1 -bmRlciB0aGUgdGVybXMgb2YgU2VjdGlvbiAxCmFib3ZlLCBwcm92aWRlZCB0aGF0IHlvdSBhbHNv -IG1lZXQgYWxsIG9mIHRoZXNlIGNvbmRpdGlvbnM6CgpcYmVnaW57ZW51bWVyYXRlfQoKXGl0ZW0g -CgpZb3UgbXVzdCBjYXVzZSB0aGUgbW9kaWZpZWQgZmlsZXMgdG8gY2FycnkgcHJvbWluZW50IG5v -dGljZXMgc3RhdGluZyB0aGF0CnlvdSBjaGFuZ2VkIHRoZSBmaWxlcyBhbmQgdGhlIGRhdGUgb2Yg -YW55IGNoYW5nZS4KClxpdGVtCgpZb3UgbXVzdCBjYXVzZSBhbnkgd29yayB0aGF0IHlvdSBkaXN0 -cmlidXRlIG9yIHB1Ymxpc2gsIHRoYXQgaW4Kd2hvbGUgb3IgaW4gcGFydCBjb250YWlucyBvciBp -cyBkZXJpdmVkIGZyb20gdGhlIFByb2dyYW0gb3IgYW55CnBhcnQgdGhlcmVvZiwgdG8gYmUgbGlj -ZW5zZWQgYXMgYSB3aG9sZSBhdCBubyBjaGFyZ2UgdG8gYWxsIHRoaXJkCnBhcnRpZXMgdW5kZXIg -dGhlIHRlcm1zIG9mIHRoaXMgTGljZW5zZS4KClxpdGVtCklmIHRoZSBtb2RpZmllZCBwcm9ncmFt -IG5vcm1hbGx5IHJlYWRzIGNvbW1hbmRzIGludGVyYWN0aXZlbHkKd2hlbiBydW4sIHlvdSBtdXN0 -IGNhdXNlIGl0LCB3aGVuIHN0YXJ0ZWQgcnVubmluZyBmb3Igc3VjaAppbnRlcmFjdGl2ZSB1c2Ug -aW4gdGhlIG1vc3Qgb3JkaW5hcnkgd2F5LCB0byBwcmludCBvciBkaXNwbGF5IGFuCmFubm91bmNl -bWVudCBpbmNsdWRpbmcgYW4gYXBwcm9wcmlhdGUgY29weXJpZ2h0IG5vdGljZSBhbmQgYQpub3Rp -Y2UgdGhhdCB0aGVyZSBpcyBubyB3YXJyYW50eSAob3IgZWxzZSwgc2F5aW5nIHRoYXQgeW91IHBy -b3ZpZGUKYSB3YXJyYW50eSkgYW5kIHRoYXQgdXNlcnMgbWF5IHJlZGlzdHJpYnV0ZSB0aGUgcHJv -Z3JhbSB1bmRlcgp0aGVzZSBjb25kaXRpb25zLCBhbmQgdGVsbGluZyB0aGUgdXNlciBob3cgdG8g -dmlldyBhIGNvcHkgb2YgdGhpcwpMaWNlbnNlLiAgKEV4Y2VwdGlvbjogaWYgdGhlIFByb2dyYW0g -aXRzZWxmIGlzIGludGVyYWN0aXZlIGJ1dApkb2VzIG5vdCBub3JtYWxseSBwcmludCBzdWNoIGFu -IGFubm91bmNlbWVudCwgeW91ciB3b3JrIGJhc2VkIG9uCnRoZSBQcm9ncmFtIGlzIG5vdCByZXF1 -aXJlZCB0byBwcmludCBhbiBhbm5vdW5jZW1lbnQuKQoKXGVuZHtlbnVtZXJhdGV9CgoKVGhlc2Ug -cmVxdWlyZW1lbnRzIGFwcGx5IHRvIHRoZSBtb2RpZmllZCB3b3JrIGFzIGEgd2hvbGUuICBJZgpp -ZGVudGlmaWFibGUgc2VjdGlvbnMgb2YgdGhhdCB3b3JrIGFyZSBub3QgZGVyaXZlZCBmcm9tIHRo -ZSBQcm9ncmFtLAphbmQgY2FuIGJlIHJlYXNvbmFibHkgY29uc2lkZXJlZCBpbmRlcGVuZGVudCBh -bmQgc2VwYXJhdGUgd29ya3MgaW4KdGhlbXNlbHZlcywgdGhlbiB0aGlzIExpY2Vuc2UsIGFuZCBp -dHMgdGVybXMsIGRvIG5vdCBhcHBseSB0byB0aG9zZQpzZWN0aW9ucyB3aGVuIHlvdSBkaXN0cmli -dXRlIHRoZW0gYXMgc2VwYXJhdGUgd29ya3MuICBCdXQgd2hlbiB5b3UKZGlzdHJpYnV0ZSB0aGUg -c2FtZSBzZWN0aW9ucyBhcyBwYXJ0IG9mIGEgd2hvbGUgd2hpY2ggaXMgYSB3b3JrIGJhc2VkCm9u -IHRoZSBQcm9ncmFtLCB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSB3aG9sZSBtdXN0IGJlIG9uIHRo -ZSB0ZXJtcyBvZgp0aGlzIExpY2Vuc2UsIHdob3NlIHBlcm1pc3Npb25zIGZvciBvdGhlciBsaWNl -bnNlZXMgZXh0ZW5kIHRvIHRoZQplbnRpcmUgd2hvbGUsIGFuZCB0aHVzIHRvIGVhY2ggYW5kIGV2 -ZXJ5IHBhcnQgcmVnYXJkbGVzcyBvZiB3aG8gd3JvdGUgaXQuCgpUaHVzLCBpdCBpcyBub3QgdGhl -IGludGVudCBvZiB0aGlzIHNlY3Rpb24gdG8gY2xhaW0gcmlnaHRzIG9yIGNvbnRlc3QKeW91ciBy -aWdodHMgdG8gd29yayB3cml0dGVuIGVudGlyZWx5IGJ5IHlvdTsgcmF0aGVyLCB0aGUgaW50ZW50 -IGlzIHRvCmV4ZXJjaXNlIHRoZSByaWdodCB0byBjb250cm9sIHRoZSBkaXN0cmlidXRpb24gb2Yg -ZGVyaXZhdGl2ZSBvcgpjb2xsZWN0aXZlIHdvcmtzIGJhc2VkIG9uIHRoZSBQcm9ncmFtLgoKSW4g -YWRkaXRpb24sIG1lcmUgYWdncmVnYXRpb24gb2YgYW5vdGhlciB3b3JrIG5vdCBiYXNlZCBvbiB0 -aGUgUHJvZ3JhbQp3aXRoIHRoZSBQcm9ncmFtIChvciB3aXRoIGEgd29yayBiYXNlZCBvbiB0aGUg -UHJvZ3JhbSkgb24gYSB2b2x1bWUgb2YKYSBzdG9yYWdlIG9yIGRpc3RyaWJ1dGlvbiBtZWRpdW0g -ZG9lcyBub3QgYnJpbmcgdGhlIG90aGVyIHdvcmsgdW5kZXIKdGhlIHNjb3BlIG9mIHRoaXMgTGlj -ZW5zZS4KClxpdGVtCllvdSBtYXkgY29weSBhbmQgZGlzdHJpYnV0ZSB0aGUgUHJvZ3JhbSAob3Ig -YSB3b3JrIGJhc2VkIG9uIGl0LAp1bmRlciBTZWN0aW9uIDIpIGluIG9iamVjdCBjb2RlIG9yIGV4 -ZWN1dGFibGUgZm9ybSB1bmRlciB0aGUgdGVybXMgb2YKU2VjdGlvbnMgMSBhbmQgMiBhYm92ZSBw -cm92aWRlZCB0aGF0IHlvdSBhbHNvIGRvIG9uZSBvZiB0aGUgZm9sbG93aW5nOgoKXGJlZ2lue2Vu -dW1lcmF0ZX0KClxpdGVtCgpBY2NvbXBhbnkgaXQgd2l0aCB0aGUgY29tcGxldGUgY29ycmVzcG9u -ZGluZyBtYWNoaW5lLXJlYWRhYmxlCnNvdXJjZSBjb2RlLCB3aGljaCBtdXN0IGJlIGRpc3RyaWJ1 -dGVkIHVuZGVyIHRoZSB0ZXJtcyBvZiBTZWN0aW9ucwoxIGFuZCAyIGFib3ZlIG9uIGEgbWVkaXVt -IGN1c3RvbWFyaWx5IHVzZWQgZm9yIHNvZnR3YXJlIGludGVyY2hhbmdlOyBvciwKClxpdGVtCgpB -Y2NvbXBhbnkgaXQgd2l0aCBhIHdyaXR0ZW4gb2ZmZXIsIHZhbGlkIGZvciBhdCBsZWFzdCB0aHJl -ZQp5ZWFycywgdG8gZ2l2ZSBhbnkgdGhpcmQgcGFydHksIGZvciBhIGNoYXJnZSBubyBtb3JlIHRo -YW4geW91cgpjb3N0IG9mIHBoeXNpY2FsbHkgcGVyZm9ybWluZyBzb3VyY2UgZGlzdHJpYnV0aW9u -LCBhIGNvbXBsZXRlCm1hY2hpbmUtcmVhZGFibGUgY29weSBvZiB0aGUgY29ycmVzcG9uZGluZyBz -b3VyY2UgY29kZSwgdG8gYmUKZGlzdHJpYnV0ZWQgdW5kZXIgdGhlIHRlcm1zIG9mIFNlY3Rpb25z -IDEgYW5kIDIgYWJvdmUgb24gYSBtZWRpdW0KY3VzdG9tYXJpbHkgdXNlZCBmb3Igc29mdHdhcmUg -aW50ZXJjaGFuZ2U7IG9yLAoKXGl0ZW0KCkFjY29tcGFueSBpdCB3aXRoIHRoZSBpbmZvcm1hdGlv -biB5b3UgcmVjZWl2ZWQgYXMgdG8gdGhlIG9mZmVyCnRvIGRpc3RyaWJ1dGUgY29ycmVzcG9uZGlu -ZyBzb3VyY2UgY29kZS4gIChUaGlzIGFsdGVybmF0aXZlIGlzCmFsbG93ZWQgb25seSBmb3Igbm9u -Y29tbWVyY2lhbCBkaXN0cmlidXRpb24gYW5kIG9ubHkgaWYgeW91CnJlY2VpdmVkIHRoZSBwcm9n -cmFtIGluIG9iamVjdCBjb2RlIG9yIGV4ZWN1dGFibGUgZm9ybSB3aXRoIHN1Y2gKYW4gb2ZmZXIs -IGluIGFjY29yZCB3aXRoIFN1YnNlY3Rpb24gYiBhYm92ZS4pCgpcZW5ke2VudW1lcmF0ZX0KCgpU -aGUgc291cmNlIGNvZGUgZm9yIGEgd29yayBtZWFucyB0aGUgcHJlZmVycmVkIGZvcm0gb2YgdGhl -IHdvcmsgZm9yCm1ha2luZyBtb2RpZmljYXRpb25zIHRvIGl0LiAgRm9yIGFuIGV4ZWN1dGFibGUg -d29yaywgY29tcGxldGUgc291cmNlCmNvZGUgbWVhbnMgYWxsIHRoZSBzb3VyY2UgY29kZSBmb3Ig -YWxsIG1vZHVsZXMgaXQgY29udGFpbnMsIHBsdXMgYW55CmFzc29jaWF0ZWQgaW50ZXJmYWNlIGRl -ZmluaXRpb24gZmlsZXMsIHBsdXMgdGhlIHNjcmlwdHMgdXNlZCB0bwpjb250cm9sIGNvbXBpbGF0 -aW9uIGFuZCBpbnN0YWxsYXRpb24gb2YgdGhlIGV4ZWN1dGFibGUuICBIb3dldmVyLCBhcyBhCnNw -ZWNpYWwgZXhjZXB0aW9uLCB0aGUgc291cmNlIGNvZGUgZGlzdHJpYnV0ZWQgbmVlZCBub3QgaW5j -bHVkZQphbnl0aGluZyB0aGF0IGlzIG5vcm1hbGx5IGRpc3RyaWJ1dGVkIChpbiBlaXRoZXIgc291 -cmNlIG9yIGJpbmFyeQpmb3JtKSB3aXRoIHRoZSBtYWpvciBjb21wb25lbnRzIChjb21waWxlciwg -a2VybmVsLCBhbmQgc28gb24pIG9mIHRoZQpvcGVyYXRpbmcgc3lzdGVtIG9uIHdoaWNoIHRoZSBl -eGVjdXRhYmxlIHJ1bnMsIHVubGVzcyB0aGF0IGNvbXBvbmVudAppdHNlbGYgYWNjb21wYW5pZXMg -dGhlIGV4ZWN1dGFibGUuCgpJZiBkaXN0cmlidXRpb24gb2YgZXhlY3V0YWJsZSBvciBvYmplY3Qg -Y29kZSBpcyBtYWRlIGJ5IG9mZmVyaW5nCmFjY2VzcyB0byBjb3B5IGZyb20gYSBkZXNpZ25hdGVk -IHBsYWNlLCB0aGVuIG9mZmVyaW5nIGVxdWl2YWxlbnQKYWNjZXNzIHRvIGNvcHkgdGhlIHNvdXJj -ZSBjb2RlIGZyb20gdGhlIHNhbWUgcGxhY2UgY291bnRzIGFzCmRpc3RyaWJ1dGlvbiBvZiB0aGUg -c291cmNlIGNvZGUsIGV2ZW4gdGhvdWdoIHRoaXJkIHBhcnRpZXMgYXJlIG5vdApjb21wZWxsZWQg -dG8gY29weSB0aGUgc291cmNlIGFsb25nIHdpdGggdGhlIG9iamVjdCBjb2RlLgoKXGl0ZW0KWW91 -IG1heSBub3QgY29weSwgbW9kaWZ5LCBzdWJsaWNlbnNlLCBvciBkaXN0cmlidXRlIHRoZSBQcm9n -cmFtCmV4Y2VwdCBhcyBleHByZXNzbHkgcHJvdmlkZWQgdW5kZXIgdGhpcyBMaWNlbnNlLiAgQW55 -IGF0dGVtcHQKb3RoZXJ3aXNlIHRvIGNvcHksIG1vZGlmeSwgc3VibGljZW5zZSBvciBkaXN0cmli -dXRlIHRoZSBQcm9ncmFtIGlzCnZvaWQsIGFuZCB3aWxsIGF1dG9tYXRpY2FsbHkgdGVybWluYXRl -IHlvdXIgcmlnaHRzIHVuZGVyIHRoaXMgTGljZW5zZS4KSG93ZXZlciwgcGFydGllcyB3aG8gaGF2 -ZSByZWNlaXZlZCBjb3BpZXMsIG9yIHJpZ2h0cywgZnJvbSB5b3UgdW5kZXIKdGhpcyBMaWNlbnNl -IHdpbGwgbm90IGhhdmUgdGhlaXIgbGljZW5zZXMgdGVybWluYXRlZCBzbyBsb25nIGFzIHN1Y2gK -cGFydGllcyByZW1haW4gaW4gZnVsbCBjb21wbGlhbmNlLgoKXGl0ZW0KWW91IGFyZSBub3QgcmVx -dWlyZWQgdG8gYWNjZXB0IHRoaXMgTGljZW5zZSwgc2luY2UgeW91IGhhdmUgbm90CnNpZ25lZCBp -dC4gIEhvd2V2ZXIsIG5vdGhpbmcgZWxzZSBncmFudHMgeW91IHBlcm1pc3Npb24gdG8gbW9kaWZ5 -IG9yCmRpc3RyaWJ1dGUgdGhlIFByb2dyYW0gb3IgaXRzIGRlcml2YXRpdmUgd29ya3MuICBUaGVz -ZSBhY3Rpb25zIGFyZQpwcm9oaWJpdGVkIGJ5IGxhdyBpZiB5b3UgZG8gbm90IGFjY2VwdCB0aGlz -IExpY2Vuc2UuICBUaGVyZWZvcmUsIGJ5Cm1vZGlmeWluZyBvciBkaXN0cmlidXRpbmcgdGhlIFBy -b2dyYW0gKG9yIGFueSB3b3JrIGJhc2VkIG9uIHRoZQpQcm9ncmFtKSwgeW91IGluZGljYXRlIHlv -dXIgYWNjZXB0YW5jZSBvZiB0aGlzIExpY2Vuc2UgdG8gZG8gc28sIGFuZAphbGwgaXRzIHRlcm1z -IGFuZCBjb25kaXRpb25zIGZvciBjb3B5aW5nLCBkaXN0cmlidXRpbmcgb3IgbW9kaWZ5aW5nCnRo -ZSBQcm9ncmFtIG9yIHdvcmtzIGJhc2VkIG9uIGl0LgoKXGl0ZW0KRWFjaCB0aW1lIHlvdSByZWRp -c3RyaWJ1dGUgdGhlIFByb2dyYW0gKG9yIGFueSB3b3JrIGJhc2VkIG9uIHRoZQpQcm9ncmFtKSwg -dGhlIHJlY2lwaWVudCBhdXRvbWF0aWNhbGx5IHJlY2VpdmVzIGEgbGljZW5zZSBmcm9tIHRoZQpv -cmlnaW5hbCBsaWNlbnNvciB0byBjb3B5LCBkaXN0cmlidXRlIG9yIG1vZGlmeSB0aGUgUHJvZ3Jh -bSBzdWJqZWN0IHRvCnRoZXNlIHRlcm1zIGFuZCBjb25kaXRpb25zLiAgWW91IG1heSBub3QgaW1w -b3NlIGFueSBmdXJ0aGVyCnJlc3RyaWN0aW9ucyBvbiB0aGUgcmVjaXBpZW50cycgZXhlcmNpc2Ug -b2YgdGhlIHJpZ2h0cyBncmFudGVkIGhlcmVpbi4KWW91IGFyZSBub3QgcmVzcG9uc2libGUgZm9y -IGVuZm9yY2luZyBjb21wbGlhbmNlIGJ5IHRoaXJkIHBhcnRpZXMgdG8KdGhpcyBMaWNlbnNlLgoK -XGl0ZW0KSWYsIGFzIGEgY29uc2VxdWVuY2Ugb2YgYSBjb3VydCBqdWRnbWVudCBvciBhbGxlZ2F0 -aW9uIG9mIHBhdGVudAppbmZyaW5nZW1lbnQgb3IgZm9yIGFueSBvdGhlciByZWFzb24gKG5vdCBs -aW1pdGVkIHRvIHBhdGVudCBpc3N1ZXMpLApjb25kaXRpb25zIGFyZSBpbXBvc2VkIG9uIHlvdSAo -d2hldGhlciBieSBjb3VydCBvcmRlciwgYWdyZWVtZW50IG9yCm90aGVyd2lzZSkgdGhhdCBjb250 -cmFkaWN0IHRoZSBjb25kaXRpb25zIG9mIHRoaXMgTGljZW5zZSwgdGhleSBkbyBub3QKZXhjdXNl -IHlvdSBmcm9tIHRoZSBjb25kaXRpb25zIG9mIHRoaXMgTGljZW5zZS4gIElmIHlvdSBjYW5ub3QK -ZGlzdHJpYnV0ZSBzbyBhcyB0byBzYXRpc2Z5IHNpbXVsdGFuZW91c2x5IHlvdXIgb2JsaWdhdGlv -bnMgdW5kZXIgdGhpcwpMaWNlbnNlIGFuZCBhbnkgb3RoZXIgcGVydGluZW50IG9ibGlnYXRpb25z -LCB0aGVuIGFzIGEgY29uc2VxdWVuY2UgeW91Cm1heSBub3QgZGlzdHJpYnV0ZSB0aGUgUHJvZ3Jh -bSBhdCBhbGwuICBGb3IgZXhhbXBsZSwgaWYgYSBwYXRlbnQKbGljZW5zZSB3b3VsZCBub3QgcGVy -bWl0IHJveWFsdHktZnJlZSByZWRpc3RyaWJ1dGlvbiBvZiB0aGUgUHJvZ3JhbSBieQphbGwgdGhv -c2Ugd2hvIHJlY2VpdmUgY29waWVzIGRpcmVjdGx5IG9yIGluZGlyZWN0bHkgdGhyb3VnaCB5b3Us -IHRoZW4KdGhlIG9ubHkgd2F5IHlvdSBjb3VsZCBzYXRpc2Z5IGJvdGggaXQgYW5kIHRoaXMgTGlj -ZW5zZSB3b3VsZCBiZSB0bwpyZWZyYWluIGVudGlyZWx5IGZyb20gZGlzdHJpYnV0aW9uIG9mIHRo -ZSBQcm9ncmFtLgoKSWYgYW55IHBvcnRpb24gb2YgdGhpcyBzZWN0aW9uIGlzIGhlbGQgaW52YWxp -ZCBvciB1bmVuZm9yY2VhYmxlIHVuZGVyCmFueSBwYXJ0aWN1bGFyIGNpcmN1bXN0YW5jZSwgdGhl -IGJhbGFuY2Ugb2YgdGhlIHNlY3Rpb24gaXMgaW50ZW5kZWQgdG8KYXBwbHkgYW5kIHRoZSBzZWN0 -aW9uIGFzIGEgd2hvbGUgaXMgaW50ZW5kZWQgdG8gYXBwbHkgaW4gb3RoZXIKY2lyY3Vtc3RhbmNl -cy4KCkl0IGlzIG5vdCB0aGUgcHVycG9zZSBvZiB0aGlzIHNlY3Rpb24gdG8gaW5kdWNlIHlvdSB0 -byBpbmZyaW5nZSBhbnkKcGF0ZW50cyBvciBvdGhlciBwcm9wZXJ0eSByaWdodCBjbGFpbXMgb3Ig -dG8gY29udGVzdCB2YWxpZGl0eSBvZiBhbnkKc3VjaCBjbGFpbXM7IHRoaXMgc2VjdGlvbiBoYXMg -dGhlIHNvbGUgcHVycG9zZSBvZiBwcm90ZWN0aW5nIHRoZQppbnRlZ3JpdHkgb2YgdGhlIGZyZWUg -c29mdHdhcmUgZGlzdHJpYnV0aW9uIHN5c3RlbSwgd2hpY2ggaXMKaW1wbGVtZW50ZWQgYnkgcHVi -bGljIGxpY2Vuc2UgcHJhY3RpY2VzLiAgTWFueSBwZW9wbGUgaGF2ZSBtYWRlCmdlbmVyb3VzIGNv -bnRyaWJ1dGlvbnMgdG8gdGhlIHdpZGUgcmFuZ2Ugb2Ygc29mdHdhcmUgZGlzdHJpYnV0ZWQKdGhy -b3VnaCB0aGF0IHN5c3RlbSBpbiByZWxpYW5jZSBvbiBjb25zaXN0ZW50IGFwcGxpY2F0aW9uIG9m -IHRoYXQKc3lzdGVtOyBpdCBpcyB1cCB0byB0aGUgYXV0aG9yL2Rvbm9yIHRvIGRlY2lkZSBpZiBo -ZSBvciBzaGUgaXMgd2lsbGluZwp0byBkaXN0cmlidXRlIHNvZnR3YXJlIHRocm91Z2ggYW55IG90 -aGVyIHN5c3RlbSBhbmQgYSBsaWNlbnNlZSBjYW5ub3QKaW1wb3NlIHRoYXQgY2hvaWNlLgoKVGhp -cyBzZWN0aW9uIGlzIGludGVuZGVkIHRvIG1ha2UgdGhvcm91Z2hseSBjbGVhciB3aGF0IGlzIGJl -bGlldmVkIHRvCmJlIGEgY29uc2VxdWVuY2Ugb2YgdGhlIHJlc3Qgb2YgdGhpcyBMaWNlbnNlLgoK -XGl0ZW0KSWYgdGhlIGRpc3RyaWJ1dGlvbiBhbmQvb3IgdXNlIG9mIHRoZSBQcm9ncmFtIGlzIHJl -c3RyaWN0ZWQgaW4KY2VydGFpbiBjb3VudHJpZXMgZWl0aGVyIGJ5IHBhdGVudHMgb3IgYnkgY29w -eXJpZ2h0ZWQgaW50ZXJmYWNlcywgdGhlCm9yaWdpbmFsIGNvcHlyaWdodCBob2xkZXIgd2hvIHBs -YWNlcyB0aGUgUHJvZ3JhbSB1bmRlciB0aGlzIExpY2Vuc2UKbWF5IGFkZCBhbiBleHBsaWNpdCBn -ZW9ncmFwaGljYWwgZGlzdHJpYnV0aW9uIGxpbWl0YXRpb24gZXhjbHVkaW5nCnRob3NlIGNvdW50 -cmllcywgc28gdGhhdCBkaXN0cmlidXRpb24gaXMgcGVybWl0dGVkIG9ubHkgaW4gb3IgYW1vbmcK -Y291bnRyaWVzIG5vdCB0aHVzIGV4Y2x1ZGVkLiAgSW4gc3VjaCBjYXNlLCB0aGlzIExpY2Vuc2Ug -aW5jb3Jwb3JhdGVzCnRoZSBsaW1pdGF0aW9uIGFzIGlmIHdyaXR0ZW4gaW4gdGhlIGJvZHkgb2Yg -dGhpcyBMaWNlbnNlLgoKXGl0ZW0KVGhlIEZyZWUgU29mdHdhcmUgRm91bmRhdGlvbiBtYXkgcHVi -bGlzaCByZXZpc2VkIGFuZC9vciBuZXcgdmVyc2lvbnMKb2YgdGhlIEdlbmVyYWwgUHVibGljIExp -Y2Vuc2UgZnJvbSB0aW1lIHRvIHRpbWUuICBTdWNoIG5ldyB2ZXJzaW9ucyB3aWxsCmJlIHNpbWls -YXIgaW4gc3Bpcml0IHRvIHRoZSBwcmVzZW50IHZlcnNpb24sIGJ1dCBtYXkgZGlmZmVyIGluIGRl -dGFpbCB0bwphZGRyZXNzIG5ldyBwcm9ibGVtcyBvciBjb25jZXJucy4KCkVhY2ggdmVyc2lvbiBp -cyBnaXZlbiBhIGRpc3Rpbmd1aXNoaW5nIHZlcnNpb24gbnVtYmVyLiAgSWYgdGhlIFByb2dyYW0K -c3BlY2lmaWVzIGEgdmVyc2lvbiBudW1iZXIgb2YgdGhpcyBMaWNlbnNlIHdoaWNoIGFwcGxpZXMg -dG8gaXQgYW5kIGBgYW55CmxhdGVyIHZlcnNpb24nJywgeW91IGhhdmUgdGhlIG9wdGlvbiBvZiBm -b2xsb3dpbmcgdGhlIHRlcm1zIGFuZCBjb25kaXRpb25zCmVpdGhlciBvZiB0aGF0IHZlcnNpb24g -b3Igb2YgYW55IGxhdGVyIHZlcnNpb24gcHVibGlzaGVkIGJ5IHRoZSBGcmVlClNvZnR3YXJlIEZv -dW5kYXRpb24uICBJZiB0aGUgUHJvZ3JhbSBkb2VzIG5vdCBzcGVjaWZ5IGEgdmVyc2lvbiBudW1i -ZXIgb2YKdGhpcyBMaWNlbnNlLCB5b3UgbWF5IGNob29zZSBhbnkgdmVyc2lvbiBldmVyIHB1Ymxp -c2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZQpGb3VuZGF0aW9uLgoKXGl0ZW0KSWYgeW91IHdpc2gg -dG8gaW5jb3Jwb3JhdGUgcGFydHMgb2YgdGhlIFByb2dyYW0gaW50byBvdGhlciBmcmVlCnByb2dy -YW1zIHdob3NlIGRpc3RyaWJ1dGlvbiBjb25kaXRpb25zIGFyZSBkaWZmZXJlbnQsIHdyaXRlIHRv -IHRoZSBhdXRob3IKdG8gYXNrIGZvciBwZXJtaXNzaW9uLiAgRm9yIHNvZnR3YXJlIHdoaWNoIGlz -IGNvcHlyaWdodGVkIGJ5IHRoZSBGcmVlClNvZnR3YXJlIEZvdW5kYXRpb24sIHdyaXRlIHRvIHRo -ZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb247IHdlIHNvbWV0aW1lcwptYWtlIGV4Y2VwdGlvbnMg -Zm9yIHRoaXMuICBPdXIgZGVjaXNpb24gd2lsbCBiZSBndWlkZWQgYnkgdGhlIHR3byBnb2Fscwpv -ZiBwcmVzZXJ2aW5nIHRoZSBmcmVlIHN0YXR1cyBvZiBhbGwgZGVyaXZhdGl2ZXMgb2Ygb3VyIGZy -ZWUgc29mdHdhcmUgYW5kCm9mIHByb21vdGluZyB0aGUgc2hhcmluZyBhbmQgcmV1c2Ugb2Ygc29m -dHdhcmUgZ2VuZXJhbGx5LgoKXGJlZ2lue2NlbnRlcn0Ke1xMYXJnZVxzYwpObyBXYXJyYW50eQp9 -ClxlbmR7Y2VudGVyfQoKXGl0ZW0Ke1xzYyBCZWNhdXNlIHRoZSBwcm9ncmFtIGlzIGxpY2Vuc2Vk -IGZyZWUgb2YgY2hhcmdlLCB0aGVyZSBpcyBubyB3YXJyYW50eQpmb3IgdGhlIHByb2dyYW0sIHRv -IHRoZSBleHRlbnQgcGVybWl0dGVkIGJ5IGFwcGxpY2FibGUgbGF3LiAgRXhjZXB0IHdoZW4Kb3Ro -ZXJ3aXNlIHN0YXRlZCBpbiB3cml0aW5nIHRoZSBjb3B5cmlnaHQgaG9sZGVycyBhbmQvb3Igb3Ro -ZXIgcGFydGllcwpwcm92aWRlIHRoZSBwcm9ncmFtIGBgYXMgaXMnJyB3aXRob3V0IHdhcnJhbnR5 -IG9mIGFueSBraW5kLCBlaXRoZXIgZXhwcmVzc2VkCm9yIGltcGxpZWQsIGluY2x1ZGluZywgYnV0 -IG5vdCBsaW1pdGVkIHRvLCB0aGUgaW1wbGllZCB3YXJyYW50aWVzIG9mCm1lcmNoYW50YWJpbGl0 -eSBhbmQgZml0bmVzcyBmb3IgYSBwYXJ0aWN1bGFyIHB1cnBvc2UuICBUaGUgZW50aXJlIHJpc2sg -YXMKdG8gdGhlIHF1YWxpdHkgYW5kIHBlcmZvcm1hbmNlIG9mIHRoZSBwcm9ncmFtIGlzIHdpdGgg -eW91LiAgU2hvdWxkIHRoZQpwcm9ncmFtIHByb3ZlIGRlZmVjdGl2ZSwgeW91IGFzc3VtZSB0aGUg -Y29zdCBvZiBhbGwgbmVjZXNzYXJ5IHNlcnZpY2luZywKcmVwYWlyIG9yIGNvcnJlY3Rpb24ufQoK -XGl0ZW0Ke1xzYyBJbiBubyBldmVudCB1bmxlc3MgcmVxdWlyZWQgYnkgYXBwbGljYWJsZSBsYXcg -b3IgYWdyZWVkIHRvIGluIHdyaXRpbmcKd2lsbCBhbnkgY29weXJpZ2h0IGhvbGRlciwgb3IgYW55 -IG90aGVyIHBhcnR5IHdobyBtYXkgbW9kaWZ5IGFuZC9vcgpyZWRpc3RyaWJ1dGUgdGhlIHByb2dy -YW0gYXMgcGVybWl0dGVkIGFib3ZlLCBiZSBsaWFibGUgdG8geW91IGZvciBkYW1hZ2VzLAppbmNs -dWRpbmcgYW55IGdlbmVyYWwsIHNwZWNpYWwsIGluY2lkZW50YWwgb3IgY29uc2VxdWVudGlhbCBk -YW1hZ2VzIGFyaXNpbmcKb3V0IG9mIHRoZSB1c2Ugb3IgaW5hYmlsaXR5IHRvIHVzZSB0aGUgcHJv -Z3JhbSAoaW5jbHVkaW5nIGJ1dCBub3QgbGltaXRlZAp0byBsb3NzIG9mIGRhdGEgb3IgZGF0YSBi -ZWluZyByZW5kZXJlZCBpbmFjY3VyYXRlIG9yIGxvc3NlcyBzdXN0YWluZWQgYnkKeW91IG9yIHRo -aXJkIHBhcnRpZXMgb3IgYSBmYWlsdXJlIG9mIHRoZSBwcm9ncmFtIHRvIG9wZXJhdGUgd2l0aCBh -bnkgb3RoZXIKcHJvZ3JhbXMpLCBldmVuIGlmIHN1Y2ggaG9sZGVyIG9yIG90aGVyIHBhcnR5IGhh -cyBiZWVuIGFkdmlzZWQgb2YgdGhlCnBvc3NpYmlsaXR5IG9mIHN1Y2ggZGFtYWdlcy59CgpcZW5k -e2VudW1lcmF0ZX0KCgpcYmVnaW57Y2VudGVyfQp7XExhcmdlXHNjIEVuZCBvZiBUZXJtcyBhbmQg -Q29uZGl0aW9uc30KXGVuZHtjZW50ZXJ9CgoKXHBhZ2VicmVha1syXQoKXHNlY3Rpb24qe0FwcGVu -ZGl4OiBIb3cgdG8gQXBwbHkgVGhlc2UgVGVybXMgdG8gWW91ciBOZXcgUHJvZ3JhbXN9CgpJZiB5 -b3UgZGV2ZWxvcCBhIG5ldyBwcm9ncmFtLCBhbmQgeW91IHdhbnQgaXQgdG8gYmUgb2YgdGhlIGdy -ZWF0ZXN0CnBvc3NpYmxlIHVzZSB0byB0aGUgcHVibGljLCB0aGUgYmVzdCB3YXkgdG8gYWNoaWV2 -ZSB0aGlzIGlzIHRvIG1ha2UgaXQKZnJlZSBzb2Z0d2FyZSB3aGljaCBldmVyeW9uZSBjYW4gcmVk -aXN0cmlidXRlIGFuZCBjaGFuZ2UgdW5kZXIgdGhlc2UKdGVybXMuCgogIFRvIGRvIHNvLCBhdHRh -Y2ggdGhlIGZvbGxvd2luZyBub3RpY2VzIHRvIHRoZSBwcm9ncmFtLiAgSXQgaXMgc2FmZXN0IHRv -CiAgYXR0YWNoIHRoZW0gdG8gdGhlIHN0YXJ0IG9mIGVhY2ggc291cmNlIGZpbGUgdG8gbW9zdCBl -ZmZlY3RpdmVseSBjb252ZXkKICB0aGUgZXhjbHVzaW9uIG9mIHdhcnJhbnR5OyBhbmQgZWFjaCBm -aWxlIHNob3VsZCBoYXZlIGF0IGxlYXN0IHRoZQogIGBgY29weXJpZ2h0JycgbGluZSBhbmQgYSBw -b2ludGVyIHRvIHdoZXJlIHRoZSBmdWxsIG5vdGljZSBpcyBmb3VuZC4KClxiZWdpbntxdW90ZX0K -b25lIGxpbmUgdG8gZ2l2ZSB0aGUgcHJvZ3JhbSdzIG5hbWUgYW5kIGEgYnJpZWYgaWRlYSBvZiB3 -aGF0IGl0IGRvZXMuIFxcCkNvcHlyaWdodCAoQykgeXl5eSAgbmFtZSBvZiBhdXRob3IgXFwKClRo -aXMgcHJvZ3JhbSBpcyBmcmVlIHNvZnR3YXJlOyB5b3UgY2FuIHJlZGlzdHJpYnV0ZSBpdCBhbmQv -b3IgbW9kaWZ5Cml0IHVuZGVyIHRoZSB0ZXJtcyBvZiB0aGUgR05VIEdlbmVyYWwgUHVibGljIExp -Y2Vuc2UgYXMgcHVibGlzaGVkIGJ5CnRoZSBGcmVlIFNvZnR3YXJlIEZvdW5kYXRpb247IGVpdGhl -ciB2ZXJzaW9uIDIgb2YgdGhlIExpY2Vuc2UsIG9yCihhdCB5b3VyIG9wdGlvbikgYW55IGxhdGVy -IHZlcnNpb24uCgpUaGlzIHByb2dyYW0gaXMgZGlzdHJpYnV0ZWQgaW4gdGhlIGhvcGUgdGhhdCBp -dCB3aWxsIGJlIHVzZWZ1bCwKYnV0IFdJVEhPVVQgQU5ZIFdBUlJBTlRZOyB3aXRob3V0IGV2ZW4g -dGhlIGltcGxpZWQgd2FycmFudHkgb2YKTUVSQ0hBTlRBQklMSVRZIG9yIEZJVE5FU1MgRk9SIEEg -UEFSVElDVUxBUiBQVVJQT1NFLiAgU2VlIHRoZQpHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBm -b3IgbW9yZSBkZXRhaWxzLgoKWW91IHNob3VsZCBoYXZlIHJlY2VpdmVkIGEgY29weSBvZiB0aGUg -R05VIEdlbmVyYWwgUHVibGljIExpY2Vuc2UKYWxvbmcgd2l0aCB0aGlzIHByb2dyYW07IGlmIG5v -dCwgd3JpdGUgdG8gdGhlIEZyZWUgU29mdHdhcmUKRm91bmRhdGlvbiwgSW5jLiwgNTkgVGVtcGxl -IFBsYWNlIC0gU3VpdGUgMzMwLCBCb3N0b24sIE1BICAwMjExMS0xMzA3LCBVU0EuClxlbmR7cXVv -dGV9CgpBbHNvIGFkZCBpbmZvcm1hdGlvbiBvbiBob3cgdG8gY29udGFjdCB5b3UgYnkgZWxlY3Ry -b25pYyBhbmQgcGFwZXIgbWFpbC4KCklmIHRoZSBwcm9ncmFtIGlzIGludGVyYWN0aXZlLCBtYWtl -IGl0IG91dHB1dCBhIHNob3J0IG5vdGljZSBsaWtlIHRoaXMKd2hlbiBpdCBzdGFydHMgaW4gYW4g -aW50ZXJhY3RpdmUgbW9kZToKClxiZWdpbntxdW90ZX0KR25vbW92aXNpb24gdmVyc2lvbiA2OSwg -Q29weXJpZ2h0IChDKSB5eXl5ICBuYW1lIG9mIGF1dGhvciBcXApHbm9tb3Zpc2lvbiBjb21lcyB3 -aXRoIEFCU09MVVRFTFkgTk8gV0FSUkFOVFk7IGZvciBkZXRhaWxzIHR5cGUgYHNob3cgdycuIFxc -ClRoaXMgaXMgZnJlZSBzb2Z0d2FyZSwgYW5kIHlvdSBhcmUgd2VsY29tZSB0byByZWRpc3RyaWJ1 -dGUgaXQKdW5kZXIgY2VydGFpbiBjb25kaXRpb25zOyB0eXBlIGBzaG93IGMnIGZvciBkZXRhaWxz -LgpcZW5ke3F1b3RlfQoKClRoZSBoeXBvdGhldGljYWwgY29tbWFuZHMge1x0dCBzaG93IHd9IGFu -ZCB7XHR0IHNob3cgY30gc2hvdWxkIHNob3cgdGhlCmFwcHJvcHJpYXRlIHBhcnRzIG9mIHRoZSBH -ZW5lcmFsIFB1YmxpYyBMaWNlbnNlLiAgT2YgY291cnNlLCB0aGUgY29tbWFuZHMKeW91IHVzZSBt -YXkgYmUgY2FsbGVkIHNvbWV0aGluZyBvdGhlciB0aGFuIHtcdHQgc2hvdyB3fSBhbmQge1x0dCBz -aG93IGN9Owp0aGV5IGNvdWxkIGV2ZW4gYmUgbW91c2UtY2xpY2tzIG9yIG1lbnUgaXRlbXMtLS13 -aGF0ZXZlciBzdWl0cyB5b3VyCnByb2dyYW0uCgpZb3Ugc2hvdWxkIGFsc28gZ2V0IHlvdXIgZW1w -bG95ZXIgKGlmIHlvdSB3b3JrIGFzIGEgcHJvZ3JhbW1lcikgb3IgeW91cgpzY2hvb2wsIGlmIGFu -eSwgdG8gc2lnbiBhIGBgY29weXJpZ2h0IGRpc2NsYWltZXInJyBmb3IgdGhlIHByb2dyYW0sIGlm -Cm5lY2Vzc2FyeS4gIEhlcmUgaXMgYSBzYW1wbGU7IGFsdGVyIHRoZSBuYW1lczoKClxiZWdpbntx -dW90ZX0KWW95b2R5bmUsIEluYy4sIGhlcmVieSBkaXNjbGFpbXMgYWxsIGNvcHlyaWdodCBpbnRl -cmVzdCBpbiB0aGUgcHJvZ3JhbSBcXApgR25vbW92aXNpb24nICh3aGljaCBtYWtlcyBwYXNzZXMg -YXQgY29tcGlsZXJzKSB3cml0dGVuIGJ5IEphbWVzIEhhY2tlci4gXFwKCnNpZ25hdHVyZSBvZiBU -eSBDb29uLCAxIEFwcmlsIDE5ODkgXFwKVHkgQ29vbiwgUHJlc2lkZW50IG9mIFZpY2UKXGVuZHtx -dW90ZX0KCgpUaGlzIEdlbmVyYWwgUHVibGljIExpY2Vuc2UgZG9lcyBub3QgcGVybWl0IGluY29y -cG9yYXRpbmcgeW91ciBwcm9ncmFtCmludG8gcHJvcHJpZXRhcnkgcHJvZ3JhbXMuICBJZiB5b3Vy -IHByb2dyYW0gaXMgYSBzdWJyb3V0aW5lIGxpYnJhcnksIHlvdQptYXkgY29uc2lkZXIgaXQgbW9y -ZSB1c2VmdWwgdG8gcGVybWl0IGxpbmtpbmcgcHJvcHJpZXRhcnkgYXBwbGljYXRpb25zCndpdGgg -dGhlIGxpYnJhcnkuICBJZiB0aGlzIGlzIHdoYXQgeW91IHdhbnQgdG8gZG8sIHVzZSB0aGUgR05V -IExpYnJhcnkKR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBpbnN0ZWFkIG9mIHRoaXMgTGljZW5zZS4K -Cgo= -" ++ Glurf ++ lists:reverse(Glurf++"kalle"). diff --git a/lib/compiler/test/compilation_SUITE_data/nested_tuples_in_case_expr.erl b/lib/compiler/test/compilation_SUITE_data/nested_tuples_in_case_expr.erl deleted file mode 100644 index bbacb7cf14..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/nested_tuples_in_case_expr.erl +++ /dev/null @@ -1,37 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% 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. -%% 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(nested_tuples_in_case_expr). --export([nested_tuples_in_case_expr/0,t/2]). - -nested_tuples_in_case_expr() -> - ok. - -t(A, B) -> - case {{element(1, A),element(2, B)},{element(2, A),element(2, B)}} of - {Same,Same} -> ok; - {{0,1},{up,X}} -> bar(X); - {_,{X,_}} -> bar(X) - end. - -bar(X) -> X. - - - - diff --git a/lib/compiler/test/compilation_SUITE_data/otp_2141.erl b/lib/compiler/test/compilation_SUITE_data/otp_2141.erl deleted file mode 100644 index 4737d0972e..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_2141.erl +++ /dev/null @@ -1,25 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-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(otp_2141). --export([otp_2141/0]). - - -otp_2141() -> - ok. diff --git a/lib/compiler/test/compilation_SUITE_data/otp_2173.erl b/lib/compiler/test/compilation_SUITE_data/otp_2173.erl deleted file mode 100644 index 5479700d1d..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_2173.erl +++ /dev/null @@ -1,32 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-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(otp_2173). --compile(export_all). - --record(t, {a = fun(X) -> X*X end}). - -otp_2173() -> - ok. - -t() -> - #t{}. - - - diff --git a/lib/compiler/test/compilation_SUITE_data/otp_5076.erl b/lib/compiler/test/compilation_SUITE_data/otp_5076.erl deleted file mode 100644 index 19f974bb5b..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_5076.erl +++ /dev/null @@ -1,28 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-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(otp_5076). --export([?MODULE/0]). - -?MODULE() -> - [] = t(), - ok. - -t() -> - [3 || {3=4} <- []]. diff --git a/lib/compiler/test/compilation_SUITE_data/otp_5092.erl b/lib/compiler/test/compilation_SUITE_data/otp_5092.erl deleted file mode 100644 index 88c4519cc6..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_5092.erl +++ /dev/null @@ -1,40 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-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(otp_5092). --export([?MODULE/0]). - -?MODULE() -> - [] = t(), - [] = t2(), - [t] = t4(), - [] = t5(), - ok. - -t() -> - [t || {C=D}={_,_} <- []]. - -t2() -> - [X || {X,{Y}={X,X}} <- []]. - -t4() -> - [t || "a"++"b" = "ab" <- ["ab"]]. - -t5() -> - [{X,Y} || {X} <- [], begin Y = X, Y =:= X end]. diff --git a/lib/compiler/test/compilation_SUITE_data/otp_5244.erl b/lib/compiler/test/compilation_SUITE_data/otp_5244.erl deleted file mode 100644 index 8144e5a225..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_5244.erl +++ /dev/null @@ -1,48 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2004-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(otp_5244). --export([?MODULE/0]). - -?MODULE() -> - L = [{stretch,0,0}, - {bad,[]}, - {bad,atom}, - {bad,0}, - {bad,16#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}, - {bad,16#555555555555555555555555555555555555555555555555555}], - remove_failure(L, unit, 0). - -remove_failure([], _Unit, _MaxFailure) -> - ok; -remove_failure([{bad,Bad}|_], _Unit, _MaxFailure) -> - Bad; -remove_failure([{stretch,_,Mi}=Stretch | Specs], Unit, _MaxFailure) -> - {MinMax,NewMaxFailure} = max_failure(), - case {MinMax,remove_failure(Specs, Unit, NewMaxFailure)} of - {min,{NewMaxFailure,Rest}} -> - {done,[{fixed,Mi} | Rest]}; - {min,_} when Specs =/= [] -> - remove_failure([Stretch|tl(Specs)], Unit, NewMaxFailure); - {min,_} -> - ok - end. - -max_failure() -> - {min,1}. diff --git a/lib/compiler/test/compilation_SUITE_data/otp_6121a.erl b/lib/compiler/test/compilation_SUITE_data/otp_6121a.erl deleted file mode 100644 index a8fc5f3bb5..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_6121a.erl +++ /dev/null @@ -1,33 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-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(otp_6121a). --export([?MODULE/0]). - -%% Thanks to Martin Bjorklund. - -?MODULE() -> - G = fun() -> ok end, - try - fun() -> ok end - after - fun({A, B}) -> A + B end - end, - ok. - diff --git a/lib/compiler/test/compilation_SUITE_data/otp_6121b.erl b/lib/compiler/test/compilation_SUITE_data/otp_6121b.erl deleted file mode 100644 index df2e60deb5..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/otp_6121b.erl +++ /dev/null @@ -1,34 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2006-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(otp_6121b). --export([?MODULE/0]). - -%% Thanks to Tim Rath. - -?MODULE() -> - A = {6}, - try - io:fwrite("") - after - fun () -> - fun () -> {B} = A end - end - end. - diff --git a/lib/compiler/test/compilation_SUITE_data/pattern_expr.erl b/lib/compiler/test/compilation_SUITE_data/pattern_expr.erl deleted file mode 100644 index a78b3501d1..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/pattern_expr.erl +++ /dev/null @@ -1,31 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1999-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(pattern_expr). - --export(pattern_expr/0). - -pattern_expr() -> - f(). - -f() -> - case 4 of - 2+2 -> - ok - end. diff --git a/lib/compiler/test/compilation_SUITE_data/trycatch_4.erl b/lib/compiler/test/compilation_SUITE_data/trycatch_4.erl deleted file mode 100644 index f465376bdc..0000000000 --- a/lib/compiler/test/compilation_SUITE_data/trycatch_4.erl +++ /dev/null @@ -1,51 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2005-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(trycatch_4). --export([trycatch_4/0]). --record(state, {foo}). - -trycatch_4() -> - handle_info({foo}, #state{}), - ok. - -handle_info({_}, State) -> - foo(), - State#state{foo = bar}, - case ok of - _ -> - case catch foo() of - ok -> - {stop, State} - end - end; -handle_info(_, State) -> - (catch begin - foo(), - State#state{foo = bar} - end), - case ok of - _ -> - case catch foo() of - ok -> - {stop, State} - end - end. - -foo() -> ok. diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index e1db99b357..e634f0fcc2 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -31,13 +31,12 @@ binary/1, makedep/1, cond_and_ifdef/1, listings/1, listings_big/1, other_output/1, encrypted_abstr/1, strict_record/1, - missing_testheap/1, cover/1, env/1, core/1, asm/1, + cover/1, env/1, core/1, + core_roundtrip/1, asm/1, sys_pre_attributes/1, dialyzer/1, warnings/1, pre_load_check/1 ]). --export([init/3]). - suite() -> [{ct_hooks,[ts_install_cth]}]. %% To cover the stripping of 'type' and 'spec' in beam_asm. @@ -50,7 +49,7 @@ all() -> binary, makedep, cond_and_ifdef, listings, listings_big, other_output, encrypted_abstr, strict_record, - missing_testheap, cover, env, core, asm, + cover, env, core, core_roundtrip, asm, sys_pre_attributes, dialyzer, warnings, pre_load_check]. groups() -> @@ -545,7 +544,6 @@ verify_abstract(Target) -> has_crypto() -> try crypto:start(), - <<_,_,_,_,_>> = crypto:rand_bytes(5), crypto:stop(), true catch @@ -649,46 +647,6 @@ test_sloppy() -> {1,2} = record_access:test(Turtle), Turtle. -missing_testheap(Config) when is_list(Config) -> - DataDir = proplists:get_value(data_dir, Config), - PrivDir = proplists:get_value(priv_dir, Config), - Opts = [{outdir,PrivDir}], - OldPath = code:get_path(), - try - code:add_patha(PrivDir), - c:c(filename:join(DataDir, "missing_testheap1"), Opts), - c:c(filename:join(DataDir, "missing_testheap2"), Opts), - ok = test(fun() -> - missing_testheap1:f({a,self()},{state,true,b}) - end, {a,b}), - ok = test(fun() -> - missing_testheap2:f({a,self()},16#80000000) end, - bigger) - after - code:set_path(OldPath), - file:delete(filename:join(PrivDir, "missing_testheap1.beam")), - file:delete(filename:join(PrivDir, "missing_testheap2.beam")) - end, - ok. - -test(Fun, Result) -> - test(500, Fun, Result, []). - -test(0, _, _, _) -> - ok; -test(Iter, Fun, Result, Filler) -> - spawn(?MODULE, init, [self(), Fun, list_to_tuple(Filler)]), - receive - {result, Result} -> - test(Iter-1, Fun, Result, [0|Filler]); - {result, Other} -> - io:format("Expected ~p; got ~p~n", [Result, Other]), - ct:fail(failed) - end. - -init(ReplyTo, Fun, _Filler) -> - ReplyTo ! {result, Fun()}. - env(Config) when is_list(Config) -> {Simple,Target} = get_files(Config, simple, env), {ok,Cwd} = file:get_cwd(), @@ -790,6 +748,120 @@ compile_forms(Forms, Opts) -> Other -> throw({error,Other}) end. +%% Pretty-print core and read it back. Should be identical. + +core_roundtrip(Config) -> + PrivDir = proplists:get_value(priv_dir, Config), + Outdir = filename:join(PrivDir, atom_to_list(?FUNCTION_NAME)), + ok = file:make_dir(Outdir), + + Wc = filename:join(filename:dirname(code:which(?MODULE)), "*.beam"), + TestBeams = filelib:wildcard(Wc), + test_lib:p_run(fun(F) -> do_core_roundtrip(F, Outdir) end, TestBeams). + +do_core_roundtrip(Beam, Outdir) -> + try + {ok,{Mod,[{abstract_code,{raw_abstract_v1,Abstr}}]}} = + beam_lib:chunks(Beam, [abstract_code]), + do_core_roundtrip_1(Mod, Abstr, Outdir) + catch + throw:{error,Error} -> + io:format("*** compilation failure '~p' for file ~s\n", + [Error,Beam]), + error; + Class:Error -> + io:format("~p: ~p ~p\n~p\n", + [Beam,Class,Error,erlang:get_stacktrace()]), + error + end. + +do_core_roundtrip_1(Mod, Abstr, Outdir) -> + {ok,Mod,Core0} = compile:forms(Abstr, [to_core0]), + do_core_roundtrip_2(Mod, Core0, Outdir), + + %% Primarily, test that annotations are accepted for all + %% constructs. Secondarily, smoke test cerl_trees:label/1. + {Core,_} = cerl_trees:label(Core0), + do_core_roundtrip_2(Mod, Core, Outdir). + +do_core_roundtrip_2(M, Core0, Outdir) -> + CoreFile = filename:join(Outdir, atom_to_list(M)++".core"), + CorePP = core_pp:format_all(Core0), + ok = file:write_file(CoreFile, CorePP), + + %% Parse the .core file and return the result as Core Erlang Terms. + Core2 = case compile:file(CoreFile, [report_errors,from_core, + no_copt,to_core,binary]) of + {ok,M,Core1} -> Core1; + Other -> throw({error,Other}) + end, + Core = undo_var_translation(Core2), + ok = file:delete(CoreFile), + + case cmp_core(Core0, Core, M) of + true -> ok; + false -> error + end, + + ok. + +undo_var_translation(Tree) -> + F = fun(Node) -> + case cerl:is_c_var(Node) of + true -> + Name0 = cerl:var_name(Node), + try atom_to_list(Name0) of + "_X"++Name -> + cerl:update_c_var(Node, list_to_atom(Name)); + "_"++Name -> + cerl:update_c_var(Node, list_to_atom(Name)); + _ -> + Node + catch + error:badarg -> + Node + + end; + false -> + Node + end + end, + cerl_trees:map(F, Tree). + +cmp_core(E, E, _Mod) -> + true; +cmp_core(M1, M2, Mod) -> + cmp_core_fs(cerl:module_defs(M1), cerl:module_defs(M2), Mod). + +cmp_core_fs([F1|T1], [F2|T2], Mod) -> + cmp_core_f(F1, F2, Mod) andalso cmp_core_fs(T1, T2, Mod); +cmp_core_fs([], [], _Mod) -> + true; +cmp_core_fs(_, _, _Mod) -> + false. + +cmp_core_f(E, E, _Mod) -> + true; +cmp_core_f({Name,F1}, {Name,F2}, Mod) -> + case diff(F1, F2) of + F1 -> + true; + Diff -> + io:format("~p ~p:\n~p\n", [Mod,Name,Diff]), + false + end. + +diff(E, E) -> + E; +diff([H1|T1], [H2|T2]) -> + [diff(H1, H2)|diff(T1, T2)]; +diff(T1, T2) when tuple_size(T1) =:= tuple_size(T2) -> + L = diff(tuple_to_list(T1), tuple_to_list(T2)), + list_to_tuple(L); +diff(E1, E2) -> + {'DIFF',E1,E2}. + + %% Compile to Beam assembly language (.S) and then try to %% run .S through the compiler again. diff --git a/lib/compiler/test/compile_SUITE_data/missing_testheap1.erl b/lib/compiler/test/compile_SUITE_data/missing_testheap1.erl deleted file mode 100644 index 2c1eb8a3ae..0000000000 --- a/lib/compiler/test/compile_SUITE_data/missing_testheap1.erl +++ /dev/null @@ -1,36 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-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(missing_testheap1). - --compile(export_all). -%%-export([Function/Arity, ...]). --record(state,{e1,e2}). - -f({a,DpId},State) when State == -#state{e1=true, - e2=a} -> - {a,a}; - -f({a,DpId},State) when State == -#state{e1=true, - e2=b} -> - {a,b}. - - diff --git a/lib/compiler/test/compile_SUITE_data/missing_testheap2.erl b/lib/compiler/test/compile_SUITE_data/missing_testheap2.erl deleted file mode 100644 index b2aa9b5a5a..0000000000 --- a/lib/compiler/test/compile_SUITE_data/missing_testheap2.erl +++ /dev/null @@ -1,30 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-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(missing_testheap2). - --compile(export_all). - -f({a,DpId},16#7fffffff) -> - big; - -f({a,DpId},16#80000000) -> - bigger. - - diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl index ffbf59f0ee..13d274d98a 100644 --- a/lib/compiler/test/guard_SUITE.erl +++ b/lib/compiler/test/guard_SUITE.erl @@ -28,7 +28,7 @@ or_guard/1,more_or_guards/1, complex_or_guards/1,and_guard/1, xor_guard/1,more_xor_guards/1, - old_guard_tests/1, + old_guard_tests/1,complex_guard/1, build_in_guard/1,gbif/1, t_is_boolean/1,is_function_2/1, tricky/1,rel_ops/1,rel_op_combinations/1,literal_type_tests/1, @@ -48,7 +48,8 @@ groups() -> [misc,const_cond,basic_not,complex_not,nested_nots, semicolon,complex_semicolon,comma,or_guard, more_or_guards,complex_or_guards,and_guard,xor_guard, - more_xor_guards,build_in_guard,old_guard_tests,gbif, + more_xor_guards,build_in_guard, + old_guard_tests,complex_guard,gbif, t_is_boolean,is_function_2,tricky, rel_ops,rel_op_combinations, literal_type_tests,basic_andalso_orelse,traverse_dcd, @@ -947,6 +948,26 @@ og(_) -> what. on(V) when number(V) -> number; on(_) -> not_number. +complex_guard(_Config) -> + _ = [true = do_complex_guard(X, Y, Z) || + X <- [4,5], Y <- [4,5], Z <- [4,5]], + _ = [true = do_complex_guard(X, Y, Z) || + X <- [1,2,3], Y <- [1,2,3], Z <- [1,2,3]], + _ = [catch do_complex_guard(X, Y, Z) || + X <- [1,2,3,4,5], Y <- [0,6], Z <- [1,2,3,4,5]], + ok. + +do_complex_guard(X1, Y1, Z1) -> + if + ((X1 =:= 4) or (X1 =:= 5)) and + ((Y1 =:= 4) or (Y1 =:= 5)) and + ((Z1 =:= 4) or (Z1 =:= 5)) or + ((X1 =:= 1) or (X1 =:= 2) or (X1 =:= 3)) and + ((Y1 =:= 1) or (Y1 =:= 2) or (Y1 =:= 3)) and + ((Z1 =:= 1) or (Z1 =:= 2) or (Z1 =:= 3)) -> + true + end. + gbif(Config) when is_list(Config) -> error = gbif_1(1, {false,true}), ok = gbif_1(2, {false,true}), diff --git a/lib/compiler/test/lc_SUITE.erl b/lib/compiler/test/lc_SUITE.erl index dd0bcb4245..3cb49433ce 100644 --- a/lib/compiler/test/lc_SUITE.erl +++ b/lib/compiler/test/lc_SUITE.erl @@ -89,6 +89,18 @@ basic(Config) when is_list(Config) -> %% Filter expressions with andalso/orelse. "abc123" = alphanum("?abc123.;"), + %% Aliased patterns. + [] = [t || {C=D}={_,_} <- []], + [] = [X || {X,{Y}={X,X}} <- []], + [t] = [t || "a"++"b" = "ab" <- ["ab"]], + + %% Strange filter block. + [] = [{X,Y} || {X} <- [], begin Y = X, Y =:= X end], + [{a,a}] = [{X,Y} || {X} <- [{a}], begin Y = X, Y =:= X end], + + %% Not matching. + [] = [3 || {3=4} <- []], + %% Error cases. [] = [{xx,X} || X <- L0, element(2, X) == no_no_no], {'EXIT',_} = (catch [X || X <- L1, list_to_atom(X) == dum]), diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl index f6a24b3211..45d84a7b80 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,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]). + coverage/1,grab_bag/1]). -include_lib("common_test/include/ct.hrl"). @@ -38,7 +38,8 @@ groups() -> [{p,[parallel], [pmatch,mixed,aliases,match_in_call,untuplify, shortcut_boolean,letify_guard,selectify, - underscore,match_map,map_vars_used,coverage]}]. + underscore,match_map,map_vars_used,coverage, + grab_bag]}]. init_per_suite(Config) -> @@ -93,9 +94,9 @@ mixit(X) -> 2 -> b; 3 -> 42; 4 -> 77; - 5 -> blurf; - 6 -> 87987987; - 7 -> {a,b,c} + 4+1 -> blurf; + 5+1 -> 87987987; + 6+1 -> {a,b,c} end of a -> glufs; b -> klafs; @@ -465,4 +466,53 @@ coverage_2(2, b, x) -> ok. coverage_3([$a]++[]++"bc") -> ok. +grab_bag(_Config) -> + [_|T] = id([a,b,c]), + [b,c] = id(T), + + T1 = fun() -> + [_|_] = x + end, + {'EXIT',_} = (catch T1()), + + T2 = fun(A, B) -> + case {{element(1, A),element(2, B)}, + {element(2, A),element(2, B)}} of + {Same,Same} -> ok; + {{0,1},{up,X}} -> id(X); + {_,{X,_}} -> id(X) + end + end, + ok = T2({a,a,z,z}, {z,a,z}), + 1 = T2({0,up}, {zzz,1}), + y = T2({x,y}, {a,z,z}), + + %% OTP-5244. + L = [{stretch,0,0}, + {bad,[]}, + {bad,atom}, + {bad,0}, + {bad,16#AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA}, + {bad,16#555555555555555555555555555555555555555555555555555}], + ok = grab_bag_remove_failure(L, unit, 0), + + ok. + +grab_bag_remove_failure([], _Unit, _MaxFailure) -> + ok; +grab_bag_remove_failure([{bad,Bad}|_], _Unit, _MaxFailure) -> + Bad; +grab_bag_remove_failure([{stretch,_,Mi}=Stretch | Specs], Unit, _MaxFailure) -> + {MinMax,NewMaxFailure} = id({min,1}), + case {MinMax,grab_bag_remove_failure(Specs, Unit, NewMaxFailure)} of + {min,{NewMaxFailure,Rest}} -> + {done,[{fixed,Mi} | Rest]}; + {min,_} when Specs =/= [] -> + grab_bag_remove_failure([Stretch|tl(Specs)], Unit, NewMaxFailure); + {min,_} -> + ok + end. + + + id(I) -> I. diff --git a/lib/compiler/test/misc_SUITE.erl b/lib/compiler/test/misc_SUITE.erl index 8f9ed685bf..f05fe6c943 100644 --- a/lib/compiler/test/misc_SUITE.erl +++ b/lib/compiler/test/misc_SUITE.erl @@ -23,7 +23,8 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, tobias/1,empty_string/1,md5/1,silly_coverage/1, - confused_literals/1,integer_encoding/1,override_bif/1]). + confused_literals/1,integer_encoding/0,integer_encoding/1, + override_bif/1]). -include_lib("common_test/include/ct.hrl"). diff --git a/lib/compiler/test/record_SUITE.erl b/lib/compiler/test/record_SUITE.erl index 4f048f5080..5546765f26 100644 --- a/lib/compiler/test/record_SUITE.erl +++ b/lib/compiler/test/record_SUITE.erl @@ -28,7 +28,7 @@ init_per_testcase/2,end_per_testcase/2, errors/1,record_test_2/1,record_test_3/1,record_access_in_guards/1, guard_opt/1,eval_once/1,foobar/1,missing_test_heap/1, - nested_access/1,coverage/1]). + nested_access/1,coverage/1,grab_bag/1]). init_per_testcase(_Case, Config) -> Config. @@ -48,7 +48,7 @@ groups() -> [{p,test_lib:parallel(), [errors,record_test_2,record_test_3, record_access_in_guards,guard_opt,eval_once,foobar, - missing_test_heap,nested_access,coverage]}]. + missing_test_heap,nested_access,coverage,grab_bag]}]. init_per_suite(Config) -> @@ -601,4 +601,60 @@ coverage(Config) when is_list(Config) -> #rr{a=1,b=2,c=42} = id(R), %Test for correctness. ok. + +-record(default_fun, {a = fun(X) -> X*X end}). + +%% compiler treats records with 1 and 2 fields differently... +-record(gb_nil, {}). +-record(gb_foo, {hello=1}). +-record(gb_bar, {hello=2,there=3}). + +%% Taken from compilation_SUITE. +grab_bag(_Config) -> + T1 = fun() -> + X = #foo{}, + Y = #foo{}, + {X#foo.a == Y#foo.a,X#foo.b} + end, + {true,undefined} = T1(), + + T2 = fun(X, Y) -> + first_arg(X#foo.a =/= Y#foo.a, X#foo.b =/= X#foo.b) + end, + true = T2(#foo{a=x,b=z}, #foo{a=y,b=z}), + + T3 = fun() -> + #default_fun{a=Fun} = id(#default_fun{}), + 9 = Fun(3) + end, + T3(), + + %% Stupid code, but the compiler used to crash. + T4 = fun() -> + F0 = fun() -> + R1 = #gb_nil{}, + R2 = R1#gb_nil{}, + R1 = R2 + end, + F1 = fun() -> + R1 = #gb_foo{}, + R2 = R1#gb_foo{}, + R1 = R2 + end, + + F2 = fun() -> + R1 = #gb_bar{}, + R2 = R1#gb_bar{}, + R1 = R2 + end, + F0(), + F1(), + F2() + end, + T4(), + + ok. + +first_arg(First, _) -> First. + id(I) -> I. diff --git a/lib/compiler/test/trycatch_SUITE.erl b/lib/compiler/test/trycatch_SUITE.erl index 5e0bb6d00a..f7ad78cb8d 100644 --- a/lib/compiler/test/trycatch_SUITE.erl +++ b/lib/compiler/test/trycatch_SUITE.erl @@ -26,7 +26,7 @@ nested_of/1,nested_catch/1,nested_after/1, nested_horrid/1,last_call_optimization/1,bool/1, plain_catch_coverage/1,andalso_orelse/1,get_in_try/1, - hockey/1]). + hockey/1,handle_info/1,catch_in_catch/1,grab_bag/1]). -include_lib("common_test/include/ct.hrl"). @@ -42,7 +42,7 @@ groups() -> after_oops,eclectic,rethrow,nested_of,nested_catch, nested_after,nested_horrid,last_call_optimization, bool,plain_catch_coverage,andalso_orelse,get_in_try, - hockey]}]. + hockey,handle_info,catch_in_catch,grab_bag]}]. init_per_suite(Config) -> @@ -919,8 +919,6 @@ andalso_orelse_1(A, B) -> catched end,B}. -id(I) -> I. - andalso_orelse_2({Type,Keyval}) -> try if is_atom(Type) andalso length(Keyval) > 0 -> ok; @@ -957,3 +955,86 @@ hockey() -> receive _ -> (b = fun() -> ok end) + hockey, +x after 0 -> ok end, try (a = fun() -> ok end) + hockey, + y catch _ -> ok end. + + +-record(state, {foo}). + +handle_info(_Config) -> + do_handle_info({foo}, #state{}), + ok. + +do_handle_info({_}, State) -> + handle_info_ok(), + State#state{foo = bar}, + case ok of + _ -> + case catch handle_info_ok() of + ok -> + {stop, State} + end + end; +do_handle_info(_, State) -> + (catch begin + handle_info_ok(), + State#state{foo = bar} + end), + case ok of + _ -> + case catch handle_info_ok() of + ok -> + {stop, State} + end + end. + +handle_info_ok() -> ok. + +'catch_in_catch'(_Config) -> + process_flag(trap_exit, true), + Pid = spawn_link(fun() -> + catch_in_catch_init(x), + exit(good_exit) + end), + receive + {'EXIT',Pid,good_exit} -> + ok; + Other -> + io:format("Unexpected: ~p\n", [Other]), + error + after 32000 -> + io:format("No message received\n"), + error + end. + +'catch_in_catch_init'(Param) -> + process_flag(trap_exit, true), + %% The catches were improperly nested, causing a "No catch found" crash. + (catch begin + id(Param), + (catch exit(bar)) + end + ), + ignore. + +grab_bag(_Config) -> + %% Thanks to Martin Bjorklund. + _ = fun() -> ok end, + try + fun() -> ok end + after + fun({A, B}) -> A + B end + end, + + %% Thanks to Tim Rath. + A = {6}, + try + io:fwrite("") + after + fun () -> + fun () -> {_} = A end + end + end, + + ok. + + +id(I) -> I. diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index 1be22a0b8a..067e220863 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -214,7 +214,6 @@ static ERL_NIF_TERM aes_ctr_stream_init(ErlNifEnv* env, int argc, const ERL_NIF_ static ERL_NIF_TERM aes_ctr_stream_encrypt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_bytes_1(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); -static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM strong_rand_mpint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM rand_uniform_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); static ERL_NIF_TERM mod_exp_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); @@ -285,7 +284,6 @@ static ErlNifFunc nif_funcs[] = { {"aes_ctr_stream_decrypt", 2, aes_ctr_stream_encrypt}, {"rand_bytes", 1, rand_bytes_1}, {"strong_rand_bytes_nif", 1, strong_rand_bytes_nif}, - {"rand_bytes", 3, rand_bytes_3}, {"strong_rand_mpint_nif", 3, strong_rand_mpint_nif}, {"rand_uniform_nif", 2, rand_uniform_nif}, {"mod_exp_nif", 4, mod_exp_nif}, @@ -1927,27 +1925,7 @@ static ERL_NIF_TERM strong_rand_bytes_nif(ErlNifEnv* env, int argc, const ERL_NI return ret; } -static ERL_NIF_TERM rand_bytes_3(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) -{/* (Bytes, TopMask, BottomMask) */ - unsigned bytes; - unsigned char* data; - unsigned top_mask, bot_mask; - ERL_NIF_TERM ret; - if (!enif_get_uint(env, argv[0], &bytes) - || !enif_get_uint(env, argv[1], &top_mask) - || !enif_get_uint(env, argv[2], &bot_mask)) { - return enif_make_badarg(env); - } - data = enif_make_new_binary(env, bytes, &ret); - RAND_pseudo_bytes(data, bytes); - ERL_VALGRIND_MAKE_MEM_DEFINED(data, bytes); - if (bytes > 0) { - data[bytes-1] |= top_mask; - data[0] |= bot_mask; - } - return ret; -} static ERL_NIF_TERM strong_rand_mpint_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {/* (Bytes, TopMask, BottomMask) */ unsigned bits; diff --git a/lib/crypto/doc/src/crypto.xml b/lib/crypto/doc/src/crypto.xml index e0b989436f..5a5627747c 100644 --- a/lib/crypto/doc/src/crypto.xml +++ b/lib/crypto/doc/src/crypto.xml @@ -308,6 +308,8 @@ <desc> <p>Generates public keys of type <c>Type</c>. See also <seealso marker="public_key:public_key#generate_key-1">public_key:generate_key/1</seealso> + May throw exception <c>low_entropy</c> in case the random generator + failed due to lack of secure "randomness". </p> </desc> </func> @@ -596,22 +598,6 @@ </func> <func> - <name>rand_bytes(N) -> binary()</name> - <fsummary>Generate a binary of random bytes</fsummary> - <type> - <v>N = integer()</v> - </type> - <desc> - <p>Generates N bytes randomly uniform 0..255, and returns the - result in a binary. Uses the <c>crypto</c> library pseudo-random - number generator.</p> - <p>This function is not recommended for cryptographic purposes. - Please use <seealso marker="#strong_rand_bytes/1"> - strong_rand_bytes/1</seealso> instead.</p> - </desc> - </func> - - <func> <name>rand_seed(Seed) -> ok</name> <fsummary>Set the seed for random bytes generation</fsummary> <type> diff --git a/lib/crypto/src/crypto.erl b/lib/crypto/src/crypto.erl index a154476560..025d57e9c5 100644 --- a/lib/crypto/src/crypto.erl +++ b/lib/crypto/src/crypto.erl @@ -28,7 +28,7 @@ -export([generate_key/2, generate_key/3, compute_key/4]). -export([hmac/3, hmac/4, hmac_init/2, hmac_update/2, hmac_final/1, hmac_final_n/2]). -export([exor/2, strong_rand_bytes/1, mod_pow/3]). --export([rand_bytes/1, rand_bytes/3, rand_uniform/2]). +-export([rand_uniform/2]). -export([block_encrypt/3, block_decrypt/3, block_encrypt/4, block_decrypt/4]). -export([next_iv/2, next_iv/3]). -export([stream_init/2, stream_init/3, stream_encrypt/2, stream_decrypt/2]). @@ -39,6 +39,9 @@ -export([rand_seed/1]). %% DEPRECATED +-export([rand_bytes/1]). +-deprecated({rand_bytes, 1, next_major_release}). + %% Replaced by hash_* -export([md4/1, md4_init/0, md4_update/2, md4_final/1]). -export([md5/1, md5_init/0, md5_update/2, md5_final/1]). @@ -407,8 +410,6 @@ strong_rand_bytes(Bytes) -> end. strong_rand_bytes_nif(_Bytes) -> ?nif_stub. -rand_bytes(_Bytes, _Topmask, _Bottommask) -> ?nif_stub. - rand_uniform(From,To) when is_binary(From), is_binary(To) -> case rand_uniform_nif(From,To) of @@ -546,7 +547,7 @@ generate_key(dh, DHParameters, PrivateKey) -> generate_key(srp, {host, [Verifier, Generator, Prime, Version]}, PrivArg) when is_binary(Verifier), is_binary(Generator), is_binary(Prime), is_atom(Version) -> Private = case PrivArg of - undefined -> random_bytes(32); + undefined -> strong_rand_bytes(32); _ -> ensure_int_as_bin(PrivArg) end, host_srp_gen_key(Private, Verifier, Generator, Prime, Version); @@ -554,7 +555,7 @@ generate_key(srp, {host, [Verifier, Generator, Prime, Version]}, PrivArg) generate_key(srp, {user, [Generator, Prime, Version]}, PrivateArg) when is_binary(Generator), is_binary(Prime), is_atom(Version) -> Private = case PrivateArg of - undefined -> random_bytes(32); + undefined -> strong_rand_bytes(32); _ -> PrivateArg end, user_srp_gen_key(Private, Generator, Prime); @@ -606,16 +607,6 @@ compute_key(ecdh, Others, My, Curve) -> nif_curve_params(Curve), ensure_int_as_bin(My)). - -random_bytes(N) -> - try strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - rand_bytes(N) - end. - %%-------------------------------------------------------------------- %%% On load %%-------------------------------------------------------------------- diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 0d18cd8017..6732f27824 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -269,7 +269,6 @@ rand_uniform() -> [{doc, "rand_uniform and random_bytes testing"}]. rand_uniform(Config) when is_list(Config) -> rand_uniform_aux_test(10), - 10 = byte_size(crypto:rand_bytes(10)), 10 = byte_size(crypto:strong_rand_bytes(10)). %%-------------------------------------------------------------------- @@ -649,8 +648,8 @@ ipow(A, B, M, Prod) -> do_exor(B) -> Z1 = zero_bin(B), Z1 = crypto:exor(B, B), - B1 = crypto:rand_bytes(100), - B2 = crypto:rand_bytes(100), + B1 = crypto:strong_rand_bytes(100), + B2 = crypto:strong_rand_bytes(100), Z2 = zero_bin(B1), Z2 = crypto:exor(B1, B1), Z2 = crypto:exor(B2, B2), diff --git a/lib/crypto/test/old_crypto_SUITE.erl b/lib/crypto/test/old_crypto_SUITE.erl index f57e9ff341..0d97290d10 100644 --- a/lib/crypto/test/old_crypto_SUITE.erl +++ b/lib/crypto/test/old_crypto_SUITE.erl @@ -2068,8 +2068,8 @@ exor_test(Config) when is_list(Config) -> B = <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>, Z1 = zero_bin(B), Z1 = crypto:exor(B, B), - B1 = crypto:rand_bytes(100), - B2 = crypto:rand_bytes(100), + B1 = crypto:strong_rand_bytes(100), + B2 = crypto:strong_rand_bytes(100), Z2 = zero_bin(B1), Z2 = crypto:exor(B1, B1), Z2 = crypto:exor(B2, B2), diff --git a/lib/eunit/src/eunit_lib.erl b/lib/eunit/src/eunit_lib.erl index 4dbe023257..9dbb4835f8 100644 --- a/lib/eunit/src/eunit_lib.erl +++ b/lib/eunit/src/eunit_lib.erl @@ -192,7 +192,6 @@ error_msg(Title, Fmt, Args) -> io_lib:fwrite("*** ~ts ***\n~ts\n\n", [Title, Msg]). -ifdef(TEST). --dialyzer({no_match, format_exception_test_/0}). format_exception_test_() -> [?_assertMatch( "\nymmud:rorre"++_, @@ -274,7 +273,6 @@ dlist_next([], Xs) -> -ifdef(TEST). --dialyzer({no_match, dlist_test_/0}). dlist_test_() -> {"deep list traversal", [{"non-list term -> singleton list", @@ -340,7 +338,6 @@ is_nonempty_string([]) -> false; is_nonempty_string(Cs) -> is_string(Cs). -ifdef(TEST). --dialyzer({no_match, is_string_test_/0}). is_string_test_() -> {"is_string", [{"no non-lists", ?_assert(not is_string($A))}, @@ -402,7 +399,7 @@ uniq([X | Xs]) -> [X | uniq(Xs)]; uniq([]) -> []. -ifdef(TEST). --dialyzer({[no_match, no_fail_call, no_improper_lists], uniq_test_/0}). +-dialyzer({[no_fail_call, no_improper_lists], uniq_test_/0}). uniq_test_() -> {"uniq", [?_assertError(function_clause, uniq(ok)), @@ -581,7 +578,6 @@ trie_match([], _T) -> -ifdef(TEST). --dialyzer({no_match, trie_test_/0}). trie_test_() -> [{"basic representation", [?_assert(trie_new() =:= gb_trees:empty()), diff --git a/lib/inets/doc/src/mod_esi.xml b/lib/inets/doc/src/mod_esi.xml index ae567ea185..deef010e54 100644 --- a/lib/inets/doc/src/mod_esi.xml +++ b/lib/inets/doc/src/mod_esi.xml @@ -23,10 +23,6 @@ </legalnotice> <title>mod_esi</title> - <prepared>Joakim Grebenö</prepared> - <docno></docno> - <date>1997-10-14</date> - <rev>2.2</rev> <file>mod_esi.sgml</file> </header> <module>mod_esi</module> @@ -39,6 +35,56 @@ <marker id="deliver"></marker> </description> + <section> + <title>DATA TYPES</title> + <p>The following data types are used in the functions for mod_esi:</p> + + <taglist> + <tag><c>env() = </c></tag> + <item> <p><c>{EnvKey()::atom(), Value::term()}</c></p> + </item> + + <p>Currently supported key value pairs</p> + <taglist> + + <tag><c>{server_software, string()}</c></tag> + <item><p>Indicates the inets version.</p></item> + + <tag><c>{server_name, string()}</c></tag> + <item><p>The local hostname. </p></item> + + <tag><c>{gateway_interface, string()}</c></tag> + <item><p>Legacy string used in CGI, just ignore.</p> </item> + + <tag><c>{server_protocol, string()}</c></tag> + <item><p> HTTP version, currently "HTTP/1.1"</p></item> + + <tag>{server_port, integer()}</tag> + <item><p>Servers port number.</p></item> + + <tag><c>{request_method, "GET | "PUT" | "DELETE | "POST" | "PATCH"}</c></tag> + + <tag><c>{remote_adress, inet:ip_address()} </c></tag> + <item><p>The clients ip address.</p></item> + + <tag><c>{peer_cert, undefined | no_peercert | DER:binary()</c></tag> + <item> + <p>For TLS connections where client certificates are used this will + be an ASN.1 DER-encoded X509-certificate as an Erlang binary. + If client certificates are not used the value will be <c>no_peercert</c>, + and if TLS is not used (HTTP or connection is lost due to network failure) + the value will be <c>undefined</c>. + </p></item> + + <tag><c>{script_name, string()}</c></tag> + <item><p>Request URI</p></item> + + <tag><c>{http_LowerCaseHTTPHeaderName, string()}</c></tag> + <item><p>example: {http_content_type, "text/html"}</p></item> + </taglist> + + </taglist> + <funcs> <func> <name>deliver(SessionID, Data) -> ok | {error, Reason}</name> @@ -63,11 +109,11 @@ overhead. Do not assume anything about the data type of <c>SessionID</c>. <c>SessionID</c> must be the value given as input to the ESI callback function that you implemented.</p> - </note> + </note> </desc> </func> </funcs> - + </section> <section> <title>ESI Callback Functions</title> </section> @@ -78,9 +124,7 @@ to the server process by calling <c>mod_esi:deliver/2</c>.</fsummary> <type> <v>SessionID = term()</v> - <v>Env = [EnvironmentDirectives] ++ ParsedHeader</v> - <v>EnvironmentDirectives = {Key,Value}</v> - <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name</v> + <v>Env = env()</v> <v>Input = string()</v> </type> <desc> @@ -111,9 +155,7 @@ <fsummary>Creates a dynamic web page and returns it as a list. This function is deprecated and is only kept for backwards compatibility.</fsummary> <type> - <v>Env = [EnvironmentDirectives] ++ ParsedHeader</v> - <v>EnvironmentDirectives = {Key,Value}</v> - <v>Key = query_string | content_length | server_software | gateway_interface | server_protocol | server_port | request_method | remote_addr | script_name.</v> + <v>Env = env()</v> <v>Input = string()</v> <v>Response = string()</v> </type> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index b3d2221930..bd6b76a73e 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -33,7 +33,22 @@ <file>notes.xml</file> </header> - <section><title>Inets 6.2.1</title> + <section><title>Inets 6.2.2</title> + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Add environment information item peer_cert to mod_esi</p> + <p> + Own Id: OTP-13510</p> + </item> + </list> + </section> + +</section> + +<section><title>Inets 6.2.1</title> <section><title>Fixed Bugs and Malfunctions</title> <list> diff --git a/lib/inets/src/http_server/httpd_example.erl b/lib/inets/src/http_server/httpd_example.erl index 366e37742b..424d269859 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]). +-export([get/2, post/2, yahoo/2, test1/2, get_bin/2, peer/2]). -export([newformat/3]). %% These are used by the inets test-suite @@ -94,10 +94,26 @@ default(Env,Input) -> io_lib:format("~p",[httpd:parse_query(Input)]),"\n", footer()]. +peer(Env, Input) -> + Header = + case proplists:get_value(peer_cert, Env) of + undefined -> + header("text/html", "Peer-Cert-Exist:false"); + _ -> + header("text/html", "Peer-Cert-Exist:true") + end, + [Header, + top("Test peer_cert environment option"), + "<B>Peer cert:</B> ", + io_lib:format("~p",[proplists:get_value(peer_cert, Env)]),"\n", + footer()]. + header() -> header("text/html"). header(MimeType) -> "Content-type: " ++ MimeType ++ "\r\n\r\n". +header(MimeType, Other) -> + "Content-type: " ++ MimeType ++ "\r\n" ++ Other ++ "\r\n\r\n". top(Title) -> "<HTML> diff --git a/lib/inets/src/http_server/httpd_script_env.erl b/lib/inets/src/http_server/httpd_script_env.erl index 25f9bea7b3..e15613273e 100644 --- a/lib/inets/src/http_server/httpd_script_env.erl +++ b/lib/inets/src/http_server/httpd_script_env.erl @@ -61,6 +61,19 @@ which_port(#mod{config_db = ConfigDb}) -> which_peername(#mod{init_data = #init_data{peername = {_, RemoteAddr}}}) -> RemoteAddr. +which_peercert(#mod{socket_type = {Type, _}, socket = Socket}) when Type == essl; + Type == ssl -> + case ssl:peercert(Socket) of + {ok, Cert} -> + Cert; + {error, no_peercert} -> + no_peercert; + _ -> + undefined + end; +which_peercert(_) -> %% Not an ssl connection + undefined. + which_resolve(#mod{init_data = #init_data{resolve = Resolve}}) -> Resolve. @@ -78,6 +91,7 @@ create_basic_elements(esi, ModData) -> {server_port, which_port(ModData)}, {request_method, which_method(ModData)}, {remote_addr, which_peername(ModData)}, + {peer_cert, which_peercert(ModData)}, {script_name, which_request_uri(ModData)}]; create_basic_elements(cgi, ModData) -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 4d0d656acb..a603357ebd 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -18,12 +18,16 @@ %% %CopyrightEnd% {"%VSN%", [ - {<<"6.2">>, [{load_module, httpc, soft_purge, soft_purge, []}]}, + {<<"6.2.1">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}]}, + {<<"6.2">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}, + {load_module, httpc, soft_purge, soft_purge, []}]}, {<<"6\\..*">>,[{restart_application, inets}]}, {<<"5\\..*">>,[{restart_application, inets}]} ], [ - {<<"6.2">>, [{load_module, httpc, soft_purge, soft_purge, []}]}, + {<<"6.2.1">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}]}, + {<<"6.2">>, [{load_module, httpd_script_env, soft_purge, soft_purge, []}, + {load_module, httpc, 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 1d8a603981..93520c1cb4 100644 --- a/lib/inets/test/httpd_SUITE.erl +++ b/lib/inets/test/httpd_SUITE.erl @@ -755,7 +755,11 @@ esi(Config) when is_list(Config) -> %% Check "ErlScriptNoCache" directive (default: false) ok = http_status("GET /cgi-bin/erl/httpd_example:get ", Config, [{statuscode, 200}, - {no_header, "cache-control"}]). + {no_header, "cache-control"}]), + ok = http_status("GET /cgi-bin/erl/httpd_example:peer ", + Config, [{statuscode, 200}, + {header, "peer-cert-exist", peer(Config)}]). + %%------------------------------------------------------------------------- mod_esi_chunk_timeout(Config) when is_list(Config) -> ok = httpd_1_1:mod_esi_chunk_timeout(?config(type, Config), @@ -2065,3 +2069,11 @@ response_default_headers() -> {"X-Frame-Options", "SAMEORIGIN"}, %% Override built-in default {"Date", "Override-date"}]. + +peer(Config) -> + case proplists:get_value(type, Config) of + ssl -> + "true"; + _ -> + "false" + end.
\ No newline at end of file diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 121f55c389..fc33b546d9 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,6 +19,6 @@ # %CopyrightEnd% APPLICATION = inets -INETS_VSN = 6.2.1 +INETS_VSN = 6.2.2 PRE_VSN = APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" diff --git a/lib/kernel/src/global_group.erl b/lib/kernel/src/global_group.erl index e71f83f9d3..8ac0bd9551 100644 --- a/lib/kernel/src/global_group.erl +++ b/lib/kernel/src/global_group.erl @@ -692,7 +692,7 @@ handle_cast({registered_names, User}, S) -> handle_cast({registered_names_res, Result, Pid, From}, S) -> % io:format(">>>>> registered_names_res Result ~p~n",[Result]), unlink(Pid), - exit(Pid, normal), + Pid ! kill, Wait = get(registered_names), NewWait = lists:delete({Pid, From},Wait), put(registered_names, NewWait), @@ -718,7 +718,7 @@ handle_cast({send_res, Result, Name, Msg, Pid, From}, S) -> ToPid ! Msg end, unlink(Pid), - exit(Pid, normal), + Pid ! kill, Wait = get(send), NewWait = lists:delete({Pid, From, Name, Msg},Wait), put(send, NewWait), @@ -748,7 +748,7 @@ handle_cast({find_name_res, Result, Pid, From}, S) -> % io:format(">>>>> find_name_res Result ~p~n",[Result]), % io:format(">>>>> find_name_res get() ~p~n",[get()]), unlink(Pid), - exit(Pid, normal), + Pid ! kill, Wait = get(whereis_name), NewWait = lists:delete({Pid, From},Wait), put(whereis_name, NewWait), diff --git a/lib/kernel/test/global_group_SUITE.erl b/lib/kernel/test/global_group_SUITE.erl index 06a5b7fcfe..594ee6b537 100644 --- a/lib/kernel/test/global_group_SUITE.erl +++ b/lib/kernel/test/global_group_SUITE.erl @@ -1153,6 +1153,16 @@ test_exit(Config) when is_list(Config) -> rpc:call(Cp1, global_group, send, [king, "The message"]), undefined = rpc:call(Cp1, global_group, whereis_name, [king]), + % make sure the search process really exits after every global_group operations + ProcessCount0 = rpc:call(Cp1, erlang, system_info, [process_count]), + _ = rpc:call(Cp1, global_group, whereis_name, [{node, Cp1nn}, whatever_pid_name]), + ProcessCount1 = rpc:call(Cp1, erlang, system_info, [process_count]), + _ = rpc:call(Cp1, global_group, registered_names, [{node, Cp1nn}]), + ProcessCount2 = rpc:call(Cp1, erlang, system_info, [process_count]), + _ = rpc:call(Cp1, global_group, send, [{node, Cp1nn}, whatever_pid_name, msg]), + ProcessCount3 = rpc:call(Cp1, erlang, system_info, [process_count]), + ProcessCount0 = ProcessCount1 = ProcessCount2 = ProcessCount3, + %% stop the nodes, and make sure names are released. stop_node(Cp1), stop_node(Cp2), diff --git a/lib/mnesia/test/mnesia_config_test.erl b/lib/mnesia/test/mnesia_config_test.erl index a31c9b1f4f..204b8fa394 100644 --- a/lib/mnesia/test/mnesia_config_test.erl +++ b/lib/mnesia/test/mnesia_config_test.erl @@ -693,9 +693,9 @@ event_module(Config) when is_list(Config) -> end, ?match({[ok, ok], []}, rpc:multicall(Nodes, mnesia, start, [Def])), - receive after 1000 -> ok end, + receive after 2000 -> ok end, mnesia_event ! {get_log, self()}, - DebugLog1 = receive + DebugLog1 = receive {log, L1} -> L1 after 10000 -> [timeout] end, @@ -706,9 +706,9 @@ event_module(Config) when is_list(Config) -> ?match({[ok], []}, rpc:multicall([N2], mnesia, start, [])), - receive after 1000 -> ok end, + receive after 2000 -> ok end, mnesia_event ! {get_log, self()}, - DebugLog = receive + DebugLog = receive {log, L} -> L after 10000 -> [timeout] end, diff --git a/lib/mnesia/test/mnesia_consistency_test.erl b/lib/mnesia/test/mnesia_consistency_test.erl index 6c3e68ba38..9cc84de87b 100644 --- a/lib/mnesia/test/mnesia_consistency_test.erl +++ b/lib/mnesia/test/mnesia_consistency_test.erl @@ -565,6 +565,7 @@ consistency_after_fallback_3_disc_only(Config) when is_list(Config) -> consistency_after_fallback(disc_only_copies, 3, Config). consistency_after_fallback(ReplicaType, NodeConfig, Config) -> + put(mnesia_test_verbose, true), %%?verbose("Starting consistency_after_fallback2 at ~p~n", [self()]), Delay = 5, Nodes = ?acquire_nodes(NodeConfig, [{tc_timeout, timer:minutes(10)} | Config]), @@ -594,10 +595,11 @@ consistency_after_fallback(ReplicaType, NodeConfig, Config) -> ?match(ok, mnesia_tpcb:verify_tabs()), %% Stop and then start mnesia and check table consistency - %%?verbose("Restarting Mnesia~n", []), + ?verbose("Kill Mnesia~n", []), mnesia_test_lib:kill_mnesia(Nodes), + ?verbose("Start Mnesia~n", []), mnesia_test_lib:start_mnesia(Nodes,[account,branch,teller,history]), - + ?verbose("Verify tabs~n", []), ?match(ok, mnesia_tpcb:verify_tabs()), if ReplicaType == ram_copies -> diff --git a/lib/observer/test/ttb_SUITE.erl b/lib/observer/test/ttb_SUITE.erl index 499cdb3fc8..c06ec21f36 100644 --- a/lib/observer/test/ttb_SUITE.erl +++ b/lib/observer/test/ttb_SUITE.erl @@ -819,6 +819,7 @@ myhandler(_Fd,Trace,_,Relay) -> simple_call_handler() -> {fun(A, {trace_ts, _, call, _, _} ,_,_) -> io:format(A, "ok.~n", []); + (A, {drop, N}, _, _) -> io:format(A, "{drop, ~p}.", [N]); (_, end_of_trace, _, _) -> ok end, []}. marking_call_handler() -> @@ -954,17 +955,24 @@ begin_trace_local(ServerNode, ClientNode, Dest) -> ?line ttb:tpl(client, get, []). check_size(N, Dest, Output, ServerNode, ClientNode) -> - ?line begin_trace(ServerNode, ClientNode, Dest), - ?line case Dest of + begin_trace(ServerNode, ClientNode, Dest), + case Dest of {local, _} -> - ?line ttb_helper:msgs_ip(N); + ttb_helper:msgs_ip(N); _ -> - ?line ttb_helper:msgs(N) + ttb_helper:msgs(N) end, - ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), - ?line ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]), - ?line {ok, Ret} = file:consult(Output), - ?line true = (N + 1 == length(Ret)). + {_, D} = ttb:stop([fetch, return_fetch_dir]), + ttb:format(D, [{out, Output}, {handler, simple_call_handler()}]), + {ok, Ret} = file:consult(Output), + check_output(N+1, Ret). + +check_output(Expected, Ret) + when length(Ret) =:= Expected -> ok; +check_output(Expected, Ret) -> + io:format("~p~n",[Ret]), + io:format("Expected ~p got ~p ~n",[Expected, length(Ret)]), + Expected = length(Ret). fetch_when_no_option_given(suite) -> []; @@ -1166,8 +1174,8 @@ changing_cwd_on_control_node(Config) when is_list(Config) -> ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)), - ?line ok = file:set_cwd(OldDir). + check_output(2*(NumMsgs + 1),Ret), + ok = file:set_cwd(OldDir). changing_cwd_on_control_node(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1176,18 +1184,19 @@ changing_cwd_on_control_node_with_local_trace(suite) -> changing_cwd_on_control_node_with_local_trace(doc) -> ["Changing cwd on control node during local tracing is safe"]; changing_cwd_on_control_node_with_local_trace(Config) when is_list(Config) -> - ?line {ok, OldDir} = file:get_cwd(), - ?line {ServerNode, ClientNode} = start_client_and_server(), - ?line begin_trace(ServerNode, ClientNode, {local, ?FNAME}), - ?line NumMsgs = 3, - ?line ttb_helper:msgs_ip(NumMsgs), - ?line ok = file:set_cwd(".."), - ?line ttb_helper:msgs_ip(NumMsgs), - ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), - ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), - ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)), - ?line ok = file:set_cwd(OldDir). + {ok, OldDir} = file:get_cwd(), + {ServerNode, ClientNode} = start_client_and_server(), + begin_trace(ServerNode, ClientNode, {local, ?FNAME}), + NumMsgs = 3, + ttb_helper:msgs_ip(NumMsgs), + ok = file:set_cwd(".."), + ttb_helper:msgs_ip(NumMsgs), + {_, D} = ttb:stop([fetch, return_fetch_dir]), + ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), + {ok, Ret} = file:consult(?OUTPUT), + Expected = 2*(NumMsgs + 1), + check_output(Expected, Ret), + ok = file:set_cwd(OldDir). changing_cwd_on_control_node_with_local_trace(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1205,7 +1214,7 @@ changing_cwd_on_remote_node(Config) when is_list(Config) -> ?line {_, D} = ttb:stop([fetch, return_fetch_dir]), ?line ttb:format(D, [{out, ?OUTPUT}, {handler, simple_call_handler()}]), ?line {ok, Ret} = file:consult(?OUTPUT), - ?line true = (2*(NumMsgs + 1) == length(Ret)). + check_output(2*(NumMsgs + 1),Ret). changing_cwd_on_remote_node(cleanup,_Config) -> ?line stop_client_and_server(). @@ -1497,7 +1506,7 @@ logic(N, M, TracingType) -> ct:log("formatted ~p",[{D,?OUTPUT}]), ?line {ok, Ret} = file:consult(?OUTPUT), ct:log("consulted: ~p",[Ret]), - ?line M = length(Ret). + check_output(M,Ret). begin_trace_with_resume(ServerNode, ClientNode, Dest) -> ?line {ok, _} = ttb:tracer([ServerNode,ClientNode], [{file, Dest}, resume]), diff --git a/lib/public_key/doc/src/public_key.xml b/lib/public_key/doc/src/public_key.xml index 6923066da7..04daee460f 100644 --- a/lib/public_key/doc/src/public_key.xml +++ b/lib/public_key/doc/src/public_key.xml @@ -118,7 +118,7 @@ <p><c> not_encrypted | cipher_info()}</c></p></item> <tag><c>cipher_info() = </c></tag> - <item><p><c>{"RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC", crypto:rand_bytes(8)</c></p> + <item><p><c>{"RC2-CBC" | "DES-CBC" | "DES-EDE3-CBC", crypto:strong_rand_bytes(8)</c></p> <p><c>| {#'PBEParameter{}, digest_type()} | #'PBES2-params'{}}</c></p> </item> diff --git a/lib/public_key/doc/src/using_public_key.xml b/lib/public_key/doc/src/using_public_key.xml index 86808adbea..e3a1eed4be 100644 --- a/lib/public_key/doc/src/using_public_key.xml +++ b/lib/public_key/doc/src/using_public_key.xml @@ -124,7 +124,7 @@ ...>>, not_encrypted}, {'Certificate',<<48,130,3,200,48,130,3,49,160,3,2,1,2,2,1, - 1,48,13,6,9,42,134,72,134,247,...>>>, + 1,48,13,6,9,42,134,72,134,247,...>>, not_encrypted}]</code> <p>Certificates can be decoded as usual:</p> diff --git a/lib/public_key/test/public_key_SUITE.erl b/lib/public_key/test/public_key_SUITE.erl index be1a4472e9..51050c4480 100644 --- a/lib/public_key/test/public_key_SUITE.erl +++ b/lib/public_key/test/public_key_SUITE.erl @@ -199,7 +199,7 @@ encrypted_pem(Config) when is_list(Config) -> RSAKey = public_key:der_decode('RSAPrivateKey', DerRSAKey), - Salt0 = crypto:rand_bytes(8), + Salt0 = crypto:strong_rand_bytes(8), Entry0 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, {{"DES-EDE3-CBC", Salt0}, "1234abcd"}), RSAKey = public_key:pem_entry_decode(Entry0,"1234abcd"), @@ -208,7 +208,7 @@ encrypted_pem(Config) when is_list(Config) -> [{'RSAPrivateKey', _, {"DES-EDE3-CBC", Salt0}}] = erl_make_certs:pem_to_der(Des3KeyFile), - Salt1 = crypto:rand_bytes(8), + Salt1 = crypto:strong_rand_bytes(8), Entry1 = public_key:pem_entry_encode('RSAPrivateKey', RSAKey, {{"DES-CBC", Salt1}, "4567efgh"}), DesKeyFile = filename:join(Datadir, "des_client_key.pem"), diff --git a/lib/runtime_tools/test/dbg_SUITE.erl b/lib/runtime_tools/test/dbg_SUITE.erl index 17c04c0ed4..9c9d6ca352 100644 --- a/lib/runtime_tools/test/dbg_SUITE.erl +++ b/lib/runtime_tools/test/dbg_SUITE.erl @@ -20,31 +20,22 @@ -module(dbg_SUITE). %% Test functions --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2, - big/1, tiny/1, simple/1, message/1, distributed/1, port/1, - ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1, - ip_port_busy/1, wrap_port/1, wrap_port_time/1, - with_seq_trace/1, dead_suspend/1, local_trace/1, - saved_patterns/1, tracer_exit_on_stop/1, +-export([all/0, suite/0, + big/1, tiny/1, simple/1, message/1, distributed/1, port/1, + ip_port/1, file_port/1, file_port2/1, file_port_schedfix/1, + ip_port_busy/1, wrap_port/1, wrap_port_time/1, + with_seq_trace/1, dead_suspend/1, local_trace/1, + saved_patterns/1, tracer_exit_on_stop/1, erl_tracer/1, distributed_erl_tracer/1]). --export([init_per_testcase/2, end_per_testcase/2]). -export([tracee1/1, tracee2/1]). -export([dummy/0, exported/1]). -export([enabled/3, trace/6, load_nif/1]). -include_lib("common_test/include/ct.hrl"). --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - ?line Dog=test_server:timetrap(?default_timeout), - [{watchdog, Dog}|Config]. -end_per_testcase(_Case, Config) -> - Dog=?config(watchdog, Config), - test_server:timetrap_cancel(Dog), - ok. -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> [big, tiny, simple, message, distributed, port, ip_port, @@ -53,283 +44,248 @@ all() -> local_trace, saved_patterns, tracer_exit_on_stop, erl_tracer, distributed_erl_tracer]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. -end_per_group(_GroupName, Config) -> - Config. - - -big(suite) -> []; -big(doc) -> ["Rudimentary interface test"]; +%% Rudimentary interface test big(Config) when is_list(Config) -> - ?line {ok,OldCurDir} = file:get_cwd(), - Datadir=?config(data_dir, Config), - Privdir=?config(priv_dir, Config), - ?line ok=file:set_cwd(Privdir), + {ok,OldCurDir} = file:get_cwd(), + Datadir=proplists:get_value(data_dir, Config), + Privdir=proplists:get_value(priv_dir, Config), + ok=file:set_cwd(Privdir), try - %% make sure dbg is stopped (and returns correctly) - ?line ok = dbg:stop(), - - %% compile test module and make sure it is loaded. - ?line {ok,Mod} = compile:file(Datadir++"/dbg_test",[trace]), - ?line code:purge(dbg_test), - ?line {module, Mod}=code:load_file(dbg_test), - - %% run/debug a named test function. - ?line Pid = spawn_link(dbg_test, loop, [Config]), - ?line true = register(dbg_test_loop, Pid), - ?line {ok,_} = dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(dbg_test_loop, [m,p,c]), - ?line ok = dbg:c(dbg_test, test, [Config]), - ?line ok = dbg:i(), - ?line dbg_test_loop ! {dbg_test, stop}, - unregister(dbg_test_loop), - ?line ok = dbg:stop(), - - %% run/debug a Pid. - ?line Pid2=spawn_link(dbg_test,loop,[Config]), - ?line {ok,_} = dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid2,[s,r,p]), - ?line ok = dbg:c(dbg_test, test, [Config]), - ?line ok = dbg:i(), - ?line Pid2 ! {dbg_test, stop}, - - ?line ok=file:set_cwd(OldCurDir) + %% make sure dbg is stopped (and returns correctly) + ok = dbg:stop(), + + %% compile test module and make sure it is loaded. + {ok,Mod} = compile:file(Datadir++"/dbg_test",[trace]), + code:purge(dbg_test), + {module, Mod}=code:load_file(dbg_test), + + %% run/debug a named test function. + Pid = spawn_link(dbg_test, loop, [Config]), + true = register(dbg_test_loop, Pid), + {ok,_} = dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(dbg_test_loop, [m,p,c]), + ok = dbg:c(dbg_test, test, [Config]), + ok = dbg:i(), + dbg_test_loop ! {dbg_test, stop}, + unregister(dbg_test_loop), + ok = dbg:stop(), + + %% run/debug a Pid. + Pid2=spawn_link(dbg_test,loop,[Config]), + {ok,_} = dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(Pid2,[s,r,p]), + ok = dbg:c(dbg_test, test, [Config]), + ok = dbg:i(), + Pid2 ! {dbg_test, stop}, + + ok=file:set_cwd(OldCurDir) after - ?line dbg:stop() + dbg:stop() end, ok. -tiny(suite) -> []; -tiny(doc) -> ["Rudimentary interface test"]; +%% Rudimentary interface test tiny(Config) when is_list(Config) -> - ?line {ok,OldCurDir} = file:get_cwd(), - Datadir=?config(data_dir, Config), - Privdir=?config(priv_dir, Config), - ?line ok=file:set_cwd(Privdir), + {ok,OldCurDir} = file:get_cwd(), + Datadir=proplists:get_value(data_dir, Config), + Privdir=proplists:get_value(priv_dir, Config), + ok=file:set_cwd(Privdir), try - %% compile test module and make sure it is loaded. - ?line {ok, Mod} = compile:file(Datadir++"/dbg_test",[trace]), - ?line code:purge(dbg_test), - ?line {module, Mod}=code:load_file(dbg_test), - - ?line Pid=spawn_link(dbg_test,loop,[Config]), - if - is_pid(Pid) -> - ?line dbg:tracer(), - ?line {ok,[{matched, _node, 1}]} = dbg:p(Pid,[s,r,m,p,c]), - ?line ok = dbg:c(dbg_test,test,[Config]), - ?line ok = dbg:i(), - ?line Pid ! {dbg_test, stop}; - true -> - ?line ok=file:set_cwd(OldCurDir), - ?t:fail("Could not spawn external test process.~n"), - failure - end + %% compile test module and make sure it is loaded. + {ok, Mod} = compile:file(Datadir++"/dbg_test",[trace]), + code:purge(dbg_test), + {module, Mod}=code:load_file(dbg_test), + + Pid=spawn_link(dbg_test,loop,[Config]), + if + is_pid(Pid) -> + dbg:tracer(), + {ok,[{matched, _node, 1}]} = dbg:p(Pid,[s,r,m,p,c]), + ok = dbg:c(dbg_test,test,[Config]), + ok = dbg:i(), + Pid ! {dbg_test, stop}; + true -> + ok=file:set_cwd(OldCurDir), + ct:fail("Could not spawn external test process.~n"), + failure + end after - ?line ok = dbg:stop(), - ?line ok = file:set_cwd(OldCurDir) + ok = dbg:stop(), + ok = file:set_cwd(OldCurDir) end, ok. -simple(suite) -> - []; -simple(doc) -> - ["Simple interface test with own handler"]; +%% Simple interface test with own handler simple(Config) when is_list(Config) -> try - ?line start(), - ?line dbg:p(self(),call), - ?line dbg:tp(dbg,ltp,[]), - ?line dbg:ltp(), - ?line stop(), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]}}] = flush() + start(), + dbg:p(self(),call), + dbg:tp(dbg,ltp,[]), + dbg:ltp(), + stop(), + S = self(), + [{trace,S,call,{dbg,ltp,[]}}] = flush() after - ?line dbg:stop() + dbg:stop() end, ok. -message(suite) -> - []; -message(doc) -> - ["Simple interface test with pam code that appends a message"]; +%% Simple interface test with pam code that appends a message message(Config) when is_list(Config) -> - ?line {ok, _} = start(), + {ok, _} = start(), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg,ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg,ln,Saved), - ?line {value, {saved, Saved}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg,ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg,ln,Saved), + {value, {saved, Saved}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln() after - ?line stop() + stop() end, - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},S}] = flush(), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},S}] = flush(), ok. -distributed(suite) -> - []; -distributed(doc) -> - ["Simple test of distributed tracing"]; +%% Simple test of distributed tracing distributed(Config) when is_list(Config) -> - ?line {ok, _} = start(), - ?line Node = start_slave(), + {ok, _} = start(), + Node = start_slave(), try - ?line RexPid = rpc:call(Node, erlang, whereis, [rex]), - ?line RexPidList = pid_to_list(RexPid), - ?line {ok, Node} = dbg:n(Node), - ?line {ok, X} = dbg:p(all,call), - ?line {value, {matched, Node, _}} = lists:keysearch(Node, 2, X), - ?line {ok, Y} = dbg:p(RexPidList, s), - ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Y), - ?line {ok, Z} = dbg:tp(dbg,ltp,[]), - ?line {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Z), - ?line dbg:cn(Node), - ?line dbg:tp(dbg,ln,[]), - ?line ok = rpc:call(Node, dbg, ltp, []), - ?line ok = rpc:call(Node, dbg, ln, []), - ?line ok = dbg:ln(), - ?line S = self(), - ?line {TraceSend, TraceCall} = - lists:partition(fun ({trace,RP,send,_,_}) when RP =:= RexPid -> true; - (_) -> false end, - flush()), - ?line [_|_] = TraceSend, - ?line [{trace,Pid,call,{dbg,ltp,[]}}, - {trace,S,call,{dbg,ln,[]}}] = TraceCall, - ?line Node = node(Pid), - %% - ?line stop() + RexPid = rpc:call(Node, erlang, whereis, [rex]), + RexPidList = pid_to_list(RexPid), + {ok, Node} = dbg:n(Node), + {ok, X} = dbg:p(all,call), + {value, {matched, Node, _}} = lists:keysearch(Node, 2, X), + {ok, Y} = dbg:p(RexPidList, s), + {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Y), + {ok, Z} = dbg:tp(dbg,ltp,[]), + {value, {matched, Node, 1}} = lists:keysearch(Node, 2, Z), + dbg:cn(Node), + dbg:tp(dbg,ln,[]), + ok = rpc:call(Node, dbg, ltp, []), + ok = rpc:call(Node, dbg, ln, []), + ok = dbg:ln(), + S = self(), + {TraceSend, TraceCall} = + lists:partition(fun ({trace,RP,send,_,_}) when RP =:= RexPid -> true; + (_) -> false end, + flush()), + [_|_] = TraceSend, + [{trace,Pid,call,{dbg,ltp,[]}}, + {trace,S,call,{dbg,ln,[]}}] = TraceCall, + Node = node(Pid), + %% + stop() after - ?line stop_slave(Node), - ?line stop() + stop_slave(Node), + stop() end, ok. -local_trace(suite) -> - []; -local_trace(doc) -> - ["Tests tracing of local function calls."]; +%% Tests tracing of local function calls. local_trace(Config) when is_list(Config) -> - ?line {ok, _} = start(), + {ok, _} = start(), try - ?line S = self(), - ?line %% Split "<X.Y.Z>" into {X, Y, Z} - ?line "<"++L1 = L = pid_to_list(S), - ?line NoDot = fun ($.) -> false; (_) -> true end, - ?line {LX,"."++L2} = lists:splitwith(NoDot, L1), - ?line {LY,"."++L3} = lists:splitwith(NoDot, L2), - ?line ">"++L4 = lists:reverse(L3), - ?line LZ = lists:reverse(L4), - ?line X = 0 = list_to_integer(LX), - ?line Y = list_to_integer(LY), - ?line Z = list_to_integer(LZ), - ?line XYZ = {X, Y, Z}, - ?line io:format("Self = ~w = ~w~n", [S,XYZ]), - ?line {ok, [{matched, _node, 1}]} = dbg:p(S,call), - ?line {ok, [{matched, _node, 1}]} = dbg:p(XYZ,call), - if Z =:= 0 -> - ?line {ok, [{matched, _node, 1}]} = dbg:p(Y,call); - true -> ok - end, - ?line {ok, [{matched, _node, 1}]} = dbg:p(L,call), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]), - ?line 4 = not_exported(2), - ?line [{trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), - ?line {ok, _} = dbg:tp(?MODULE,exported,[]), - ?line 4 = ?MODULE:exported(2), - ?line [{trace,S,call,{?MODULE,exported,[2]}}, - {trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), - ?line {ok, _} = dbg:ctpl(?MODULE), - ?line 4 = ?MODULE:exported(2), - ?line [{trace,S,call,{?MODULE,exported,[2]}}] = flush(), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,[]), - ?line {ok, _} = dbg:ctp(?MODULE), - ?line 4 = ?MODULE:exported(2), - ?line [] = flush(), - ?line {ok, _} = dbg:tpl(?MODULE,not_exported,x), - ?line catch ?MODULE:exported(x), - ?line [{trace,S,call,{dbg_SUITE,not_exported,[x]}}, - {trace,S,exception_from, - {dbg_SUITE,not_exported,1}, - {error,badarith}}] = flush() + S = self(), + %% Split "<X.Y.Z>" into {X, Y, Z} + "<"++L1 = L = pid_to_list(S), + NoDot = fun ($.) -> false; (_) -> true end, + {LX,"."++L2} = lists:splitwith(NoDot, L1), + {LY,"."++L3} = lists:splitwith(NoDot, L2), + ">"++L4 = lists:reverse(L3), + LZ = lists:reverse(L4), + X = 0 = list_to_integer(LX), + Y = list_to_integer(LY), + Z = list_to_integer(LZ), + XYZ = {X, Y, Z}, + io:format("Self = ~w = ~w~n", [S,XYZ]), + {ok, [{matched, _node, 1}]} = dbg:p(S,call), + {ok, [{matched, _node, 1}]} = dbg:p(XYZ,call), + if Z =:= 0 -> + {ok, [{matched, _node, 1}]} = dbg:p(Y,call); + true -> ok + end, + {ok, [{matched, _node, 1}]} = dbg:p(L,call), + {ok, _} = dbg:tpl(?MODULE,not_exported,[]), + 4 = not_exported(2), + [{trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), + {ok, _} = dbg:tp(?MODULE,exported,[]), + 4 = ?MODULE:exported(2), + [{trace,S,call,{?MODULE,exported,[2]}}, + {trace,S,call,{?MODULE,not_exported,[2]}}] = flush(), + {ok, _} = dbg:ctpl(?MODULE), + 4 = ?MODULE:exported(2), + [{trace,S,call,{?MODULE,exported,[2]}}] = flush(), + {ok, _} = dbg:tpl(?MODULE,not_exported,[]), + {ok, _} = dbg:ctp(?MODULE), + 4 = ?MODULE:exported(2), + [] = flush(), + {ok, _} = dbg:tpl(?MODULE,not_exported,x), + catch ?MODULE:exported(x), + [{trace,S,call,{dbg_SUITE,not_exported,[x]}}, + {trace,S,exception_from, + {dbg_SUITE,not_exported,1}, + {error,badarith}}] = flush() after - ?line stop() + stop() end, ok. -port(suite) -> - []; -port(doc) -> - ["Test that tracing on port works"]; +%% Test that tracing on port works port(Config) when is_list(Config) -> try S = self(), - start(), + start(), TestFile = filename:join(proplists:get_value(priv_dir, Config),"port_test"), Fun = dbg:trace_port(file, TestFile), %% Do a run to get rid of all extra port operations port_close(Fun()), - dbg:p(new,ports), + dbg:p(new,ports), Port = Fun(), port_close(Port), - stop(), + stop(), TraceFileDrv = list_to_atom(lists:flatten(["trace_file_drv n ",TestFile])), - [{trace,Port,open,S,TraceFileDrv}, + [{trace,Port,open,S,TraceFileDrv}, {trace,Port,getting_linked,S}, {trace,Port,closed,normal}, {trace,Port,unlink,S}] = flush() after - dbg:stop() + dbg:stop() end, ok. -saved_patterns(suite) -> - []; -saved_patterns(doc) -> - ["Tests saving of match_spec's."]; +%% Tests saving of match_spec's. saved_patterns(Config) when is_list(Config) -> - ?line dbg:stop(), - ?line {ok,[{saved,1}]} = - dbg:tp(dbg,ctp,1,[{'_',[],[{message, blahonga}]}]), - ?line {ok,[{saved,2}]} = - dbg:tp(dbg,ctp,1,[{['_'],[],[{message, blahonga}]}]), - ?line Privdir=?config(priv_dir, Config), - ?line file:make_dir(Privdir), - ?line File = filename:join([Privdir, "blahonga.ms"]), - ?line dbg:wtp(File), - ?line dbg:stop(), - ?line dbg:ctp('_','_','_'), - ?line {ok, _} = start(), + dbg:stop(), + {ok,[{saved,1}]} = + dbg:tp(dbg,ctp,1,[{'_',[],[{message, blahonga}]}]), + {ok,[{saved,2}]} = + dbg:tp(dbg,ctp,1,[{['_'],[],[{message, blahonga}]}]), + Privdir=proplists:get_value(priv_dir, Config), + file:make_dir(Privdir), + File = filename:join([Privdir, "blahonga.ms"]), + dbg:wtp(File), + dbg:stop(), + dbg:ctp('_','_','_'), + {ok, _} = start(), try - ?line dbg:rtp(File), - ?line {ok,[{matched,_node,1},{saved,1}]} = dbg:tp(dbg,ltp,0,1), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line dbg:ltp(), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},blahonga}] = flush() + dbg:rtp(File), + {ok,[{matched,_node,1},{saved,1}]} = dbg:tp(dbg,ltp,0,1), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + dbg:ltp(), + S = self(), + [{trace,S,call,{dbg,ltp,[]},blahonga}] = flush() after - ?line stop() + stop() end, ok. @@ -341,148 +297,132 @@ not_exported(N) -> exported(N) -> not_exported(N). -ip_port(suite) -> - []; -ip_port(doc) -> - ["Test tracing to IP port"]; +%% Test tracing to IP port ip_port(Config) when is_list(Config) -> - ?line stop(), - ?line Port = dbg:trace_port(ip, 0), - ?line {ok, _} = dbg:tracer(port, Port), + stop(), + Port = dbg:trace_port(ip, 0), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln(), - ?line {ok, IpPort} = dbg:trace_port_control(get_listen_port), - ?line io:format("IpPort = ~p~n", [IpPort]), - ?line dbg:trace_client(ip, IpPort, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},hej}] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln(), + {ok, IpPort} = dbg:trace_port_control(get_listen_port), + io:format("IpPort = ~p~n", [IpPort]), + dbg:trace_client(ip, IpPort, {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},hej}] = flush() after - ?line stop() + stop() end, ok. -ip_port_busy(suite) -> - []; -ip_port_busy(doc) -> - ["Test that the dbg server does not hang if the tracer don't start ", - "(OTP-3592)"]; +%% Test that the dbg server does not hang if the tracer don't start (OTP-3592) ip_port_busy(Config) when is_list(Config) -> - ?line stop(), - ?line Tracer = dbg:trace_port(ip, 4745), - ?line Port = Tracer(), - ?line {error, Reason} = dbg:tracer(port, Tracer), + stop(), + Tracer = dbg:trace_port(ip, 4745), + Port = Tracer(), + {error, Reason} = dbg:tracer(port, Tracer), try - ?line io:format("Error reason = ~p~n", [Reason]), - ?line true = port_close(Port) + io:format("Error reason = ~p~n", [Reason]), + true = port_close(Port) after - ?line dbg:stop() + dbg:stop() end, - ?line ok. + ok. -file_port(suite) -> - []; -file_port(doc) -> - ["Test tracing to file port (simple)"]; +%% Test tracing to file port (simple) file_port(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C), - ?line FName = filename:join([?config(data_dir, Config), FTMP]), - ?line Port = dbg:trace_port(file, FName), - ?line {ok, _} = dbg:tracer(port, Port), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), + Port = dbg:trace_port(file, FName), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), - ?line ok = dbg:ltp(), - ?line ok = dbg:ln(), - ?line stop(), - ?line dbg:trace_client(file, FName, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace,S,call,{dbg,ltp,[]},S}, - {trace,S,call,{dbg,ln,[]},hej}, - end_of_trace] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), + ok = dbg:ltp(), + ok = dbg:ln(), + stop(), + dbg:trace_client(file, FName, {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}, + {trace,S,call,{dbg,ln,[]},hej}, + end_of_trace] = flush() after - ?line stop(), - ?line file:delete(FName) + stop(), + file:delete(FName) end, ok. -file_port2(suite) -> - []; -file_port2(doc) -> - ["Test tracing to file port with 'follow_file'"]; +%% Test tracing to file port with 'follow_file' file_port2(Config) when is_list(Config) -> stop(), {A,B,C} = erlang:now(), FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ - "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), - FName = filename:join([?config(data_dir, Config), FTMP]), + "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% Ok, lets try with flush and follow_file, not a chance on VxWorks %% with NFS caching... Port2 = dbg:trace_port(file, FName), {ok, _} = dbg:tracer(port, Port2), try - {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), - {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), - ok = dbg:ltp(), - ok = dbg:flush_trace_port(), - dbg:trace_client(follow_file, FName, - {fun myhandler/2, self()}), - S = self(), - [{trace,S,call,{dbg,ltp,[]},S}] = flush(), - ok = dbg:ln(), - ok = dbg:flush_trace_port(), - receive after 1000 -> ok end, %% Polls every second... - [{trace,S,call,{dbg,ln,[]},hej}] = flush(), - stop(), - [] = flush() + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, _} = dbg:tp(dbg, ltp,[{'_',[],[{message, {self}}]}]), + {ok, _} = dbg:tp(dbg, ln, [{'_',[],[{message, hej}]}]), + ok = dbg:ltp(), + ok = dbg:flush_trace_port(), + dbg:trace_client(follow_file, FName, + {fun myhandler/2, self()}), + S = self(), + [{trace,S,call,{dbg,ltp,[]},S}] = flush(), + ok = dbg:ln(), + ok = dbg:flush_trace_port(), + receive after 1000 -> ok end, %% Polls every second... + [{trace,S,call,{dbg,ln,[]},hej}] = flush(), + stop(), + [] = flush() after - stop(), - file:delete(FName) + stop(), + file:delete(FName) end, ok. -file_port_schedfix(suite) -> - []; -file_port_schedfix(doc) -> - ["Test that the scheduling timestamp fix for trace flag 'running' works."]; +%% Test that the scheduling timestamp fix for trace flag 'running' works. file_port_schedfix(Config) when is_list(Config) -> - ?line case (catch erlang:system_info(smp_support)) of - true -> - {skip, "No schedule fix on SMP"}; - _ -> - try - file_port_schedfix1(Config) - after - dbg:stop() - end - end. + case (catch erlang:system_info(smp_support)) of + true -> + {skip, "No schedule fix on SMP"}; + _ -> + try + file_port_schedfix1(Config) + after + dbg:stop() + end + end. file_port_schedfix1(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ - "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), - ?line FName = filename:join([?config(data_dir, Config), FTMP]), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ + "-" ++ integer_to_list(B) ++ "-" ++ integer_to_list(C), + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% - ?line Port = dbg:trace_port(file, {FName, wrap, ".wraplog", 8*1024, 4}), - ?line {ok, _} = dbg:tracer(port, Port), - ?line {ok,[{matched,_node,0}]} = dbg:p(new,[running,procs,send,timestamp]), + Port = dbg:trace_port(file, {FName, wrap, ".wraplog", 8*1024, 4}), + {ok, _} = dbg:tracer(port, Port), + {ok,[{matched,_node,0}]} = dbg:p(new,[running,procs,send,timestamp]), %% %% Generate the trace data %% @@ -503,146 +443,143 @@ file_port_schedfix1(Config) when is_list(Config) -> %% execution time. Wallclock. A normal result is about 10 times more %% without schedule in - schedule out compensation (OTP-3938). %% - ?line ok = token_volleyball(3, 4, 8), + ok = token_volleyball(3, 4, 8), %% - ?line {ok,[{matched,_,_}]} = dbg:p(all, [clear]), - ?line stop(), + {ok,[{matched,_,_}]} = dbg:p(all, [clear]), + stop(), % Some debug code to run on all platforms, for finding the fault on genny % Dont touch please /PaN - ?line io:format("Trace dump by PaN BEGIN~n"), - ?line dbg:trace_client(file,{FName, wrap, ".wraplog"},{fun(end_of_trace,Pid)-> Pid ! done; (Mesg,Pid) -> io:format("~w~n",[Mesg]),Pid end,self()}), + io:format("Trace dump by PaN BEGIN~n"), + dbg:trace_client(file,{FName, wrap, ".wraplog"},{fun(end_of_trace,Pid)-> Pid ! done; (Mesg,Pid) -> io:format("~w~n",[Mesg]),Pid end,self()}), receive done -> ok end, - ?line io:format("Trace dump by PaN END~n"), - %% + io:format("Trace dump by PaN END~n"), + %% %% Get the trace result %% - ?line Tag = make_ref(), - ?line dbg:trace_client(file, {FName, wrap, ".wraplog"}, - {fun schedstat_handler/2, {self(), Tag, []}}), - ?line Result = - receive - {Tag, D} -> - lists:map( - fun({Pid, {A1, B1, C1}}) -> - {Pid, C1/1000000 + B1 + A1*1000000} - end, - D) - end, - ?line ok = io:format("Result=~p", [Result]), -% erlang:display({?MODULE, ?LINE, Result}), + Tag = make_ref(), + dbg:trace_client(file, {FName, wrap, ".wraplog"}, + {fun schedstat_handler/2, {self(), Tag, []}}), + Result = + receive + {Tag, D} -> + lists:map( + fun({Pid, {A1, B1, C1}}) -> + {Pid, C1/1000000 + B1 + A1*1000000} + end, + D) + end, + ok = io:format("Result=~p", [Result]), + % erlang:display({?MODULE, ?LINE, Result}), %% %% Analyze the result %% - ?line {Min, Max} = - lists:foldl( - fun({_Pid, M}, {Mi, Ma}) -> - {if M < Mi -> M; true -> Mi end, - if M > Ma -> M; true -> Ma end} - end, - {void, 0}, - Result), + {Min, Max} = + lists:foldl( + fun({_Pid, M}, {Mi, Ma}) -> + {if M < Mi -> M; true -> Mi end, + if M > Ma -> M; true -> Ma end} + end, + {void, 0}, + Result), % More PaN debug - ?line io:format("Min = ~f, Max = ~f~n",[Min,Max]), + io:format("Min = ~f, Max = ~f~n",[Min,Max]), %% %% Cleanup %% - ?line ToBeDeleted = filelib:wildcard(FName++"*"++".wraplog"), - ?line lists:map(fun file:delete/1, ToBeDeleted), -% io:format("ToBeDeleted=~p", [ToBeDeleted]), + ToBeDeleted = filelib:wildcard(FName++"*"++".wraplog"), + lists:map(fun file:delete/1, ToBeDeleted), + % io:format("ToBeDeleted=~p", [ToBeDeleted]), %% %% Present the result %% P = (Max / Min - 1) * 100, BottomLine = lists:flatten(io_lib:format("~.2f %", [P])), if P > 100 -> - Reason = {BottomLine, '>', "100%"}, - erlang:display({file_port_schedfix, fail, Reason}), - test_server:fail(Reason); + Reason = {BottomLine, '>', "100%"}, + erlang:display({file_port_schedfix, fail, Reason}), + ct:fail(Reason); true -> - {comment, BottomLine} + {comment, BottomLine} end. -wrap_port(suite) -> - []; -wrap_port(doc) -> - ["Test tracing to wrapping file port"]; +%% Test tracing to wrapping file port wrap_port(Config) when is_list(Config) -> - ?line Self = self(), - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", - ?line FName = filename:join([?config(data_dir, Config), FTMP]), - ?line FNameWildcard = FName++"*"++".trace", + Self = self(), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), + FNameWildcard = FName++"*"++".trace", %% WrapSize=0 and WrapCnt=11 will force the trace to wrap after %% every trace message, and to contain only the last 10 entries %% after trace stop since the last file will be empty waiting %% for its first trace message. - ?line WrapSize = 0, - ?line WrapCnt = 11, - ?line WrapFilesSpec = {FName, wrap, ".trace", WrapSize, WrapCnt}, - ?line wrap_port_init(WrapFilesSpec), + WrapSize = 0, + WrapCnt = 11, + WrapFilesSpec = {FName, wrap, ".trace", WrapSize, WrapCnt}, + wrap_port_init(WrapFilesSpec), %% The number of iterations, N, is tested to place wrap the log, %% giving a gap in the filename sequence at index 3. %% This should be a difficult case for %% the trace_client file sorting functionality. N = 7, - ?line lists:foreach( - fun(Cnt) -> - ?MODULE:tracee1(Cnt), - ?MODULE:tracee2(Cnt) - end, - lists:seq(1, N)), - ?line stop(), + lists:foreach( + fun(Cnt) -> + ?MODULE:tracee1(Cnt), + ?MODULE:tracee2(Cnt) + end, + lists:seq(1, N)), + stop(), try - ?line Files1 = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files1]), - ?line Tc1 = dbg:trace_client(file, WrapFilesSpec, - {fun myhandler/2, {wait_for_go,Self}}), - ?line Tref1 = erlang:monitor(process, Tc1), - Tc1 ! {go,Self}, - ?line [{'DOWN',Tref1,_,_,normal}, - end_of_trace - |Result] = lists:reverse(flush()), - ?line M = N - (WrapCnt-1) div 2, - ?line M = wrap_port_result(Result, Self, N), - %% - %% Start a new wrap log with the same name to verify that - %% all files are cleared at wrap log start. Only produce - %% two trace messages to also place the gap at index 3, - %% so the trace log will be misinterpreted. - %% - ?line wrap_port_init(WrapFilesSpec), - ?line Files2 = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files2]), - ?line -1 = ?MODULE:tracee1(-1), - ?line -1 = ?MODULE:tracee2(-1), - ?line stop(), - ?line Files = filelib:wildcard(FNameWildcard), - ?line io:format("~p~n", [Files]), - ?line Tc2 = dbg:trace_client(file, WrapFilesSpec, - {fun myhandler/2, {wait_for_go,Self}}), - ?line Tref2 = erlang:monitor(process, Tc2), - Tc2 ! {go,Self}, - ?line [{trace,Self,call,{?MODULE,tracee1,[-1]},Self}, - {trace,Self,call,{?MODULE,tracee2,[-1]},hej}, - end_of_trace, - {'DOWN',Tref2,_,_,normal}] = flush(), - %% - ?line lists:map(fun(F) -> file:delete(F) end, Files) + Files1 = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files1]), + Tc1 = dbg:trace_client(file, WrapFilesSpec, + {fun myhandler/2, {wait_for_go,Self}}), + Tref1 = erlang:monitor(process, Tc1), + Tc1 ! {go,Self}, + [{'DOWN',Tref1,_,_,normal}, + end_of_trace + |Result] = lists:reverse(flush()), + M = N - (WrapCnt-1) div 2, + M = wrap_port_result(Result, Self, N), + %% + %% Start a new wrap log with the same name to verify that + %% all files are cleared at wrap log start. Only produce + %% two trace messages to also place the gap at index 3, + %% so the trace log will be misinterpreted. + %% + wrap_port_init(WrapFilesSpec), + Files2 = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files2]), + -1 = ?MODULE:tracee1(-1), + -1 = ?MODULE:tracee2(-1), + stop(), + Files = filelib:wildcard(FNameWildcard), + io:format("~p~n", [Files]), + Tc2 = dbg:trace_client(file, WrapFilesSpec, + {fun myhandler/2, {wait_for_go,Self}}), + Tref2 = erlang:monitor(process, Tc2), + Tc2 ! {go,Self}, + [{trace,Self,call,{?MODULE,tracee1,[-1]},Self}, + {trace,Self,call,{?MODULE,tracee2,[-1]},hej}, + end_of_trace, + {'DOWN',Tref2,_,_,normal}] = flush(), + %% + lists:map(fun(F) -> file:delete(F) end, Files) after - ?line stop() + stop() end, ok. wrap_port_init(WrapFilesSpec) -> - ?line Port = dbg:trace_port(file, WrapFilesSpec), - ?line {ok, _} = dbg:tracer(port, Port), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, Y), + Port = dbg:trace_port(file, WrapFilesSpec), + {ok, _} = dbg:tracer(port, Port), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, Y), ok. tracee1(X) -> @@ -654,106 +591,96 @@ tracee2(X) -> wrap_port_result([], _S, M) -> M; wrap_port_result([{trace, S, call, {?MODULE, tracee2, [M]}, hej}, - {trace, S, call, {?MODULE, tracee1, [M]}, S} | Tail], - S, - M) -> + {trace, S, call, {?MODULE, tracee1, [M]}, S} | Tail], + S, + M) -> wrap_port_result(Tail, S, M-1). -wrap_port_time(suite) -> - []; -wrap_port_time(doc) -> - ["Test tracing to time limited wrapping file port"]; +%% Test tracing to time limited wrapping file port wrap_port_time(Config) when is_list(Config) -> - ?line stop(), - ?line {A,B,C} = erlang:now(), - ?line FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ - integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", - ?line FName = filename:join([?config(data_dir, Config), FTMP]), + stop(), + {A,B,C} = erlang:now(), + FTMP = atom_to_list(?MODULE) ++ integer_to_list(A) ++ "-" ++ + integer_to_list(B) ++ "-" ++ integer_to_list(C) ++ "-", + FName = filename:join([proplists:get_value(data_dir, Config), FTMP]), %% WrapTime=2 and WrapCnt=4 will force the trace to wrap after %% every 2 seconds, and to contain between 3*2 and 4*2 seconds %% of trace entries. - ?line WrapFilesSpec = {FName, wrap, ".trace", {time, 2000}, 4}, - ?line Port = dbg:trace_port(file, WrapFilesSpec), - ?line {ok, _} = dbg:tracer(port, Port), + WrapFilesSpec = {FName, wrap, ".trace", {time, 2000}, 4}, + Port = dbg:trace_port(file, WrapFilesSpec), + {ok, _} = dbg:tracer(port, Port), try - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), - ?line {value, {saved, _Saved1}} = lists:keysearch(saved, 1, X), - ?line {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), - ?line {value, {saved, _Saved2}} = lists:keysearch(saved, 1, Y), - %% The delays in the iterations places two trace messages in each - %% trace file, but the last which is empty waiting for its first - %% trace message. The number of iterations is chosen so that - %% one trace file has been wasted, and therefore the first pair - %% of trace messages. - ?line lists:foreach( - fun(Cnt) -> - receive after 1000 -> ok end, - ?MODULE:tracee1(Cnt), - ?MODULE:tracee2(Cnt), - receive after 1100 -> ok end - end, - lists:seq(1, 4)), - ?line stop(), - ?line Files = filelib:wildcard(FName ++ "*" ++ ".trace"), - ?line io:format("~p~n", [Files]), - ?line dbg:trace_client(file, WrapFilesSpec, {fun myhandler/2, self()}), - ?line S = self(), - ?line [{trace, S, call, {?MODULE, tracee1, [2]}, S}, - {trace, S, call, {?MODULE, tracee2, [2]}, hej}, - {trace, S, call, {?MODULE, tracee1, [3]}, S}, - {trace, S, call, {?MODULE, tracee2, [3]}, hej}, - {trace, S, call, {?MODULE, tracee1, [4]}, S}, - {trace, S, call, {?MODULE, tracee2, [4]}, hej}, - end_of_trace] = flush(), - ?line lists:map(fun(F) -> file:delete(F) end, Files) + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + {ok, X} = dbg:tp(?MODULE, tracee1,[{'_',[],[{message, {self}}]}]), + {value, {saved, _Saved1}} = lists:keysearch(saved, 1, X), + {ok, Y} = dbg:tp(?MODULE, tracee2, [{'_',[],[{message, hej}]}]), + {value, {saved, _Saved2}} = lists:keysearch(saved, 1, Y), + %% The delays in the iterations places two trace messages in each + %% trace file, but the last which is empty waiting for its first + %% trace message. The number of iterations is chosen so that + %% one trace file has been wasted, and therefore the first pair + %% of trace messages. + lists:foreach( + fun(Cnt) -> + receive after 1000 -> ok end, + ?MODULE:tracee1(Cnt), + ?MODULE:tracee2(Cnt), + receive after 1100 -> ok end + end, + lists:seq(1, 4)), + stop(), + Files = filelib:wildcard(FName ++ "*" ++ ".trace"), + io:format("~p~n", [Files]), + dbg:trace_client(file, WrapFilesSpec, {fun myhandler/2, self()}), + S = self(), + [{trace, S, call, {?MODULE, tracee1, [2]}, S}, + {trace, S, call, {?MODULE, tracee2, [2]}, hej}, + {trace, S, call, {?MODULE, tracee1, [3]}, S}, + {trace, S, call, {?MODULE, tracee2, [3]}, hej}, + {trace, S, call, {?MODULE, tracee1, [4]}, S}, + {trace, S, call, {?MODULE, tracee2, [4]}, hej}, + end_of_trace] = flush(), + lists:map(fun(F) -> file:delete(F) end, Files) after - ?line stop() + stop() end, ok. -with_seq_trace(suite) -> - []; -with_seq_trace(doc) -> - ["Test ordinary tracing combined with seq_trace"]; +%% Test ordinary tracing combined with seq_trace with_seq_trace(Config) when is_list(Config) -> try - ?line {ok, Server} = start(), - ?line {ok, Tracer} = dbg:get_tracer(), - ?line {ok, X} = dbg:tp(dbg, get_tracer, [{[],[], - [{set_seq_token, send, true}]}]), - ?line {value, {saved, _}} = lists:keysearch(saved, 1, X), - ?line {ok, [{matched, _node, 1}]} = dbg:p(self(),call), - ?line seq_trace:set_system_tracer(Tracer), - ?line dbg:get_tracer(), - receive - after 1 -> - ok - end, - ?line S = self(), - ?line ThisNode = node(), - ?line [{trace,S,call,{dbg,get_tracer,[]}}, - {seq_trace,0,{send,_,S,Server,{S,{get_tracer,ThisNode}}}}, - {seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] = - flush() + {ok, Server} = start(), + {ok, Tracer} = dbg:get_tracer(), + {ok, X} = dbg:tp(dbg, get_tracer, [{[],[], + [{set_seq_token, send, true}]}]), + {value, {saved, _}} = lists:keysearch(saved, 1, X), + {ok, [{matched, _node, 1}]} = dbg:p(self(),call), + seq_trace:set_system_tracer(Tracer), + dbg:get_tracer(), + receive + after 1 -> + ok + end, + S = self(), + ThisNode = node(), + [{trace,S,call,{dbg,get_tracer,[]}}, + {seq_trace,0,{send,_,S,Server,{S,{get_tracer,ThisNode}}}}, + {seq_trace,0,{send,_,Server,S,{dbg,{ok,Tracer}}}}] = + flush() after - ?line stop() + stop() end, ok. -dead_suspend(suite) -> - []; -dead_suspend(doc) -> - ["Test that trace messages concerning a now dead process does " - "not crash dbg."]; +%% Test that trace messages concerning a now dead process does not crash dbg. dead_suspend(Config) when is_list(Config) -> - ?line start(), + start(), try - survived = run_dead_suspend() + survived = run_dead_suspend() after - ?line stop() + stop() end. run_dead_suspend() -> @@ -766,10 +693,10 @@ run_dead_suspend() -> spawn(?MODULE, dummy, []), receive after 1000 -> ok end, case whereis(dbg) of - undefined -> - died; - _ -> - survived + undefined -> + died; + _ -> + survived end. dummy() -> @@ -781,10 +708,10 @@ tracer_exit_on_stop(_) -> %% Tracer blocks waiting for fun to complete so that the trace message and %% the exit signal message from the dbg process are in its message queue. Fun = fun() -> - ?MODULE:dummy(), - Ref = erlang:trace_delivered(self()), - receive {trace_delivered, _, Ref} -> stop() end - end, + ?MODULE:dummy(), + Ref = erlang:trace_delivered(self()), + receive {trace_delivered, _, Ref} -> stop() end + end, {ok, _} = dbg:tracer(process, {fun spawn_once_handler/2, {self(), Fun}}), {ok, Tracer} = dbg:get_tracer(), MRef = monitor(process, Tracer), @@ -803,9 +730,9 @@ spawn_once_handler(Event, {Pid, done} = State) -> spawn_once_handler(Event, {Pid, Fun}) -> {_, Ref} = spawn_monitor(Fun), receive - {'DOWN', Ref, _, _, _} -> - Pid ! Event, - {Pid, done} + {'DOWN', Ref, _, _, _} -> + Pid ! Event, + {Pid, done} end. %% Test that erl_tracer modules work correctly @@ -898,38 +825,38 @@ wait_node(_,0) -> no; wait_node(Node, N) -> case net_adm:ping(Node) of - pong -> - ok; - pang -> - receive - after 1000 -> - ok - end, - wait_node(Node, N - 1) + pong -> + ok; + pang -> + receive + after 1000 -> + ok + end, + wait_node(Node, N - 1) end. myhandler(Message, {wait_for_go,Pid}) -> receive - {go,Pid} -> - myhandler(Message, Pid) + {go,Pid} -> + myhandler(Message, Pid) end; myhandler(Message, Relay) -> Relay ! Message, case Message of - end_of_trace -> - ok; - _ -> - Relay + end_of_trace -> + ok; + _ -> + Relay end. flush() -> flush([]). flush(Acc) -> receive - X -> - flush(Acc ++ [X]) + X -> + flush(Acc ++ [X]) after 1000 -> - Acc + Acc end. start() -> @@ -943,88 +870,88 @@ stop() -> schedstat_handler(TraceMsg, {Parent, Tag, Data} = State) -> case TraceMsg of - {trace_ts, Pid, in, _, Ts} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, Acc}} -> - [{Pid, Acc, Ts} | lists:keydelete(Pid, 1, Data)]; - false -> - [{Pid, {0, 0, 0}, Ts} | Data]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, Pid, out, _, {A3, B3, C3}} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> - [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | - lists:keydelete(Pid, 1, Data)]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, Pid, exit, normal, {A3, B3, C3}} -> - NewData = - case lists:keysearch(Pid, 1, Data) of - {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> - [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | - lists:keydelete(Pid, 1, Data)]; - {value, {Pid, _Acc}} -> - Data; - false -> - [{Pid, {0, 0, 0}} | Data]; - Other -> - exit(Parent, {?MODULE, ?LINE, Other}), - erlang:display({?MODULE, ?LINE, Other}), - Data - end, - {Parent, Tag, NewData}; - {trace_ts, _Pid, send, _Msg, _OtherPid, _Ts} -> - State; - end_of_trace -> - Parent ! {Tag, Data}, - State + {trace_ts, Pid, in, _, Ts} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, Acc}} -> + [{Pid, Acc, Ts} | lists:keydelete(Pid, 1, Data)]; + false -> + [{Pid, {0, 0, 0}, Ts} | Data]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, Pid, out, _, {A3, B3, C3}} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> + [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | + lists:keydelete(Pid, 1, Data)]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, Pid, exit, normal, {A3, B3, C3}} -> + NewData = + case lists:keysearch(Pid, 1, Data) of + {value, {Pid, {A1, B1, C1}, {A2, B2, C2}}} -> + [{Pid, {A3-A2+A1, B3-B2+B1, C3-C2+C1}} | + lists:keydelete(Pid, 1, Data)]; + {value, {Pid, _Acc}} -> + Data; + false -> + [{Pid, {0, 0, 0}} | Data]; + Other -> + exit(Parent, {?MODULE, ?LINE, Other}), + erlang:display({?MODULE, ?LINE, Other}), + Data + end, + {Parent, Tag, NewData}; + {trace_ts, _Pid, send, _Msg, _OtherPid, _Ts} -> + State; + end_of_trace -> + Parent ! {Tag, Data}, + State end. pass_token(Token, Next, Loops) -> receive - {Token, 1} = Msg -> - sendloop(Loops), - Next ! Msg; - {Token, _Cnt} = Msg-> - sendloop(Loops), - Next ! Msg, - pass_token(Token, Next, Loops) + {Token, 1} = Msg -> + sendloop(Loops), + Next ! Msg; + {Token, _Cnt} = Msg-> + sendloop(Loops), + Next ! Msg, + pass_token(Token, Next, Loops) end. pass_token(Token, Final, Cnt, Loops) -> receive - {Token, start, Next} -> - sendloop(Loops), - Msg = {Token, Cnt}, - Next ! Msg, - pass_token(Token, Final, Next, Cnt, Loops) + {Token, start, Next} -> + sendloop(Loops), + Msg = {Token, Cnt}, + Next ! Msg, + pass_token(Token, Final, Next, Cnt, Loops) end. pass_token(Token, Final, Next, Cnt, Loops) -> receive - {Token, 1} -> - sendloop(Loops), - Msg = {Token, done}, - Final ! Msg; - {Token, Cnt} -> - sendloop(Loops), - NextCnt = Cnt-1, - Msg = {Token, NextCnt}, - Next ! Msg, - pass_token(Token, Final, Next, NextCnt, Loops) + {Token, 1} -> + sendloop(Loops), + Msg = {Token, done}, + Final ! Msg; + {Token, Cnt} -> + sendloop(Loops), + NextCnt = Cnt-1, + Msg = {Token, NextCnt}, + Next ! Msg, + pass_token(Token, Final, Next, NextCnt, Loops) end. sendloop(Loops) -> diff --git a/lib/runtime_tools/test/dyntrace_SUITE.erl b/lib/runtime_tools/test/dyntrace_SUITE.erl index f3c1cce8f8..7be2f49a8b 100644 --- a/lib/runtime_tools/test/dyntrace_SUITE.erl +++ b/lib/runtime_tools/test/dyntrace_SUITE.erl @@ -20,26 +20,14 @@ -module(dyntrace_SUITE). -include_lib("common_test/include/ct.hrl"). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1]). %% Test cases -export([smoke/1,process/1]). -%% Default timetrap timeout (set in init_per_testcase) --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - Dog = test_server:timetrap(?default_timeout), - [{watchdog,Dog}|Config]. - -end_per_testcase(_Case, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> case erlang:system_info(dynamic_trace) of @@ -73,14 +61,8 @@ init_per_suite(Config) -> end_per_suite(_Config) -> ok. -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - smoke(Config) -> - Emu = ?t:lookup_config(emu_name, Config), + Emu = test_server:lookup_config(emu_name, Config), BinEmu = list_to_binary(Emu), case erlang:system_info(dynamic_trace) of dtrace -> @@ -107,8 +89,7 @@ process(_Config) -> {probe,"process-hibernate"}, {action,[{printf,["hibernate %s %s\n",{arg,0},{arg,1}]}]}, {probe,"process-exit"}, - {action,[{printf,["exit %s %s\n",{arg,0},{arg,1}]}]} - ], + {action,[{printf,["exit %s %s\n",{arg,0},{arg,1}]}]}], F = fun() -> {Pid,Ref} = spawn_monitor(fun my_process/0), Pid ! hibernate, diff --git a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl index 4fb96a0c1b..6ae51d9a26 100644 --- a/lib/runtime_tools/test/erts_alloc_config_SUITE.erl +++ b/lib/runtime_tools/test/erts_alloc_config_SUITE.erl @@ -25,9 +25,7 @@ -include_lib("common_test/include/ct.hrl"). %-compile(export_all). --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2, - init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0, init_per_testcase/2, end_per_testcase/2]). %% Testcases -export([basic/1]). @@ -35,83 +33,63 @@ %% internal export -export([make_basic_config/1]). --define(DEFAULT_TIMEOUT, ?t:minutes(2)). - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 2}}]. all() -> [basic]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - init_per_testcase(Case, Config) when is_list(Config) -> [{testcase, Case}, - {watchdog, ?t:timetrap(?DEFAULT_TIMEOUT)}, {erl_flags_env, save_env()} | Config]. end_per_testcase(_Case, Config) when is_list(Config) -> - ?t:timetrap_cancel(?config(watchdog, Config)), - restore_env(?config(erl_flags_env, Config)), + restore_env(proplists:get_value(erl_flags_env, Config)), ok. %%% %%% The test cases ------------------------------------------------------------ %%% -basic(doc) -> []; -basic(suite) -> []; basic(Config) when is_list(Config) -> - ?line ErtsAllocConfig = privfile("generated", Config), + ErtsAllocConfig = privfile("generated", Config), SbctMod = " +MBsbct 1024 +MHsbct 4096", %% Make sure we have enabled allocators ZFlgs = os:getenv("ERL_ZFLAGS", "") ++ " +Mea max +Mea config", - ?line os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod), + os:putenv("ERL_ZFLAGS", ZFlgs ++ SbctMod), - ?line {ok, Node1} = start_node(Config), - ?line ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]), - ?line stop_node(Node1), + {ok, Node1} = start_node(Config), + ok = rpc:call(Node1, ?MODULE, make_basic_config, [ErtsAllocConfig]), + stop_node(Node1), - ?line display_file(ErtsAllocConfig), + display_file(ErtsAllocConfig), - ?line ManualConfig = privfile("manual", Config), - ?line {ok, IOD} = file:open(ManualConfig, [write]), - ?line io:format(IOD, "~s", ["+MBsbct 2048"]), - ?line file:close(IOD), - ?line display_file(ManualConfig), + ManualConfig = privfile("manual", Config), + {ok, IOD} = file:open(ManualConfig, [write]), + io:format(IOD, "~s", ["+MBsbct 2048"]), + file:close(IOD), + display_file(ManualConfig), - ?line os:putenv("ERL_ZFLAGS", ZFlgs), + os:putenv("ERL_ZFLAGS", ZFlgs), - ?line {ok, Node2} = start_node(Config, - "-args_file " ++ ErtsAllocConfig - ++ " -args_file " ++ ManualConfig), + {ok, Node2} = start_node(Config, + "-args_file " ++ ErtsAllocConfig + ++ " -args_file " ++ ManualConfig), - ?line {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]), + {_, _, _, Cfg} = rpc:call(Node2, erlang, system_info, [allocator]), - ?line stop_node(Node2), + stop_node(Node2), - ?line {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg), - ?line {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg), - ?line {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg), - ?line {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg), + {value,{binary_alloc, BCfg}} = lists:keysearch(binary_alloc, 1, Cfg), + {value,{sbct, 2097152}} = lists:keysearch(sbct, 1, BCfg), + {value,{eheap_alloc, HCfg}} = lists:keysearch(eheap_alloc, 1, Cfg), + {value,{sbct, 4194304}} = lists:keysearch(sbct, 1, HCfg), - ?line ok. + ok. make_basic_config(ErtsAllocConfig) -> %% Save some different scenarios @@ -119,35 +97,35 @@ make_basic_config(ErtsAllocConfig) -> SSBegun = make_ref(), SSDone = make_ref(), SSFun = fun (F) -> - receive - SSDone -> - ok = erts_alloc_config:save_scenario(), - Tester ! SSDone - after 500 -> - ok = erts_alloc_config:save_scenario(), - F(F) - end - end, + receive + SSDone -> + ok = erts_alloc_config:save_scenario(), + Tester ! SSDone + after 500 -> + ok = erts_alloc_config:save_scenario(), + F(F) + end + end, SS = spawn_link(fun () -> - ok = erts_alloc_config:save_scenario(), - Tester ! SSBegun, - SSFun(SSFun) - end), + ok = erts_alloc_config:save_scenario(), + Tester ! SSBegun, + SSFun(SSFun) + end), receive SSBegun -> ok end, Ref = make_ref(), Tab = ets:new(?MODULE, [bag, public]), Ps = lists:map( - fun (_) -> - spawn_link( - fun () -> - ets:insert(Tab, - {self(), - lists:seq(1, 1000)}), - receive after 1000 -> ok end, - Tester ! {Ref, self()} - end) - end, - lists:seq(1, 10000)), + fun (_) -> + spawn_link( + fun () -> + ets:insert(Tab, + {self(), + lists:seq(1, 1000)}), + receive after 1000 -> ok end, + Tester ! {Ref, self()} + end) + end, + lists:seq(1, 10000)), lists:foreach(fun (P) -> receive {Ref, P} -> ok end end, Ps), ets:delete(Tab), SS ! SSDone, @@ -162,35 +140,35 @@ make_basic_config(ErtsAllocConfig) -> %% display_file(FileName) -> - ?t:format("filename: ~s~n", [FileName]), + io:format("filename: ~s~n", [FileName]), {ok, Bin} = file:read_file(FileName), io:format("~s", [binary_to_list(Bin)]), - ?t:format("eof: ~s~n", [FileName]), + io:format("eof: ~s~n", [FileName]), ok. mk_name(Config) when is_list(Config) -> {A, B, C} = now(), list_to_atom(atom_to_list(?MODULE) - ++ "-" ++ atom_to_list(?config(testcase, Config)) - ++ "-" ++ integer_to_list(A) - ++ "-" ++ integer_to_list(B) - ++ "-" ++ integer_to_list(C)). + ++ "-" ++ atom_to_list(proplists:get_value(testcase, Config)) + ++ "-" ++ integer_to_list(A) + ++ "-" ++ integer_to_list(B) + ++ "-" ++ integer_to_list(C)). start_node(Config) -> start_node(Config, ""). start_node(Config, Args) -> - ?line Pa = filename:dirname(code:which(?MODULE)), - ?line ?t:start_node(mk_name(Config), - slave, - [{args, "-pa " ++ Pa ++ " " ++ Args}]). + Pa = filename:dirname(code:which(?MODULE)), + test_server:start_node(mk_name(Config), + slave, + [{args, "-pa " ++ Pa ++ " " ++ Args}]). stop_node(Node) -> - ?line true = ?t:stop_node(Node). + true = test_server:stop_node(Node). privfile(Name, Config) -> - filename:join([?config(priv_dir, Config), - atom_to_list(?config(testcase, Config)) ++ "." ++ Name]). + filename:join([proplists:get_value(priv_dir, Config), + atom_to_list(proplists:get_value(testcase, Config)) ++ "." ++ Name]). save_env() -> {erl_flags, @@ -203,15 +181,15 @@ restore_env(EVar, false) when is_list(EVar) -> restore_env(EVar, ""); restore_env(EVar, "") when is_list(EVar) -> case os:getenv(EVar) of - false -> ok; - "" -> ok; - " " -> ok; - _ -> os:putenv(EVar, " ") + false -> ok; + "" -> ok; + " " -> ok; + _ -> os:putenv(EVar, " ") end; restore_env(EVar, Value) when is_list(EVar), is_list(Value) -> case os:getenv(EVar) of - Value -> ok; - _ -> os:putenv(EVar, Value) + Value -> ok; + _ -> os:putenv(EVar, Value) end. restore_env({erl_flags, AFlgs, Flgs, RFlgs, ZFlgs}) -> diff --git a/lib/runtime_tools/test/runtime_tools_SUITE.erl b/lib/runtime_tools/test/runtime_tools_SUITE.erl index 374d7f6694..6877e1a379 100644 --- a/lib/runtime_tools/test/runtime_tools_SUITE.erl +++ b/lib/runtime_tools/test/runtime_tools_SUITE.erl @@ -21,54 +21,27 @@ -include_lib("common_test/include/ct.hrl"). %% Test server specific exports --export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, - init_per_group/2,end_per_group/2]). --export([init_per_testcase/2, end_per_testcase/2]). +-export([all/0, suite/0]). %% Test cases -export([app_file/1, appup_file/1, start_stop_app/1]). -%% Default timetrap timeout (set in init_per_testcase) --define(default_timeout, ?t:minutes(1)). - -init_per_testcase(_Case, Config) -> - Dog = test_server:timetrap(?default_timeout), - [{watchdog, Dog} | Config]. - -end_per_testcase(_Case, Config) -> - Dog = ?config(watchdog, Config), - ?t:timetrap_cancel(Dog), - ok. - -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap, {minutes, 1}}]. all() -> [app_file, appup_file, start_stop_app]. -groups() -> - []. - -init_per_suite(Config) -> - Config. - -end_per_suite(_Config) -> - ok. - -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - app_file(_Config) -> - ?line ok = ?t:app_test(runtime_tools), + ok = test_server:app_test(runtime_tools), ok. appup_file(_Config) -> - ok = ?t:appup_test(runtime_tools). + ok = test_server:appup_test(runtime_tools). start_stop_app(_Config) -> ok = application:start(runtime_tools), diff --git a/lib/runtime_tools/test/system_information_SUITE.erl b/lib/runtime_tools/test/system_information_SUITE.erl index 5e2e0d17ac..a5a025a1cf 100644 --- a/lib/runtime_tools/test/system_information_SUITE.erl +++ b/lib/runtime_tools/test/system_information_SUITE.erl @@ -230,19 +230,19 @@ api_report(_Config) -> ok. api_to_file(Config) -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), Filename = filename:join([DataDir, "system_information_report_1.dat"]), ok = system_information:to_file(Filename), {ok, _} = file:consult(Filename), {save_config, [{report_name, Filename}]}. api_from_file(Config) -> - {api_to_file, Saved} = ?config(saved_config, Config), - DataDir = ?config(data_dir, Config), + {api_to_file, Saved} = proplists:get_value(saved_config, Config), + DataDir = proplists:get_value(data_dir, Config), Fname1 = filename:join([DataDir, "information_test_report.dat"]), Report1 = system_information:from_file(Fname1), ok = validate_report(Report1), - Fname2 = ?config(report_name, Saved), + Fname2 = proplists:get_value(report_name, Saved), Report2 = system_information:from_file(Fname2), ok = validate_report(Report2), ok. @@ -253,7 +253,7 @@ api_start_stop(_Config) -> ok. validate_server_interface(Config) -> - DataDir = ?config(data_dir, Config), + DataDir = proplists:get_value(data_dir, Config), Fname1 = filename:join([DataDir, "information_test_report.dat"]), %% load old report ok = system_information:load_report(file, Fname1), diff --git a/lib/sasl/doc/src/appup.xml b/lib/sasl/doc/src/appup.xml index a57068492c..6fbdcb9f5b 100644 --- a/lib/sasl/doc/src/appup.xml +++ b/lib/sasl/doc/src/appup.xml @@ -137,7 +137,8 @@ code change. If it is set to <c>{advanced,Extra}</c>, implemented processes using <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>, - <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>, or + <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso>, + <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso>, or <seealso marker="stdlib:gen_event"><c>gen_event</c></seealso> transform their internal state by calling the callback function <c>code_change</c>. Special processes call the callback diff --git a/lib/sasl/doc/src/error_logging.xml b/lib/sasl/doc/src/error_logging.xml index 86d0c811e9..8464a41ff9 100644 --- a/lib/sasl/doc/src/error_logging.xml +++ b/lib/sasl/doc/src/error_logging.xml @@ -90,8 +90,9 @@ a process terminates with an unexpected reason, which is any reason other than <c>normal</c>, <c>shutdown</c>, or <c>{shutdown,Term}</c>. Processes using behaviors - <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso> or - <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso> + <seealso marker="stdlib:gen_server"><c>gen_server</c></seealso>, + <seealso marker="stdlib:gen_fsm"><c>gen_fsm</c></seealso> or + <seealso marker="stdlib:gen_statem"><c>gen_statem</c></seealso> are examples of such processes. A crash report contains the following items:</p> <taglist> <tag><c>Crasher</c></tag> diff --git a/lib/sasl/test/release_handler_SUITE.erl b/lib/sasl/test/release_handler_SUITE.erl index ee620dcdb4..4dcaec03a7 100644 --- a/lib/sasl/test/release_handler_SUITE.erl +++ b/lib/sasl/test/release_handler_SUITE.erl @@ -54,7 +54,7 @@ unix_cases() -> end, [target_system, target_system_unicode] ++ RunErlCases ++ cases(). -win32_cases() -> +win32_cases() -> [{group,release} | cases()]. %% Cases that can be run on all platforms @@ -89,11 +89,16 @@ groups() -> %% {group,release} %% Top group for all cases using run_erl init_per_group(release, Config) -> - Dog = ?t:timetrap(?default_timeout), - P1gInstall = filename:join(priv_dir(Config),p1g_install), - ok = create_p1g(Config,P1gInstall), - ok = create_p1h(Config), - ?t:timetrap_cancel(Dog); + case {os:type(), os:version()} of + {{win32, nt}, Vsn} when Vsn > {6,1,999999} -> + {skip, "Requires admin privileges on Win 8 and later"}; + _ -> + Dog = ?t:timetrap(?default_timeout), + P1gInstall = filename:join(priv_dir(Config),p1g_install), + ok = create_p1g(Config,P1gInstall), + ok = create_p1h(Config), + ?t:timetrap_cancel(Dog) + end; %% {group,release_single} %% Subgroup of {group,release}, contains all cases that are not diff --git a/lib/ssh/test/ssh_benchmark_SUITE.erl b/lib/ssh/test/ssh_benchmark_SUITE.erl index 8ec1017642..6e1d18cc95 100644 --- a/lib/ssh/test/ssh_benchmark_SUITE.erl +++ b/lib/ssh/test/ssh_benchmark_SUITE.erl @@ -104,7 +104,7 @@ init_sftp_dirs(Config) -> DstDir = filename:join(UserDir, "sftp_dst"), ok = file:make_dir(DstDir), N = 100 * 1024*1024, - ok = file:write_file(filename:join(SrcDir,SrcFile), crypto:rand_bytes(N)), + ok = file:write_file(filename:join(SrcDir,SrcFile), crypto:strong_rand_bytes(N)), [{sftp_src_dir,SrcDir}, {sftp_dst_dir,DstDir}, {src_file,SrcFile}, {sftp_size,N} | Config]. diff --git a/lib/ssh/test/ssh_sftp_SUITE.erl b/lib/ssh/test/ssh_sftp_SUITE.erl index c4bb02841b..cd6c5f82b9 100644 --- a/lib/ssh/test/ssh_sftp_SUITE.erl +++ b/lib/ssh/test/ssh_sftp_SUITE.erl @@ -869,7 +869,7 @@ aes_cbc256_crypto_tar(Config) -> {"d1",fn("d1",Config)} % Dir ]), Key = <<"This is a 256 bit key. Boring...">>, - Ivec0 = crypto:rand_bytes(16), + Ivec0 = crypto:strong_rand_bytes(16), DataSize = 1024, % data_size rem 16 = 0 for aes_cbc Cinitw = fun() -> {ok, Ivec0, DataSize} end, @@ -914,7 +914,7 @@ aes_ctr_stream_crypto_tar(Config) -> {"d1",fn("d1",Config)} % Dir ]), Key = <<"This is a 256 bit key. Boring...">>, - Ivec0 = crypto:rand_bytes(16), + Ivec0 = crypto:strong_rand_bytes(16), Cinitw = Cinitr = fun() -> {ok, crypto:stream_init(aes_ctr,Key,Ivec0)} end, diff --git a/lib/ssh/test/ssh_test_lib.erl b/lib/ssh/test/ssh_test_lib.erl index abbd4857c9..4db7d09ccd 100644 --- a/lib/ssh/test/ssh_test_lib.erl +++ b/lib/ssh/test/ssh_test_lib.erl @@ -100,7 +100,7 @@ std_simple_sftp(Host, Port, Config, Opts) -> DataFile = filename:join(UserDir, "test.data"), ConnectionRef = ssh_test_lib:std_connect(Config, Host, Port, Opts), {ok, ChannelRef} = ssh_sftp:start_channel(ConnectionRef), - Data = crypto:rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)), + Data = crypto:strong_rand_bytes(proplists:get_value(std_simple_sftp_size,Config,10)), ok = ssh_sftp:write_file(ChannelRef, DataFile, Data), {ok,ReadData} = file:read_file(DataFile), ok = ssh:close(ConnectionRef), @@ -354,7 +354,7 @@ setup_rsa_pass_pharse(DataDir, UserDir, Phrase) -> setup_pass_pharse(KeyBin, OutFile, Phrase) -> [{KeyType, _,_} = Entry0] = public_key:pem_decode(KeyBin), Key = public_key:pem_entry_decode(Entry0), - Salt = crypto:rand_bytes(8), + Salt = crypto:strong_rand_bytes(8), Entry = public_key:pem_entry_encode(KeyType, Key, {{"DES-CBC", Salt}, Phrase}), Pem = public_key:pem_encode([Entry]), diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml index 7c55dd4c96..00419b63db 100644 --- a/lib/ssl/doc/src/notes.xml +++ b/lib/ssl/doc/src/notes.xml @@ -28,6 +28,36 @@ <p>This document describes the changes made to the SSL application.</p> +<section><title>SSL 7.3.1</title> + + <section><title>Fixed Bugs and Malfunctions</title> + <list> + <item> + <p> + Corrections to cipher suite handling using the 3 and 4 + tuple format in addition to commit + 89d7e21cf4ae988c57c8ef047bfe85127875c70c</p> + <p> + Own Id: OTP-13511</p> + </item> + </list> + </section> + + + <section><title>Improvements and New Features</title> + <list> + <item> + <p> + Make values for the TLS-1.2 signature_algorithms + extension configurable</p> + <p> + Own Id: OTP-13261</p> + </item> + </list> + </section> + +</section> + <section><title>SSL 7.3</title> <section><title>Fixed Bugs and Malfunctions</title> diff --git a/lib/ssl/src/ssl.appup.src b/lib/ssl/src/ssl.appup.src index 057906bcb3..11728128c4 100644 --- a/lib/ssl/src/ssl.appup.src +++ b/lib/ssl/src/ssl.appup.src @@ -1,9 +1,6 @@ %% -*- erlang -*- {"%VSN%", [ - {<<"7\\.2">>, [{load_module, tls_connection, soft_purge, soft_purge, []}, - {load_module, ssl_tls_dist_proxy, soft_purge, soft_purge, []} - ]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, @@ -11,9 +8,6 @@ {<<"3\\..*">>, [{restart_application, ssl}]} ], [ - {<<"7\\.2">>, [{load_module, tls_connection, soft_purge, soft_purge, []}, - {load_module, ssl_tls_dist_proxy, soft_purge, soft_purge, []} - ]}, {<<"7\\..*">>, [{restart_application, ssl}]}, {<<"6\\..*">>, [{restart_application, ssl}]}, {<<"5\\..*">>, [{restart_application, ssl}]}, diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 4bcd6ddb0e..025e8cea61 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -42,7 +42,7 @@ renegotiate/1, prf/5, negotiated_protocol/1, negotiated_next_protocol/1, connection_information/1, connection_information/2]). %% Misc --export([random_bytes/1, handle_options/2]). +-export([handle_options/2]). -deprecated({negotiated_next_protocol, 1, next_major_release}). -deprecated({connection_info, 1, next_major_release}). @@ -581,22 +581,6 @@ format_error(Error) -> Other end. -%%-------------------------------------------------------------------- --spec random_bytes(integer()) -> binary(). - -%% -%% Description: Generates cryptographically secure random sequence if possible -%% fallbacks on pseudo random function -%%-------------------------------------------------------------------- -random_bytes(N) -> - try crypto:strong_rand_bytes(N) of - RandBytes -> - RandBytes - catch - error:low_entropy -> - crypto:rand_bytes(N) - end. - %%%-------------------------------------------------------------- %%% Internal functions %%%-------------------------------------------------------------------- @@ -1105,10 +1089,7 @@ binary_cipher_suites(Version, []) -> %% Defaults to all supported suites that does %% not require explicit configuration ssl_cipher:filter_suites(ssl_cipher:suites(Version)); -binary_cipher_suites(Version, [{_,_,_,_}| _] = Ciphers0) -> %% Backwards compatibility - Ciphers = [{KeyExchange, Cipher, Hash} || {KeyExchange, Cipher, Hash, _} <- Ciphers0], - binary_cipher_suites(Version, Ciphers); -binary_cipher_suites(Version, [{_,_,_}| _] = Ciphers0) -> +binary_cipher_suites(Version, [Tuple|_] = Ciphers0) when is_tuple(Tuple) -> Ciphers = [ssl_cipher:suite(C) || C <- Ciphers0], binary_cipher_suites(Version, Ciphers); diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl index e66f253a70..544d9b41cd 100644 --- a/lib/ssl/src/ssl_cipher.erl +++ b/lib/ssl/src/ssl_cipher.erl @@ -1,4 +1,4 @@ -%% +% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2007-2016. All Rights Reserved. @@ -39,7 +39,8 @@ suite/1, suites/1, all_suites/1, ec_keyed_suites/0, anonymous_suites/1, psk_suites/1, srp_suites/0, rc4_suites/1, des_suites/1, openssl_suite/1, openssl_suite_name/1, filter/2, filter_suites/1, - hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1]). + hash_algorithm/1, sign_algorithm/1, is_acceptable_hash/2, is_fallback/1, + random_bytes/1]). -export_type([cipher_suite/0, erl_cipher_suite/0, openssl_cipher_suite/0, @@ -49,7 +50,8 @@ | aes_128_cbc | aes_256_cbc | aes_128_gcm | aes_256_gcm | chacha20_poly1305. -type hash() :: null | sha | md5 | sha224 | sha256 | sha384 | sha512. -type sign_algo() :: rsa | dsa | ecdsa. --type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. +-type key_algo() :: null | rsa | dhe_rsa | dhe_dss | ecdhe_ecdsa| ecdh_ecdsa | ecdh_rsa| srp_rsa| srp_dss | + psk | dhe_psk | rsa_psk | dh_anon | ecdh_anon | srp_anon. -type erl_cipher_suite() :: {key_algo(), cipher(), hash()} % Pre TLS 1.2 %% TLS 1.2, internally PRE TLS 1.2 will use default_prf | {key_algo(), cipher(), hash(), hash() | default_prf}. @@ -102,7 +104,7 @@ cipher_init(?RC4, IV, Key) -> State = crypto:stream_init(rc4, Key), #cipher_state{iv = IV, key = Key, state = State}; cipher_init(?AES_GCM, IV, Key) -> - <<Nonce:64>> = ssl:random_bytes(8), + <<Nonce:64>> = random_bytes(8), #cipher_state{iv = IV, key = Key, nonce = Nonce}; cipher_init(_BCA, IV, Key) -> #cipher_state{iv = IV, key = Key}. @@ -853,17 +855,17 @@ suite({rsa_psk, aes_256_cbc,sha}) -> %%% TLS 1.2 PSK Cipher Suites RFC 5487 -suite({psk, aes_128_gcm, null}) -> +suite({psk, aes_128_gcm, null, sha256}) -> ?TLS_PSK_WITH_AES_128_GCM_SHA256; -suite({psk, aes_256_gcm, null}) -> +suite({psk, aes_256_gcm, null, sha384}) -> ?TLS_PSK_WITH_AES_256_GCM_SHA384; -suite({dhe_psk, aes_128_gcm, null}) -> +suite({dhe_psk, aes_128_gcm, null, sha256}) -> ?TLS_DHE_PSK_WITH_AES_128_GCM_SHA256; -suite({dhe_psk, aes_256_gcm, null}) -> +suite({dhe_psk, aes_256_gcm, null, sha384}) -> ?TLS_DHE_PSK_WITH_AES_256_GCM_SHA384; -suite({rsa_psk, aes_128_gcm, null}) -> +suite({rsa_psk, aes_128_gcm, null, sha256}) -> ?TLS_RSA_PSK_WITH_AES_128_GCM_SHA256; -suite({rsa_psk, aes_256_gcm, null}) -> +suite({rsa_psk, aes_256_gcm, null, sha384}) -> ?TLS_RSA_PSK_WITH_AES_256_GCM_SHA384; suite({psk, aes_128_cbc, sha256}) -> @@ -970,74 +972,74 @@ suite({ecdh_anon, aes_256_cbc, sha}) -> ?TLS_ECDH_anon_WITH_AES_256_CBC_SHA; %%% RFC 5289 EC TLS suites -suite({ecdhe_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdhe_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdhe_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_ecdsa, aes_128_cbc, sha256}) -> +suite({ecdh_ecdsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_ecdsa, aes_256_cbc, sha384}) -> +suite({ecdh_ecdsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384; -suite({ecdhe_rsa, aes_128_cbc, sha256}) -> +suite({ecdhe_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdhe_rsa, aes_256_cbc, sha384}) -> +suite({ecdhe_rsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384; -suite({ecdh_rsa, aes_128_cbc, sha256}) -> +suite({ecdh_rsa, aes_128_cbc, sha256, sha256}) -> ?TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256; -suite({ecdh_rsa, aes_256_cbc, sha384}) -> +suite({ecdh_rsa, aes_256_cbc, sha384, sha384}) -> ?TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384; %% RFC 5288 AES-GCM Cipher Suites -suite({rsa, aes_128_gcm, null}) -> +suite({rsa, aes_128_gcm, null, sha256}) -> ?TLS_RSA_WITH_AES_128_GCM_SHA256; suite({rsa, aes_256_gcm, null}) -> ?TLS_RSA_WITH_AES_256_GCM_SHA384; -suite({dhe_rsa, aes_128_gcm, null}) -> +suite({dhe_rsa, aes_128_gcm, null, sha384}) -> ?TLS_DHE_RSA_WITH_AES_128_GCM_SHA256; -suite({dhe_rsa, aes_256_gcm, null}) -> +suite({dhe_rsa, aes_256_gcm, null, sha256}) -> ?TLS_DHE_RSA_WITH_AES_256_GCM_SHA384; -suite({dh_rsa, aes_128_gcm, null}) -> +suite({dh_rsa, aes_128_gcm, null, sha384}) -> ?TLS_DH_RSA_WITH_AES_128_GCM_SHA256; -suite({dh_rsa, aes_256_gcm, null}) -> +suite({dh_rsa, aes_256_gcm, null, sha256}) -> ?TLS_DH_RSA_WITH_AES_256_GCM_SHA384; -suite({dhe_dss, aes_128_gcm, null}) -> +suite({dhe_dss, aes_128_gcm, null, sha384}) -> ?TLS_DHE_DSS_WITH_AES_128_GCM_SHA256; -suite({dhe_dss, aes_256_gcm, null}) -> +suite({dhe_dss, aes_256_gcm, null, sha256}) -> ?TLS_DHE_DSS_WITH_AES_256_GCM_SHA384; -suite({dh_dss, aes_128_gcm, null}) -> +suite({dh_dss, aes_128_gcm, null, sha384}) -> ?TLS_DH_DSS_WITH_AES_128_GCM_SHA256; -suite({dh_dss, aes_256_gcm, null}) -> +suite({dh_dss, aes_256_gcm, null, sha384}) -> ?TLS_DH_DSS_WITH_AES_256_GCM_SHA384; -suite({dh_anon, aes_128_gcm, null}) -> +suite({dh_anon, aes_128_gcm, null, sha256}) -> ?TLS_DH_anon_WITH_AES_128_GCM_SHA256; -suite({dh_anon, aes_256_gcm, null}) -> +suite({dh_anon, aes_256_gcm, null, sha384}) -> ?TLS_DH_anon_WITH_AES_256_GCM_SHA384; %% RFC 5289 ECC AES-GCM Cipher Suites -suite({ecdhe_ecdsa, aes_128_gcm, null}) -> +suite({ecdhe_ecdsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256; -suite({ecdhe_ecdsa, aes_256_gcm, null}) -> +suite({ecdhe_ecdsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384; -suite({ecdh_ecdsa, aes_128_gcm, null}) -> +suite({ecdh_ecdsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256; -suite({ecdh_ecdsa, aes_256_gcm, null}) -> +suite({ecdh_ecdsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384; -suite({ecdhe_rsa, aes_128_gcm, null}) -> +suite({ecdhe_rsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256; -suite({ecdhe_rsa, aes_256_gcm, null}) -> +suite({ecdhe_rsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384; -suite({ecdh_rsa, aes_128_gcm, null}) -> +suite({ecdh_rsa, aes_128_gcm, null, sha256}) -> ?TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256; -suite({ecdh_rsa, aes_256_gcm, null}) -> +suite({ecdh_rsa, aes_256_gcm, null, sha384}) -> ?TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384; %% draft-agl-tls-chacha20poly1305-04 Chacha20/Poly1305 Suites -suite({ecdhe_rsa, chacha20_poly1305, null}) -> +suite({ecdhe_rsa, chacha20_poly1305, null, sha256}) -> ?TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256; -suite({ecdhe_ecdsa, chacha20_poly1305, null}) -> +suite({ecdhe_ecdsa, chacha20_poly1305, null, sha256}) -> ?TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256; -suite({dhe_rsa, chacha20_poly1305, null}) -> +suite({dhe_rsa, chacha20_poly1305, null, sha256}) -> ?TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256. %%-------------------------------------------------------------------- @@ -1472,6 +1474,16 @@ is_acceptable_prf(Prf, Algos) -> is_fallback(CipherSuites)-> lists:member(?TLS_FALLBACK_SCSV, CipherSuites). + +%%-------------------------------------------------------------------- +-spec random_bytes(integer()) -> binary(). + +%% +%% Description: Generates cryptographically secure random sequence +%%-------------------------------------------------------------------- +random_bytes(N) -> + crypto:strong_rand_bytes(N). + %%-------------------------------------------------------------------- %%% Internal functions %%-------------------------------------------------------------------- @@ -1712,7 +1724,7 @@ get_padding_aux(BlockSize, PadLength) -> random_iv(IV) -> IVSz = byte_size(IV), - ssl:random_bytes(IVSz). + random_bytes(IVSz). next_iv(Bin, IV) -> BinSz = byte_size(Bin), diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index 1568e8559f..0073e86e26 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -502,7 +502,7 @@ certify(#server_hello_done{}, role = client, key_algorithm = Alg} = State0, Connection) when Alg == rsa_psk -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), RSAPremasterSecret = <<?BYTE(Major), ?BYTE(Minor), Rand/binary>>, case ssl_handshake:premaster_secret({Alg, PSKIdentity}, PSKLookup, RSAPremasterSecret) of #alert{} = Alert -> @@ -1885,7 +1885,7 @@ handle_resumed_session(SessId, #state{connection_states = ConnectionStates0, end. make_premaster_secret({MajVer, MinVer}, rsa) -> - Rand = ssl:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), + Rand = ssl_cipher:random_bytes(?NUM_OF_PREMASTERSECRET_BYTES-2), <<?BYTE(MajVer), ?BYTE(MinVer), Rand/binary>>; make_premaster_secret(_, _) -> undefined. diff --git a/lib/ssl/src/ssl_manager.erl b/lib/ssl/src/ssl_manager.erl index e273581de9..2349158b11 100644 --- a/lib/ssl/src/ssl_manager.erl +++ b/lib/ssl/src/ssl_manager.erl @@ -551,7 +551,7 @@ last_delay_timer({_,_}, TRef, {_, LastClient}) -> new_id(_, 0, _, _) -> <<>>; new_id(Port, Tries, Cache, CacheCb) -> - Id = crypto:rand_bytes(?NUM_OF_SESSION_ID_BYTES), + Id = ssl_cipher:random_bytes(?NUM_OF_SESSION_ID_BYTES), case CacheCb:lookup(Cache, {Port, Id}) of undefined -> Now = erlang:monotonic_time(), diff --git a/lib/ssl/src/ssl_record.erl b/lib/ssl/src/ssl_record.erl index ecff950668..866bfcef7e 100644 --- a/lib/ssl/src/ssl_record.erl +++ b/lib/ssl/src/ssl_record.erl @@ -460,7 +460,7 @@ empty_security_params(ConnectionEnd = ?SERVER) -> random() -> Secs_since_1970 = calendar:datetime_to_gregorian_seconds( calendar:universal_time()) - 62167219200, - Random_28_bytes = crypto:rand_bytes(28), + Random_28_bytes = ssl_cipher:random_bytes(28), <<?UINT32(Secs_since_1970), Random_28_bytes/binary>>. dtls_next_epoch(#connection_state{epoch = undefined}) -> %% SSL/TLS diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index 50313e6a22..0c3c7aa39b 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -168,6 +168,7 @@ renegotiate_tests() -> cipher_tests() -> [cipher_suites, + cipher_suites_mix, ciphers_rsa_signed_certs, ciphers_rsa_signed_certs_openssl_names, ciphers_dsa_signed_certs, @@ -913,6 +914,31 @@ cipher_suites(Config) when is_list(Config) -> [_|_] =ssl:cipher_suites(openssl). %%-------------------------------------------------------------------- +cipher_suites_mix() -> + [{doc,"Test to have old and new cipher suites at the same time"}]. + +cipher_suites_mix(Config) when is_list(Config) -> + CipherSuites = [{ecdh_rsa,aes_128_cbc,sha256,sha256}, {rsa,aes_128_cbc,sha}], + ClientOpts = ?config(client_opts, Config), + ServerOpts = ?config(server_opts, Config), + + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, [{ciphers, CipherSuites} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). +%%-------------------------------------------------------------------- socket_options() -> [{doc,"Test API function getopts/2 and setopts/2"}]. @@ -1555,7 +1581,7 @@ tcp_connect_big(Config) when is_list(Config) -> {_, ServerNode, Hostname} = ssl_test_lib:run_where(Config), TcpOpts = [binary, {reuseaddr, true}], - Rand = crypto:rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), + Rand = crypto:strong_rand_bytes(?MAX_CIPHER_TEXT_LENGTH+1), Server = ssl_test_lib:start_upgrade_server_error([{node, ServerNode}, {port, 0}, {from, self()}, {timeout, 5000}, diff --git a/lib/ssl/test/ssl_dist_SUITE.erl b/lib/ssl/test/ssl_dist_SUITE.erl index bd0ddde090..e7cbfa63f4 100644 --- a/lib/ssl/test/ssl_dist_SUITE.erl +++ b/lib/ssl/test/ssl_dist_SUITE.erl @@ -194,7 +194,7 @@ payload(Config) when is_list(Config) -> ok = apply_on_ssl_node( NH2, fun () -> - Msg = crypto:rand_bytes(100000), + Msg = crypto:strong_rand_bytes(100000), SslPid ! {self(), Msg}, receive {SslPid, Msg} -> diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index 2cd23eb3b8..db9e1c3d38 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -349,7 +349,7 @@ wait_for_result(Pid, Msg) -> user_lookup(psk, _Identity, UserState) -> {ok, UserState}; user_lookup(srp, Username, _UserState) -> - Salt = ssl:random_bytes(16), + Salt = ssl_cipher:random_bytes(16), UserPassHash = crypto:hash(sha, [Salt, crypto:hash(sha, [Username, <<$:>>, <<"secret">>])]), {ok, {srp_1024, Salt, UserPassHash}}. @@ -905,8 +905,8 @@ anonymous_suites() -> {dh_anon, '3des_ede_cbc', sha}, {dh_anon, aes_128_cbc, sha}, {dh_anon, aes_256_cbc, sha}, - {dh_anon, aes_128_gcm, null}, - {dh_anon, aes_256_gcm, null}, + {dh_anon, aes_128_gcm, null, sha256}, + {dh_anon, aes_256_gcm, null, sha384}, {ecdh_anon,rc4_128,sha}, {ecdh_anon,'3des_ede_cbc',sha}, {ecdh_anon,aes_128_cbc,sha}, @@ -933,12 +933,12 @@ psk_suites() -> {rsa_psk, aes_256_cbc, sha}, {rsa_psk, aes_128_cbc, sha256}, {rsa_psk, aes_256_cbc, sha384}, - {psk, aes_128_gcm, null}, - {psk, aes_256_gcm, null}, - {dhe_psk, aes_128_gcm, null}, - {dhe_psk, aes_256_gcm, null}, - {rsa_psk, aes_128_gcm, null}, - {rsa_psk, aes_256_gcm, null}], + {psk, aes_128_gcm, null, sha256}, + {psk, aes_256_gcm, null, sha384}, + {dhe_psk, aes_128_gcm, null, sha256}, + {dhe_psk, aes_256_gcm, null, sha384}, + {rsa_psk, aes_128_gcm, null, sha256}, + {rsa_psk, aes_256_gcm, null, sha384}], ssl_cipher:filter_suites(Suites). psk_anon_suites() -> diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk index 47cb2909fb..8c732002de 100644 --- a/lib/ssl/vsn.mk +++ b/lib/ssl/vsn.mk @@ -1 +1 @@ -SSL_VSN = 7.3 +SSL_VSN = 7.3.1 diff --git a/lib/stdlib/doc/src/Makefile b/lib/stdlib/doc/src/Makefile index 196c86748f..26602764a6 100644 --- a/lib/stdlib/doc/src/Makefile +++ b/lib/stdlib/doc/src/Makefile @@ -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. @@ -68,6 +68,7 @@ XML_REF3_FILES = \ gen_event.xml \ gen_fsm.xml \ gen_server.xml \ + gen_statem.xml \ io.xml \ io_lib.xml \ lib.xml \ diff --git a/lib/stdlib/doc/src/binary.xml b/lib/stdlib/doc/src/binary.xml index 5402fd0163..933157fc34 100644 --- a/lib/stdlib/doc/src/binary.xml +++ b/lib/stdlib/doc/src/binary.xml @@ -366,7 +366,7 @@ <code> 1> binary:matches(<<"abcde">>, - [<<"bcde">>,<<"bc">>>,<<"de">>],[]). + [<<"bcde">>,<<"bc">>,<<"de">>],[]). [{1,4}] </code> diff --git a/lib/stdlib/doc/src/gen_fsm.xml b/lib/stdlib/doc/src/gen_fsm.xml index 37e7c6ed17..835e252704 100644 --- a/lib/stdlib/doc/src/gen_fsm.xml +++ b/lib/stdlib/doc/src/gen_fsm.xml @@ -31,12 +31,23 @@ <module>gen_fsm</module> <modulesummary>Generic Finite State Machine Behaviour</modulesummary> <description> + <note> + <p> + There is a new behaviour + <seealso marker="gen_statem"><c>gen_statem</c></seealso> + that is intended to replace <c>gen_fsm</c> for new code. + It has the same features and add some really useful. + This module will not be removed for the foreseeable future + to keep old state machine implementations running. + </p> + </note> <p>A behaviour module for implementing a finite state machine. A generic finite state machine process (gen_fsm) implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. It will also fit into an OTP supervision tree. Refer to - <seealso marker="doc/design_principles:fsm">OTP Design Principles</seealso> for more information.</p> + <seealso marker="doc/design_principles:fsm">OTP Design Principles</seealso> for more information. + </p> <p>A gen_fsm assumes all specific parts to be located in a callback module exporting a pre-defined set of functions. The relationship between the behaviour functions and the callback functions can be @@ -848,6 +859,7 @@ gen_fsm:sync_send_all_state_event -----> Module:handle_sync_event/4 <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_server">gen_server(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="supervisor">supervisor(3)</seealso>, <seealso marker="proc_lib">proc_lib(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> diff --git a/lib/stdlib/doc/src/gen_server.xml b/lib/stdlib/doc/src/gen_server.xml index 02da11250a..10dc978afc 100644 --- a/lib/stdlib/doc/src/gen_server.xml +++ b/lib/stdlib/doc/src/gen_server.xml @@ -715,6 +715,7 @@ gen_server:abcast -----> Module:handle_cast/2 <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_fsm">gen_fsm(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="supervisor">supervisor(3)</seealso>, <seealso marker="proc_lib">proc_lib(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> diff --git a/lib/stdlib/doc/src/gen_statem.xml b/lib/stdlib/doc/src/gen_statem.xml new file mode 100644 index 0000000000..ec7f267c64 --- /dev/null +++ b/lib/stdlib/doc/src/gen_statem.xml @@ -0,0 +1,1724 @@ +<?xml version="1.0" encoding="utf-8" ?> +<!DOCTYPE erlref SYSTEM "erlref.dtd"> + +<erlref> + <header> + <copyright> + <year>2016</year> + <holder>Ericsson AB. All Rights Reserved.</holder> + </copyright> + <legalnotice> + 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. + + </legalnotice> + + <title>gen_statem</title> + <prepared></prepared> + <docno></docno> + <date></date> + <rev></rev> + </header> + <module>gen_statem</module> + <modulesummary>Generic State Machine Behaviour</modulesummary> + <description> + <p> + A behaviour module for implementing a state machine. Two + <seealso marker="#type-callback_mode"><em>callback modes</em></seealso> + are supported. One for finite state machines + (<seealso marker="gen_fsm"><c>gen_fsm</c></seealso> like) + that requires the state to be an atom and uses that state as + the name of the current callback function, + and one without restriction on the state data type + that uses one callback function for all states. + </p> + <p> + This is a new behaviour in OTP-19.0. + It has been thoroughly reviewed, is stable enough + to be used by at least two heavy OTP applications, and is here to stay. + But depending on user feedback, we do not expect + but might find it necessary to make minor + not backwards compatible changes into OTP-20.0, + so its state can be designated as "not quite experimental"... + </p> + <p> + The <c>gen_statem</c> behaviour is intended to replace + <seealso marker="gen_fsm"><c>gen_fsm</c></seealso> for new code. + It has the same features and add some really useful: + </p> + <list type="bulleted"> + <item>State code is gathered</item> + <item>The state can be any term</item> + <item>Events can be postponed</item> + <item>Events can be self generated</item> + <item>A reply can be sent from a later state</item> + <item>There can be multiple sys traceable replies</item> + </list> + <p> + The callback model(s) for <c>gen_statem</c> differs from + the one for <seealso marker="gen_fsm"><c>gen_fsm</c></seealso>, + but it is still fairly easy to rewrite + from <c>gen_fsm</c> to <c>gen_statem</c>. + </p> + <p> + A generic state machine process (<c>gen_statem</c>) implemented + using this module will have a standard set of interface functions + and include functionality for tracing and error reporting. + It will also fit into an OTP supervision tree. Refer to + <seealso marker="doc/design_principles:statem"> + OTP Design Principles + </seealso> + for more information. + </p> + <p> + A <c>gen_statem</c> assumes all specific parts to be located in a + callback module exporting a pre-defined set of functions. + The relationship between the behaviour functions and the callback + functions can be illustrated as follows:</p> + <pre> +gen_statem module Callback module +----------------- --------------- +gen_statem:start +gen_statem:start_link -----> Module:init/1 + +gen_statem:stop -----> Module:terminate/3 + +gen_statem:call +gen_statem:cast +erlang:send +erlang:'!' -----> Module:StateName/3 + Module:handle_event/4 + +- -----> Module:terminate/3 + +- -----> Module:code_change/4</pre> + <p> + Events are of different + <seealso marker="#type-event_type">types</seealso> + so the callback functions can know the origin of an event + and how to respond. + </p> + <p> + If a callback function fails or returns a bad value, + the <c>gen_statem</c> will terminate. An exception of class + <seealso marker="erts:erlang#throw/1"><c>throw</c></seealso>, + however, is not regarded as an error but as a valid return. + </p> + <marker id="state_function" /> + <p> + The "<em>state function</em>" for a specific + <seealso marker="#type-state">state</seealso> + in a <c>gen_statem</c> is the callback function that is called + for all events in this state, and is selected depending on which + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + that the implementation specifies when the the server starts. + </p> + <p> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c>, the state has to be an atom and + is used as the state function name. See + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</c> + </seealso>. + This gathers all code for a specific state + in one function and hence dispatches on state first. + Note that in this mode 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. + </p> + <p> + When the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>handle_event_function</c> the state can be any term + and the state function name is + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> + </seealso>. + This makes it easy to dispatch on state or on event as you desire. + Be careful about which events you handle in which + states so you do not accidentally postpone one event + forever creating an infinite busy loop. + </p> + <p> + The <c>gen_statem</c> enqueues incoming events in order of arrival + and presents these to the + <seealso marker="#state_function">state function</seealso> + in that order. The state function can postpone an event + so it is not retried in the current state. + After a state change the queue restarts with the postponed events. + </p> + <p> + The <c>gen_statem</c> event queue model is sufficient + to emulate the normal process message queue with selective receive. + Postponing an event corresponds to not matching it + in a receive statement and changing states corresponds + to entering a new receive statement. + </p> + <p> + The <seealso marker="#state_function">state function</seealso> + can insert events using the + <seealso marker="#type-action"> + <c>action()</c> <c>next_event</c> + </seealso> + and such an event is inserted as the next to present + to the state function. That is: as if it is + the oldest incoming event. There is a dedicated + <seealso marker="#type-event_type"> + <c>event_type()</c> + </seealso> + <c>internal</c> that can be used for such events making them impossible + to mistake for external events. + </p> + <p> + Inserting an event replaces the trick of calling your own + state handling functions that you often would have to + resort to in for example <seealso marker="gen_fsm"><c>gen_fsm</c></seealso> + to force processing an inserted event before others. + A warning, though: if you in <c>gen_statem</c> for example + postpone an event in one state and then call some other state function of yours, + you have not changed states and hence the postponed event will not be retried, + which is logical but might be confusing. + </p> + <p> + See the type + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + for the details of a state transition. + </p> + <p> + A <c>gen_statem</c> handles system messages as documented in + <seealso marker="sys"><c>sys</c></seealso>. + The <c>sys</c>module can be used for debugging a <c>gen_statem</c>. + </p> + <p> + Note that a <c>gen_statem</c> does not trap exit signals + automatically, this must be explicitly initiated in + the callback module (by calling + <seealso marker="erts:erlang#process_flag/2"> + <c>process_flag(trap_exit, true)</c></seealso>. + </p> + <p> + Unless otherwise stated, all functions in this module fail if + the specified <c>gen_statem</c> does not exist or + if bad arguments are given. + </p> + <p> + The <c>gen_statem</c> process can go into hibernation; see + <seealso marker="proc_lib#hibernate/3"> + <c>proc_lib:hibernate/3</c>. + </seealso> + It is done when a + <seealso marker="#state_function">state function</seealso> or + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + specifies <c>hibernate</c> in the returned + <seealso marker="#type-action"><c>Actions</c></seealso> + list. This feature might be useful to reclaim process heap memory + while the server is expected to be idle for a long time. + However, use this feature with care + since hibernation can be too costly + to use after every event; see + <seealso marker="erts:erlang#hibernate/3"> + <c>erlang:hibernate/3</c>. + </seealso> + </p> + </description> + + <section> + <title>EXAMPLE</title> + <p> + This example shows a simple pushbutton model + for a toggling pushbutton implemented with + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + <c>state_functions</c>. + You can push the button and it replies if it went on or off, + and you can ask for a count of how many times it has been + pushed to on. + </p> + <p>This is the complete callback module file <c>pushbutton.erl</c>:</p> + <code type="erl"> +-module(pushbutton). +-behaviour(gen_statem). + +-export([start/0,push/0,get_count/0,stop/0]). +-export([terminate/3,code_change/4,init/1]). +-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. +start() -> + gen_statem:start({local,name()}, ?MODULE, [], []). +push() -> + gen_statem:call(name(), push). +get_count() -> + gen_statem:call(name(), get_count). +stop() -> + gen_statem:stop(name()). + +%% Mandatory callback functions +terminate(_Reason, _State, _Data) -> + void. +code_change(_Vsn, State, Data, _Extra) -> + {callback_mode(),State,Data}. +init([]) -> + %% Set the callback mode and initial state + data. + %% Data is used only as a counter. + State = off, Data = 0, + {callback_mode(),State,Data}. + + +%%% State functions + +off({call,From}, push, Data) -> + %% Go to 'on', increment count and reply + %% that the resulting status is 'on' + {next_state,on,Data+1,[{reply,From,on}]}; +off(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +on({call,From}, push, Data) -> + %% Go to 'off' and reply that the resulting status is 'off' + {next_state,off,Data,[{reply,From,off}]}; +on(EventType, EventContent, Data) -> + handle_event(EventType, EventContent, Data). + +%% Handle events common to all states +handle_event({call,From}, get_count, Data) -> + %% Reply with the current count + {keep_state,Data,[{reply,From,Data}]}; +handle_event(_, _, Data) -> + %% Ignore all other events + {keep_state,Data}. + </code> + <p>And this is a shell session when running it:</p> + <pre> +1> pushbutton:start(). +{ok,<0.36.0>} +2> pushbutton:get_count(). +0 +3> pushbutton:push(). +on +4> pushbutton:get_count(). +1 +5> pushbutton:push(). +off +6> pushbutton:get_count(). +1 +7> pushbutton:stop(). +ok +8> pushbutton:push(). +** exception exit: {noproc,{gen_statem,call,[pushbutton_statem,push,infinity]}} + in function gen:do_for_proc/2 (gen.erl, line 261) + in call from gen_statem:call/3 (gen_statem.erl, line 386) + </pre> + + <p> + And just to compare styles here is the same example using + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + <c>state_functions</c>, or rather here is code to replace + from the <c>init/1</c> function 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}. + + +%%% Event handling + +handle_event({call,From}, push, off, Data) -> + %% Go to 'on', increment count and reply + %% that the resulting status is 'on' + {next_state,on,Data+1,[{reply,From,on}]}; +handle_event({call,From}, push, on, Data) -> + %% Go to 'off' and reply that the resulting status is 'off' + {next_state,off,Data,[{reply,From,off}]}; +%% +%% Event handling common to all states +handle_event({call,From}, get_count, State, Data) -> + %% Reply with the current count + {next_state,State,Data,[{reply,From,Data}]}; +handle_event(_, _, State, Data) -> + %% Ignore all other events + {next_state,State,Data}. + </code> + </section> + + <datatypes> + <datatype> + <name name="server_name" /> + <desc> + <p> + Name specification to use when starting + a <c>gen_statem</c> server. See + <seealso marker="#start_link/3"> + <c>start_link/3</c> + </seealso> + and + <seealso marker="#type-server_ref"> + <c>server_ref()</c> + </seealso> below. + </p> + </desc> + </datatype> + <datatype> + <name name="server_ref" /> + <desc> + <p> + Server specification to use when addressing + a <c>gen_statem</c> server. + See <seealso marker="#call/2"><c>call/2</c></seealso> and + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + above. + </p> + <p>It can be:</p> + <taglist> + <tag><c>pid()</c><br /> + <c>LocalName</c></tag> + <item>The <c>gen_statem</c> is locally registered.</item> + <tag><c>Name, Node</c></tag> + <item> + The <c>gen_statem</c> is locally registered + on another node. + </item> + <tag><c>GlobalName</c></tag> + <item> + The <c>gen_statem</c> is globally registered + in <seealso marker="kernel:global"><c>global</c></seealso>. + </item> + <tag><c>RegMod, ViaName</c></tag> + <item> + The <c>gen_statem</c> is registered through + an alternative process registry. + The registry callback module <c>RegMod</c> + should export the functions + <c>register_name/2</c>, <c>unregister_name/1</c>, + <c>whereis_name/1</c> and <c>send/2</c>, + which should behave like the corresponding functions + in <seealso marker="kernel:global"><c>global</c></seealso>. + Thus, <c>{via,global,GlobalName}</c> is the same as + <c>{global,GlobalName}</c>. + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="debug_opt" /> + <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>. + </p> + <p> + For every entry in <c><anno>Dbgs</anno></c> + the corresponding function in + <seealso marker="sys"><c>sys</c></seealso> will be called. + </p> + </desc> + </datatype> + <datatype> + <name name="start_opt" /> + <desc> + <p> + Options that can be used when starting + a <c>gen_statem</c> server through for example + <seealso marker="#start_link/3"><c>start_link/3</c></seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="start_ret" /> + <desc> + <p> + Return value from the start functions for_example + <seealso marker="#start_link/3"><c>start_link/3</c></seealso>. + </p> + </desc> + </datatype> + + <datatype> + <name name="from" /> + <desc> + <p> + Destination to use when replying through for example the + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>{reply,From,Reply}</c> + to a process that has called the <c>gen_statem</c> server using + <seealso marker="#call/2"><c>call/2</c></seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="state" /> + <desc> + <p> + After a state change (<c>NextState =/= State</c>) + all postponed events are retried. + </p> + </desc> + </datatype> + <datatype> + <name name="state_name" /> + <desc> + <p> + If the + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c>, + the state has to be of this type. + </p> + </desc> + </datatype> + <datatype> + <name name="data" /> + <desc> + <p> + A term in which the state machine implementation + should store any server data it needs. The difference between + this and the <seealso marker="#type-state"><c>state()</c></seealso> + itself is that a change in this data does not cause + postponed events to be retried. Hence if a change + in this data would change the set of events that + are handled than that data item should be made + a part of the state. + </p> + </desc> + </datatype> + <datatype> + <name name="event_type" /> + <desc> + <p> + External events are of 3 different type: + <c>{call,<anno>From</anno>}</c>, <c>cast</c> or <c>info</c>. + <seealso marker="#call/2">Calls</seealso> + (synchronous) and + <seealso marker="#cast/2">casts</seealso> + originate from the corresponding API functions. + For calls the event contain whom to reply to. + Type <c>info</c> originates from regular process messages sent + to the <c>gen_statem</c>. It is also possible for the state machine + implementation to generate events of types + <c>timeout</c> and <c>internal</c> to itself. + </p> + </desc> + </datatype> + <datatype> + <name name="callback_mode" /> + <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-7</c></seealso>, + and with the return value from + <seealso marker="#Module:code_change/4"> + <c>Module:code_change/4</c>. + </seealso> + </p> + <taglist> + <tag><c>state_functions</c></tag> + <item> + The state has to be of type + <seealso marker="#type-state_name"><c>state_name()</c></seealso> + and one callback function per state that is + <seealso marker="#Module:StateName/3"> + <c>Module:StateName/3</c> + </seealso> + is used. + </item> + <tag><c>handle_event_function</c></tag> + <item> + The state can be any term and the callback function + <seealso marker="#Module:handle_event/4"> + <c>Module:handle_event/4</c> + </seealso> + is used for all states. + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="transition_option" /> + <desc> + <p> + Transition options may be set by + <seealso marker="#type-action">actions</seealso> + and they modify some details below in how + the state transition is done: + </p> + <list type="ordered"> + <item> + All + <seealso marker="#type-action">actions</seealso> + are processed in order of appearance. + </item> + <item> + If + <seealso marker="#type-postpone"> + <c>postpone()</c> + </seealso> + is <c>true</c> + the current event is postponed. + </item> + <item> + If the state changes the queue of incoming events + is reset to start with the oldest postponed. + </item> + <item> + All events stored with + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>next_event</c> + are inserted in the queue to be processed before + all other events. + </item> + <item> + If an + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + is set through + <seealso marker="#type-action"> + <c>action()</c> + </seealso> + <c>timeout</c> + an event timer may be started or a timeout zero event + may be enqueued. + </item> + <item> + The (possibly new) + <seealso marker="#state_function">state function</seealso> + is called with the oldest enqueued event if there is any, + otherwise the <c>gen_statem</c> goes into <c>receive</c> + or hibernation + (if + <seealso marker="#type-hibernate"> + <c>hibernate()</c> + </seealso> + is <c>true</c>) + to wait for the next message. In hibernation the next + non-system event awakens the <c>gen_statem</c>, or rather + the next incoming message awakens the <c>gen_statem</c> + but if it is a system event + it goes right back into hibernation. + </item> + </list> + </desc> + </datatype> + <datatype> + <name name="postpone" /> + <desc> + <p> + If <c>true</c> postpone the current event and retry + it when the state changes + (<c>NextState =/= State</c>). + </p> + </desc> + </datatype> + <datatype> + <name name="hibernate" /> + <desc> + <p> + If <c>true</c> hibernate the <c>gen_statem</c> + by calling + <seealso marker="proc_lib#hibernate/3"> + <c>proc_lib:hibernate/3</c> + </seealso> + before going into <c>receive</c> + to wait for a new external event. + If there are enqueued events, + to prevent receiving any new event; a + <seealso marker="erts:erlang#garbage_collect/0"> + <c>garbage_collect/0</c> + </seealso> is done instead to simulate + that the <c>gen_statem</c> entered hibernation + and immediately got awakened by the oldest enqueued event. + </p> + </desc> + </datatype> + <datatype> + <name name="event_timeout" /> + <desc> + <p> + Generate an event of + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>timeout</c> + after this time (in milliseconds) unless some other + event arrives in which case this timeout is cancelled. + Note that a retried or inserted event + counts just like a new in this respect. + </p> + <p> + If the value is <c>infinity</c> no timer is started since + it will never trigger anyway. + </p> + <p> + If the value is <c>0</c> the timeout event is immediately enqueued + unless there already are enqueued events since then the + timeout is immediately cancelled. + This is a feature ensuring that a timeout <c>0</c> event + will be processed before any not yet received external event. + </p> + <p> + Note that it is not possible nor needed to cancel this timeout + since it is cancelled automatically by any other event. + </p> + </desc> + </datatype> + <datatype> + <name name="action" /> + <desc> + <p> + These state transition actions may be invoked by + returning them from the + <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>. + </p> + <p> + Actions are executed in the containing list order. + </p> + <p> + Actions that set + <seealso marker="#type-transition_option"> + transition options + </seealso> + overrides any previous of the same type, + so the last in the containing list wins. + For example the last + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + overrides any other <c>event_timeout()</c> in the list. + </p> + <taglist> + <tag><c>postpone</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-postpone"> + <c>postpone()</c> + </seealso> + for this state transition. + This action is ignored when returned from + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + or given to + <seealso marker="#enter_loop/5"><c>enter_loop/5,6</c></seealso> + since there is no event to postpone in those cases. + </item> + <tag><c>hibernate</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-hibernate"> + <c>hibernate()</c> + </seealso> + for this state transition. + </item> + <tag><c>Timeout</c></tag> + <item> + Short for <c>{timeout,Timeout,Timeout}</c> that is + the timeout message is the timeout time. + This form exists to make the + <seealso marker="#state_function">state function</seealso> + return value <c>{next_state,NextState,NewData,Timeout}</c> + allowed like for + <seealso marker="gen_fsm#Module:StateName/2"> + <c>gen_fsm Module:StateName/2</c>. + </seealso> + </item> + <tag><c>timeout</c></tag> + <item> + Set the + <seealso marker="#type-transition_option"> + <c>transition_option()</c> + </seealso> + <seealso marker="#type-event_timeout"> + <c>event_timeout()</c> + </seealso> + to <c><anno>Time</anno></c> with <c><anno>EventContent</anno></c>. + </item> + <tag><c>reply_action()</c></tag> + <item>Reply to a caller.</item> + <tag><c>next_event</c></tag> + <item> + Store the given <c><anno>EventType</anno></c> + and <c><anno>EventContent</anno></c> for insertion after all + actions have been executed. + </item> + <item> + <p> + The stored events are inserted in the queue as the next to process + before any already queued events. The order of these stored events + is preserved so the first <c>next_event</c> in the containing list + will become the first to process. + </p> + </item> + <item> + <p> + An event of type + <seealso marker="#type-event_type"> + <c>internal</c> + </seealso> + should be used when you want to reliably distinguish + an event inserted this way from any external event. + </p> + </item> + </taglist> + </desc> + </datatype> + <datatype> + <name name="reply_action" /> + <desc> + <p> + Reply to a caller waiting for a reply in + <seealso marker="#call/2"><c>call/2</c></seealso>. + <c><anno>From</anno></c> must be the term from the + <seealso marker="#type-event_type"> + <c>{call,<anno>From</anno>}</c> + </seealso> + argument to the + <seealso marker="#state_function">state function</seealso>. + </p> + </desc> + </datatype> + <datatype> + <name name="state_function_result" /> + <desc> + <taglist> + <tag><c>next_state</c></tag> + <item> + The <c>gen_statem</c> will do a state transition to + <c><anno>NextStateName</anno></c> + (which may be the same as the current state), + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c> + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + <datatype> + <name name="handle_event_result" /> + <desc> + <taglist> + <tag><c>next_state</c></tag> + <item> + The <c>gen_statem</c> will do a state transition to + <c><anno>NextState</anno></c> + (which may be the same as the current state), + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c> + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + <datatype> + <name name="common_state_callback_result" /> + <desc> + <taglist> + <tag><c>stop</c></tag> + <item> + Terminate the <c>gen_statem</c> by calling + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + with <c>Reason</c> and + <c><anno>NewData</anno></c>, if given. + </item> + <tag><c>stop_and_reply</c></tag> + <item> + Send all <c><anno>Replies</anno></c> + then terminate the <c>gen_statem</c> by calling + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + with <c>Reason</c> and + <c><anno>NewData</anno></c>, if given. + </item> + <tag><c>keep_state</c></tag> + <item> + The <c>gen_statem</c> will keep the current state, or + do a state transition to the current state if you like, + set <c><anno>NewData</anno></c> + and execute all <c><anno>Actions</anno></c>. + This is the same as + <c>{next_state,CurrentState,<anno>NewData</anno>,<anno>Actions</anno>}</c>. + </item> + <tag><c>keep_state_and_data</c></tag> + <item> + The <c>gen_statem</c> will keep the current state or + do a state transition to the current state if you like, + keep the current server data, + and execute all <c><anno>Actions</anno></c>. + This is the same as + <c>{next_state,CurrentState,CurrentData,<anno>Actions</anno>}</c>. + </item> + </taglist> + <p> + All these terms are tuples or atoms and this property + will hold in any future version of <c>gen_statem</c>, + just in case you need such a promise. + </p> + </desc> + </datatype> + </datatypes> + + <funcs> + + <func> + <name name="start_link" arity="3" /> + <name name="start_link" arity="4" /> + <fsummary>Create a linked <c>gen_statem</c> process</fsummary> + <desc> + <p> + Creates a <c>gen_statem</c> process according + to OTP design principles + (using + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + primitives) + that is linked to the calling process. + This is essential when the <c>gen_statem</c> shall be part of + a supervision tree so it gets linked to its supervisor. + </p> + <p> + The <c>gen_statem</c> process calls + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + to initialize the server. To ensure a synchronized start-up + procedure, <c>start_link/3,4</c> does not return until + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + has returned. + </p> + <p> + <c><anno>ServerName</anno></c> specifies the + <seealso marker="#type-server_name"> + <c>server_name()</c> + </seealso> + to register for the <c>gen_statem</c>. + If the <c>gen_statem</c> is started with <c>start_link/3</c> + no <c><anno>ServerName</anno></c> is provided and + the <c>gen_statem</c> is not registered. + </p> + <p><c><anno>Module</anno></c> is the name of the callback module.</p> + <p> + <c><anno>Args</anno></c> is an arbitrary term which is passed as + the argument to + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso>. + </p> + <p> + If the option <c>{timeout,Time}</c> is present in + <c><anno>Opts</anno></c>, the <c>gen_statem</c> + is allowed to spend <c>Time</c> milliseconds initializing + or it will be terminated and the start function will return + <seealso marker="#type-start_ret"><c>{error,timeout}</c></seealso>. + </p> + <p> + If the option + <seealso marker="#type-debug_opt"><c>{debug,Dbgs}</c></seealso> + is present in <c><anno>Opts</anno></c>, debugging through + <seealso marker="sys"><c>sys</c></seealso> is activated. + </p> + <p> + If the option <c>{spawn_opt,SpawnOpts}</c> is present in + <c><anno>Opts</anno></c>, <c>SpawnOpts</c> will be passed + as option list to + <seealso marker="erts:erlang#spawn_opt/2"><c>spawn_opt/2</c></seealso> + which is used to spawn the <c>gen_statem</c> process. + </p> + <note> + <p> + Using the spawn option <c>monitor</c> is currently not + allowed, but will cause this function to fail with reason + <c>badarg</c>. + </p> + </note> + <p> + If the <c>gen_statem</c> is successfully created + and initialized this function returns + <seealso marker="#type-start_ret"> + <c>{ok,Pid}</c>, + </seealso> + where <c>Pid</c> is the <c>pid()</c> + of the <c>gen_statem</c>. + If there already exists a process with the specified + <c><anno>ServerName</anno></c> this function returns + <seealso marker="#type-start_ret"><c>{error,{already_started,Pid}}</c></seealso>, + where <c>Pid</c> is the <c>pid()</c> of that process. + </p> + <p> + If <c>Module:init/1</c> fails with <c>Reason</c>, + this function returns + <seealso marker="#type-start_ret"><c>{error,Reason}</c></seealso>. + If <c>Module:init/1</c> returns + <seealso marker="#type-start_ret"> + <c>{stop,Reason}</c> + </seealso> + or + <seealso marker="#type-start_ret"><c>ignore</c></seealso>, + the process is terminated and this function + returns + <seealso marker="#type-start_ret"> + <c>{error,Reason}</c> + </seealso> + or + <seealso marker="#type-start_ret"><c>ignore</c></seealso>, + respectively. + </p> + </desc> + </func> + + + <func> + <name name="start" arity="3" /> + <name name="start" arity="4" /> + <fsummary>Create a stand-alone <c>gen_statem</c> process</fsummary> + <desc> + <p> + Creates a stand-alone <c>gen_statem</c> process according to + OTP design principles (using + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + primitives). + Since it does not get linked to the calling process + this start function can not be used by a supervisor + to start a child. + </p> + <p> + See <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso> + for a description of arguments and return values. + </p> + </desc> + </func> + + <func> + <name name="stop" arity="1" /> + <fsummary>Synchronously stop a generic server</fsummary> + <desc> + <p> + The same as + <seealso marker="#stop/3"> + <c>stop(<anno>ServerRef</anno>, normal, infinity)</c>. + </seealso> + </p> + </desc> + </func> + <func> + <name name="stop" arity="3" /> + <fsummary>Synchronously stop a generic server</fsummary> + <desc> + <p> + Orders the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + to exit with the given <c><anno>Reason</anno></c> + and waits for it to terminate. + The <c>gen_statem</c> will call + <seealso marker="#Module:terminate/3"> + <c>Module:terminate/3</c> + </seealso> + before exiting. + </p> + <p> + This function returns <c>ok</c> if the server terminates + with the expected reason. Any other reason than <c>normal</c>, + <c>shutdown</c>, or <c>{shutdown,Term}</c> will cause an + error report to be issued through + <seealso marker="kernel:error_logger#format/2"> + <c>error_logger:format/2</c>. + </seealso> + The default <c><anno>Reason</anno></c> is <c>normal</c>. + </p> + <p> + <c><anno>Timeout</anno></c> is an integer greater than zero + which specifies how many milliseconds to wait for the server to + terminate, or the atom <c>infinity</c> to wait indefinitely. + The default value is <c>infinity</c>. + If the server has not terminated within the specified time, + a <c>timeout</c> exception is raised. + </p> + <p> + If the process does not exist, a <c>noproc</c> exception + is raised. + </p> + </desc> + </func> + + <func> + <name name="call" arity="2" /> + <name name="call" arity="3" /> + <fsummary>Make a synchronous call to a <c>gen_statem</c></fsummary> + <desc> + <p> + Makes a synchronous call to the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + by sending a request + and waiting until its reply arrives. + The <c>gen_statem</c> will call the + <seealso marker="#state_function">state function</seealso> with + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>{call,From}</c> and event content + <c><anno>Request</anno></c>. + </p> + <p> + A <c><anno>Reply</anno></c> is generated when a + <seealso marker="#state_function">state function</seealso> + returns with + <c>{reply,From,<anno>Reply</anno>}</c> as one + <seealso marker="#type-action"><c>action()</c></seealso>, + and that <c><anno>Reply</anno></c> becomes the return value + of this function. + </p> + <p> + <c><anno>Timeout</anno></c> is an integer greater than zero + which specifies how many milliseconds to wait for a reply, + or the atom <c>infinity</c> to wait indefinitely, + which is the default. If no reply is received within + the specified time, the function call fails. + </p> + <note> + <p> + 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 + less efficient than using + <c><anno>Timeout</anno> =:= infinity</c>. + </p> + </note> + <p> + The call may fail for example if the <c>gen_statem</c> dies + before or during this function call. + </p> + </desc> + </func> + + <func> + <name name="cast" arity="2" /> + <fsummary>Send an asynchronous event to a <c>gen_statem</c></fsummary> + <desc> + <p> + Sends an asynchronous event to the <c>gen_statem</c> + <seealso marker="#type-server_ref"> + <c><anno>ServerRef</anno></c> + </seealso> + and returns <c>ok</c> immediately, + ignoring if the destination node or <c>gen_statem</c> + does not exist. + The <c>gen_statem</c> will call the + <seealso marker="#state_function">state function</seealso> with + <seealso marker="#type-event_type"><c>event_type()</c></seealso> + <c>cast</c> and event content + <c><anno>Msg</anno></c>. + </p> + </desc> + </func> + + <func> + <name name="reply" arity="1" /> + <name name="reply" arity="2" /> + <fsummary>Reply to a caller</fsummary> + <desc> + <p> + This function can be used by a <c>gen_statem</c> + to explicitly send a reply to a process that waits in + <seealso marker="#call/2"><c>call/2</c></seealso> + when the reply cannot be defined in + the return value of a + <seealso marker="#state_function">state function</seealso>. + </p> + <p> + <c><anno>From</anno></c> must be the term from the + <seealso marker="#type-event_type"> + <c>{call,<anno>From</anno>}</c> + </seealso> + argument to the + <seealso marker="#state_function">state function</seealso>. + <c><anno>From</anno></c> and <c><anno>Reply</anno></c> + can also be specified using a + <seealso marker="#type-reply_action"> + <c>reply_action()</c> + </seealso> + and multiple replies with a list of them. + </p> + <note> + <p> + A reply sent with this function will not be visible + in <seealso marker="sys"><c>sys</c></seealso> debug output. + </p> + </note> + </desc> + </func> + + <func> + <name name="enter_loop" arity="5" /> + <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="#type-server_name"> + <c>server_name()</c> + </seealso> + must have been registered. + </p> + </desc> + </func> + <func> + <name name="enter_loop" arity="6" /> + <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> + 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>. + </p> + <p> + Otherwise the same as + <seealso marker="#enter_loop/7"><c>enter_loop/7</c></seealso> + with + <c>Server = <anno>Server_or_Actions</anno></c> and + <c>Actions = []</c>. + </p> + </desc> + </func> + <func> + <name name="enter_loop" arity="7" /> + <fsummary>Enter the <c>gen_statem</c> receive loop</fsummary> + <desc> + <p> + Makes an the calling process become a <c>gen_statem</c>. + Does not return, instead the calling process will enter + the <c>gen_statem</c> receive loop and become + a <c>gen_statem</c> server. + The process <em>must</em> have been started + using one of the start functions in + <seealso marker="proc_lib"><c>proc_lib</c></seealso>. + The user is responsible for any initialization of the process, + including registering a name for it. + </p> + <p> + This function is useful when a more complex initialization + procedure is needed than + the <c>gen_statem</c> behaviour 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 + <seealso marker="#start_link/3"> + <c>gen_statem:start[_link]/3,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> + <p> + <c><anno>CallbackMode</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. + </p> + <p> + Failure: If the calling process was not started by a + <seealso marker="proc_lib"><c>proc_lib</c></seealso> + start function, or if it is not registered + according to + <seealso marker="#type-server_name"><c>server_name()</c></seealso>. + </p> + </desc> + </func> + + </funcs> + + + + <section> + <title>CALLBACK FUNCTIONS</title> + <p> + The following functions should be exported from a + <c>gen_statem</c> callback module. + </p> + </section> + <funcs> + + <func> + <name>Module:init(Args) -> Result</name> + <fsummary>Initialize process and internal state</fsummary> + <type> + <v>Args = term()</v> + <v>Result = {CallbackMode,State,Data}</v> + <v> | {CallbackMode,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> + </v> + <v> + Actions = + [<seealso marker="#type-action">action()</seealso>] | + <seealso marker="#type-action">action()</seealso> + </v> + <v>Reason = term()</v> + </type> + <desc> + <marker id="Module:init-1" /> + <p> + Whenever a <c>gen_statem</c> is started using + <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 + the implementation state and server data. + </p> + <p> + <c>Args</c> is the <c>Args</c> argument provided to the start + function. + </p> + <p> + If the initialization is successful, the function should + 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>. + <c>State</c> is the initial + <seealso marker="#type-state"><c>state()</c></seealso> + and <c>Data</c> the initial server + <seealso marker="#type-data"><c>data()</c></seealso>. + </p> + <p> + The <seealso marker="#type-action"><c>Actions</c></seealso> + are executed when entering the first + <seealso marker="#type-state">state</seealso> just as for a + <seealso marker="#state_function">state function</seealso>. + </p> + <p> + If something goes wrong during the initialization + the function should return <c>{stop,Reason}</c> + or <c>ignore</c>. See + <seealso marker="#start_link/3"><c>start_link/3,4</c></seealso>. + </p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Result</c>. + </p> + </desc> + </func> + + <func> + <name>Module:StateName(EventType, EventContent, Data) -> + StateFunctionResult + </name> + <name>Module:handle_event(EventType, EventContent, + State, Data) -> HandleEventResult + </name> + <fsummary>Handle an event</fsummary> + <type> + <v> + EventType = + <seealso marker="#type-event_type">event_type()</seealso> + </v> + <v>EventContent = term()</v> + <v> + State = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + Data = NewData = + <seealso marker="#type-data">data()</seealso> + </v> + <v> + StateFunctionResult = + <seealso marker="#type-state_function_result"> + state_function_result() + </seealso> + </v> + <v> + HandleEventResult = + <seealso marker="#type-handle_event_result"> + handle_event_result() + </seealso> + </v> + </type> + <desc> + <p> + Whenever a <c>gen_statem</c> receives an event from + <seealso marker="#call/2"><c>call/2</c></seealso>, + <seealso marker="#cast/2"><c>cast/2</c></seealso> or + as a normal process message one of these functions is called. If + <seealso marker="#type-callback_mode"><em>callback mode</em></seealso> + is <c>state_functions</c> then <c>Module:StateName/3</c> is called, + and if it is <c>handle_event_function</c> + then <c>Module:handle_event/4</c> is called. + </p> + <p> + If <c>EventType</c> is + <seealso marker="#type-event_type"><c>{call,From}</c></seealso> + the caller is waiting for a reply. The reply can be sent + from this or from any other + <seealso marker="#state_function">state function</seealso> + by returning with <c>{reply,From,Reply}</c> in + <seealso marker="#type-action"><c>Actions</c></seealso>, in + <seealso marker="#type-reply_action"><c>Replies</c></seealso> + or by calling + <seealso marker="#reply/2"><c>reply(From, Reply)</c></seealso>. + </p> + <p> + If this function returns with a next state that + does not match equal (<c>=/=</c>) to the current state + all postponed events will be retried in the next state. + </p> + <p> + The only difference between <c>StateFunctionResult</c> and + <c>HandleEventResult</c> is that for <c>StateFunctionResult</c> + the next state has to be an atom but for <c>HandleEventResult</c> + there is no restriction on the next state. + </p> + <p> + See <seealso marker="#type-action"><c>action()</c></seealso> + for options that can be set and actions that can be done + by <c>gen_statem</c> after returning from this function. + </p> + <p> + These functions may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso>, + to return the result. + </p> + </desc> + </func> + + <func> + <name>Module:terminate(Reason, State, Data) -> Ignored</name> + <fsummary>Clean up before termination</fsummary> + <type> + <v>Reason = normal | shutdown | {shutdown,term()} | term()</v> + <v>State = <seealso marker="#type-state">state()</seealso></v> + <v>Data = <seealso marker="#type-data">data()</seealso></v> + <v>Ignored = term()</v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> + when it is about to terminate. It should be the opposite of + <seealso marker="#Module:init/1"><c>Module:init/1</c></seealso> + and do any necessary cleaning up. When it returns, + the <c>gen_statem</c> terminates with <c>Reason</c>. The return + value is ignored.</p> + <p> + <c>Reason</c> is a term denoting the stop reason and + <seealso marker="#type-state"><c>State</c></seealso> + is the internal state of the <c>gen_statem</c>. + </p> + <p> + <c>Reason</c> depends on why the <c>gen_statem</c> + is terminating. + If it is because another callback function has returned a + stop tuple <c>{stop,Reason}</c> in + <seealso marker="#type-action"><c>Actions</c></seealso>, + <c>Reason</c> will have the value specified in that tuple. + If it is due to a failure, <c>Reason</c> is the error reason. + </p> + <p> + If the <c>gen_statem</c> is part of a supervision tree and is + ordered by its supervisor to terminate, this function will be + called with <c>Reason = shutdown</c> if the following + conditions apply:</p> + <list type="bulleted"> + <item> + the <c>gen_statem</c> has been set + to trap exit signals, and + </item> + <item> + the shutdown strategy as defined in the supervisor's + child specification is an integer timeout value, not + <c>brutal_kill</c>. + </item> + </list> + <p> + Even if the <c>gen_statem</c> is <em>not</em> + part of a supervision tree, this function will be called + if it receives an <c>'EXIT'</c> message from its parent. + <c>Reason</c> will be the same as + in the <c>'EXIT'</c> message. + </p> + <p> + Otherwise, the <c>gen_statem</c> will be immediately terminated. + </p> + <p> + Note that for any other reason than <c>normal</c>, + <c>shutdown</c>, or <c>{shutdown,Term}</c> + the <c>gen_statem</c> is assumed to terminate due to an error + 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 may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Ignored</c>, which is ignored anyway. + </p> + </desc> + </func> + + <func> + <name>Module:code_change(OldVsn, OldState, OldData, Extra) -> + Result + </name> + <fsummary>Update the internal state during upgrade/downgrade</fsummary> + <type> + <v>OldVsn = Vsn | {down,Vsn}</v> + <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> + OldState = NewState = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + OldData = NewData = + <seealso marker="#type-data">data()</seealso> + </v> + <v>Reason = term()</v> + </type> + <desc> + <p> + This function is called by a <c>gen_statem</c> when it should + update its internal state during a release upgrade/downgrade, + that is when the instruction <c>{update,Module,Change,...}</c> + where <c>Change={advanced,Extra}</c> is given in the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file. See + <seealso marker="doc/design_principles:release_handling#instr"> + OTP Design Principles + </seealso> + for more information. + </p> + <p> + In the case of an upgrade, <c>OldVsn</c> is <c>Vsn</c>, and + in the case of a downgrade, <c>OldVsn</c> is + <c>{down,Vsn}</c>. <c>Vsn</c> is defined by the <c>vsn</c> + attribute(s) of the old version of the callback module + <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 + since the new code surely knows what <em>callback mode</em> + it needs, but for a downgrade this function will have to + know from the <c>Extra</c> argument that comes from the + <seealso marker="sasl:appup"><c>appup</c></seealso> + file what <em>callback mode</em> the old code did use. + It may also be possible to figure this out + from the <c>{down,Vsn}</c> argument since <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>. + </p> + <p> + <c>Extra</c> is passed as-is from the <c>{advanced,Extra}</c> + part of the update instruction. + </p> + <p> + If successful, the function shall return the updated + internal state in an + <c>{NewCallbackMode,NewState,NewData}</c> tuple. + </p> + <p> + If the function returns <c>Reason</c>, the ongoing + upgrade will fail and roll back to the old release.</p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Result</c> or <c>Reason</c>. + </p> + </desc> + </func> + + <func> + <name>Module:format_status(Opt, [PDict,State,Data]) -> + Status + </name> + <fsummary>Optional function for providing a term describing the + current <c>gen_statem</c> status</fsummary> + <type> + <v>Opt = normal | terminate</v> + <v>PDict = [{Key, Value}]</v> + <v> + State = + <seealso marker="#type-state">state()</seealso> + </v> + <v> + Data = + <seealso marker="#type-data">data()</seealso> + </v> + <v>Key = term()</v> + <v>Value = term()</v> + <v>Status = term()</v> + </type> + <desc> + <note> + <p> + This callback is optional, so a callback module need not + 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 will return <c>{State,Info}</c> + where <c>Info</c> informs of the crash but no details, + to hide possibly sensitive data. + </p> + </note> + <p>This function is called by a <c>gen_statem</c> process when:</p> + <list type="bulleted"> + <item> + One of + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + is invoked to get the <c>gen_statem</c> status. <c>Opt</c> is set + to the atom <c>normal</c> for this case. + </item> + <item> + The <c>gen_statem</c> terminates abnormally and logs an error. + <c>Opt</c> is set to the atom <c>terminate</c> for this case. + </item> + </list> + <p> + This function is useful for customising the form and + appearance of the <c>gen_statem</c> status for these cases. A + callback module wishing to customise the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + return value as well as how + its status appears in termination error logs exports an + instance of <c>format_status/2</c> that returns a term + describing the current status of the <c>gen_statem</c>. + </p> + <p> + <c>PDict</c> is the current value of the <c>gen_statem</c>'s + process dictionary. + </p> + <p> + <seealso marker="#type-state"><c>State</c></seealso> + is the internal state of the <c>gen_statem</c>. + </p> + <p> + <seealso marker="#type-data"><c>Data</c></seealso> + is the internal server data of the <c>gen_statem</c>. + </p> + <p> + The function should return <c>Status</c>, a term that + customises the details of the current state and status of + the <c>gen_statem</c>. There are no restrictions on the + form <c>Status</c> can take, but for the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + case (when <c>Opt</c> + is <c>normal</c>), the recommended form for + the <c>Status</c> value is <c>[{data, [{"State", + Term}]}]</c> where <c>Term</c> provides relevant details of + the <c>gen_statem</c> state. Following this recommendation isn't + required, but doing so will make the callback module status + consistent with the rest of the + <seealso marker="sys#get_status/1"> + <c>sys:get_status/1,2</c> + </seealso> + return value. + </p> + <p> + One use for this function is to return compact alternative + state representations to avoid having large state terms + printed in logfiles. Another is to hide sensitive data from + being written to the error log. + </p> + <p> + This function may use + <seealso marker="erts:erlang#throw/1"><c>throw/1</c></seealso> + to return <c>Status</c>. + </p> + </desc> + </func> + + </funcs> + + <section> + <title>SEE ALSO</title> + <p><seealso marker="gen_event"><c>gen_event(3)</c></seealso>, + <seealso marker="gen_fsm"><c>gen_fsm(3)</c></seealso>, + <seealso marker="gen_server"><c>gen_server(3)</c></seealso>, + <seealso marker="supervisor"><c>supervisor(3)</c></seealso>, + <seealso marker="proc_lib"><c>proc_lib(3)</c></seealso>, + <seealso marker="sys"><c>sys(3)</c></seealso></p> + </section> +</erlref> diff --git a/lib/stdlib/doc/src/proc_lib.xml b/lib/stdlib/doc/src/proc_lib.xml index bef037076b..245580b1ba 100644 --- a/lib/stdlib/doc/src/proc_lib.xml +++ b/lib/stdlib/doc/src/proc_lib.xml @@ -34,9 +34,9 @@ <p>This module is used to start processes adhering to the <seealso marker="doc/design_principles:des_princ">OTP Design Principles</seealso>. Specifically, the functions in this module are used by the OTP standard behaviors (<c>gen_server</c>, - <c>gen_fsm</c>, ...) when starting new processes. The functions - can also be used to start <em>special processes</em>, user - defined processes which comply to the OTP design principles. See + <c>gen_fsm</c>, <c>gen_statem</c>, ...) when starting new processes. + The functions can also be used to start <em>special processes</em>, + user defined processes which comply to the OTP design principles. See <seealso marker="doc/design_principles:spec_proc">Sys and Proc_Lib</seealso> in OTP Design Principles for an example.</p> <p>Some useful information is initialized when a process starts. The registered names, or the process identifiers, of the parent diff --git a/lib/stdlib/doc/src/ref_man.xml b/lib/stdlib/doc/src/ref_man.xml index 82ad78e675..404873ea32 100644 --- a/lib/stdlib/doc/src/ref_man.xml +++ b/lib/stdlib/doc/src/ref_man.xml @@ -4,7 +4,7 @@ <application xmlns:xi="http://www.w3.org/2001/XInclude"> <header> <copyright> - <year>1996</year><year>2015</year> + <year>1996</year><year>2016</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -66,6 +66,7 @@ <xi:include href="gen_event.xml"/> <xi:include href="gen_fsm.xml"/> <xi:include href="gen_server.xml"/> + <xi:include href="gen_statem.xml"/> <xi:include href="io.xml"/> <xi:include href="io_lib.xml"/> <xi:include href="lib.xml"/> diff --git a/lib/stdlib/doc/src/specs.xml b/lib/stdlib/doc/src/specs.xml index 0418bf7b22..45b207b13d 100644 --- a/lib/stdlib/doc/src/specs.xml +++ b/lib/stdlib/doc/src/specs.xml @@ -30,6 +30,7 @@ <xi:include href="../specs/specs_gen_event.xml"/> <xi:include href="../specs/specs_gen_fsm.xml"/> <xi:include href="../specs/specs_gen_server.xml"/> + <xi:include href="../specs/specs_gen_statem.xml"/> <xi:include href="../specs/specs_io.xml"/> <xi:include href="../specs/specs_io_lib.xml"/> <xi:include href="../specs/specs_lib.xml"/> diff --git a/lib/stdlib/doc/src/supervisor.xml b/lib/stdlib/doc/src/supervisor.xml index 421306283c..29e5a732d5 100644 --- a/lib/stdlib/doc/src/supervisor.xml +++ b/lib/stdlib/doc/src/supervisor.xml @@ -34,8 +34,8 @@ <p>A behaviour module for implementing a supervisor, a process which supervises other processes called child processes. A child process can either be another supervisor or a worker process. - Worker processes are normally implemented using one of - the <c>gen_event</c>, <c>gen_fsm</c>, or <c>gen_server</c> + Worker processes are normally implemented using one of the + <c>gen_event</c>, <c>gen_fsm</c>, <c>gen_statem</c> or <c>gen_server</c> behaviours. A supervisor implemented using this module will have a standard set of interface functions and include functionality for tracing and error reporting. Supervisors are used to build a @@ -221,7 +221,8 @@ <p><c>modules</c> is used by the release handler during code replacement to determine which processes are using a certain module. As a rule of thumb, if the child process is a - <c>supervisor</c>, <c>gen_server</c>, or <c>gen_fsm</c>, + <c>supervisor</c>, <c>gen_server</c>, + <c>gen_fsm</c> or <c>gen_statem</c> this should be a list with one element <c>[Module]</c>, where <c>Module</c> is the callback module. If the child process is an event manager (<c>gen_event</c>) with a @@ -636,6 +637,7 @@ <title>SEE ALSO</title> <p><seealso marker="gen_event">gen_event(3)</seealso>, <seealso marker="gen_fsm">gen_fsm(3)</seealso>, + <seealso marker="gen_statem">gen_statem(3)</seealso>, <seealso marker="gen_server">gen_server(3)</seealso>, <seealso marker="sys">sys(3)</seealso></p> </section> diff --git a/lib/stdlib/doc/src/sys.xml b/lib/stdlib/doc/src/sys.xml index d80ec4dfa6..2255395f46 100644 --- a/lib/stdlib/doc/src/sys.xml +++ b/lib/stdlib/doc/src/sys.xml @@ -217,14 +217,18 @@ processes. For example, a <c>gen_server</c> process returns the callback module's state, a <c>gen_fsm</c> process returns information such as its current state name and state data, - and a <c>gen_event</c> process returns information about each of its + a <c>gen_statem</c> process returns information about + its current state and data, and a <c>gen_event</c> process + returns information about each of its registered handlers. Callback modules for <c>gen_server</c>, - <c>gen_fsm</c>, and <c>gen_event</c> can also customise the value + <c>gen_fsm</c>, <c>gen_statem</c> and <c>gen_event</c> + can also customise the value of <c><anno>Misc</anno></c> by exporting a <c>format_status/2</c> function that contributes module-specific information; - see <seealso marker="gen_server#Module:format_status/2">gen_server:format_status/2</seealso>, - <seealso marker="gen_fsm#Module:format_status/2">gen_fsm:format_status/2</seealso>, and - <seealso marker="gen_event#Module:format_status/2">gen_event:format_status/2</seealso> + see <seealso marker="gen_server#Module:format_status/2">gen_server format_status/2</seealso>, + <seealso marker="gen_fsm#Module:format_status/2">gen_fsm format_status/2</seealso>, + <seealso marker="gen_statem#Module:format_status/2">gen_statem format_status/2</seealso>, and + <seealso marker="gen_event#Module:format_status/2">gen_event format_status/2</seealso> for more details.</p> </desc> </func> @@ -245,6 +249,8 @@ processes. For a <c>gen_server</c> process, the returned <c><anno>State</anno></c> is simply the callback module's state. For a <c>gen_fsm</c> process, <c><anno>State</anno></c> is the tuple <c>{CurrentStateName, CurrentStateData}</c>. + For a <c>gen_statem</c> process <c><anno>State</anno></c> is + the tuple <c>{CurrentState,CurrentData}.</c> For a <c>gen_event</c> process, <c><anno>State</anno></c> a list of tuples, where each tuple corresponds to an event handler registered in the process and contains <c>{Module, Id, HandlerState}</c>, where <c>Module</c> is the event handler's module name, @@ -263,8 +269,9 @@ details of the exception.</p> <p>The <c>system_get_state/1</c> function is primarily useful for user-defined behaviours and modules that implement OTP <seealso marker="#special_process">special - processes</seealso>. The <c>gen_server</c>, <c>gen_fsm</c>, and <c>gen_event</c> OTP - behaviour modules export this function, and so callback modules for those behaviours + processes</seealso>. The <c>gen_server</c>, <c>gen_fsm</c>, + <c>gen_statem</c> and <c>gen_event</c> OTP + behaviour modules export this function, so callback modules for those behaviours need not supply their own.</p> <p>To obtain more information about a process, including its state, see <seealso marker="#get_status-1">get_status/1</seealso> and @@ -290,6 +297,8 @@ <c>gen_fsm</c> process, <c><anno>State</anno></c> is the tuple <c>{CurrentStateName, CurrentStateData}</c>, and <c><anno>NewState</anno></c> is a similar tuple that may contain a new state name, new state data, or both. + The same applies for a <c>gen_statem</c> process but + it names the tuple fields <c>{CurrentState,CurrentData}</c>. For a <c>gen_event</c> process, <c><anno>State</anno></c> is the tuple <c>{Module, Id, HandlerState}</c> where <c>Module</c> is the event handler's module name, <c>Id</c> is the handler's ID (which is the value <c>false</c> if it was registered without @@ -304,7 +313,8 @@ state, then regardless of process type, it may simply return its <c><anno>State</anno></c> argument.</p> <p>If a <c><anno>StateFun</anno></c> function crashes or throws an exception, then - for <c>gen_server</c> and <c>gen_fsm</c> processes, the original state of the process is + for <c>gen_server</c>, <c>gen_fsm</c> or <c>gen_statem</c> processes, + the original state of the process is unchanged. For <c>gen_event</c> processes, a crashing or failing <c><anno>StateFun</anno></c> function means that only the state of the particular event handler it was working on when it failed or crashed is unchanged; it can still succeed in changing the states of other event @@ -329,7 +339,8 @@ <c>{callback_failed, StateFun, {Class, Reason}}</c>.</p> <p>The <c>system_replace_state/2</c> function is primarily useful for user-defined behaviours and modules that implement OTP <seealso marker="#special_process">special processes</seealso>. The - <c>gen_server</c>, <c>gen_fsm</c>, and <c>gen_event</c> OTP behaviour modules export this function, + <c>gen_server</c>, <c>gen_fsm</c>, <c>gen_statem</c> and + <c>gen_event</c> OTP behaviour modules export this function, and so callback modules for those behaviours need not supply their own.</p> </desc> </func> diff --git a/lib/stdlib/include/assert.hrl b/lib/stdlib/include/assert.hrl index 151794d114..9e5d4eb598 100644 --- a/lib/stdlib/include/assert.hrl +++ b/lib/stdlib/include/assert.hrl @@ -59,19 +59,22 @@ -define(assert(BoolExpr),ok). -else. %% The assert macro is written the way it is so as not to cause warnings -%% for clauses that cannot match, even if the expression is a constant. +%% for clauses that cannot match, even if the expression is a constant or +%% is known to be boolean-only. -define(assert(BoolExpr), begin ((fun () -> + __T = is_process_alive(self()), % cheap source of truth case (BoolExpr) of - true -> ok; + __T -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, true}, - case __V of false -> {value, __V}; - _ -> {not_boolean,__V} + case not __T of + __V -> {value, false}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -85,15 +88,17 @@ -define(assertNot(BoolExpr), begin ((fun () -> + __F = not is_process_alive(self()), case (BoolExpr) of - false -> ok; + __F -> ok; __V -> erlang:error({assert, [{module, ?MODULE}, {line, ?LINE}, {expression, (??BoolExpr)}, {expected, false}, - case __V of true -> {value, __V}; - _ -> {not_boolean,__V} + case not __F of + __V -> {value, true}; + _ -> {not_boolean, __V} end]}) end end)()) @@ -149,7 +154,8 @@ -else. -define(assertEqual(Expect, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Expect), case (Expr) of __X -> ok; __V -> erlang:error({assertEqual, @@ -159,7 +165,7 @@ {expected, __X}, {value, __V}]}) end - end)(Expect)) + end)()) end). -endif. @@ -169,7 +175,8 @@ -else. -define(assertNotEqual(Unexpected, Expr), begin - ((fun (__X) -> + ((fun () -> + __X = (Unexpected), case (Expr) of __X -> erlang:error({assertNotEqual, [{module, ?MODULE}, @@ -178,7 +185,7 @@ {value, __X}]}); _ -> ok end - end)(Unexpected)) + end)()) end). -endif. diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile index 9f4a446ea0..302834f9d0 100644 --- a/lib/stdlib/src/Makefile +++ b/lib/stdlib/src/Makefile @@ -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. @@ -85,6 +85,7 @@ MODULES= \ gen_event \ gen_fsm \ gen_server \ + gen_statem \ io \ io_lib \ io_lib_format \ diff --git a/lib/stdlib/src/gen.erl b/lib/stdlib/src/gen.erl index 9811a211ae..597830cf9a 100644 --- a/lib/stdlib/src/gen.erl +++ b/lib/stdlib/src/gen.erl @@ -26,7 +26,8 @@ %%% %%% The standard behaviour should export init_it/6. %%%----------------------------------------------------------------- --export([start/5, start/6, debug_options/1, +-export([start/5, start/6, debug_options/2, + name/1, unregister_name/1, get_proc_name/1, get_parent/0, call/3, call/4, reply/2, stop/1, stop/3]). -export([init_it/6, init_it/7]). @@ -124,7 +125,7 @@ init_it(GenMod, Starter, Parent, Mod, Args, Options) -> init_it2(GenMod, Starter, Parent, self(), Mod, Args, Options). init_it(GenMod, Starter, Parent, Name, Mod, Args, Options) -> - case name_register(Name) of + case register_name(Name) of true -> init_it2(GenMod, Starter, Parent, Name, Mod, Args, Options); {false, Pid} -> @@ -297,19 +298,19 @@ where({global, Name}) -> global:whereis_name(Name); where({via, Module, Name}) -> Module:whereis_name(Name); where({local, Name}) -> whereis(Name). -name_register({local, Name} = LN) -> +register_name({local, Name} = LN) -> try register(Name, self()) of true -> true catch error:_ -> {false, where(LN)} end; -name_register({global, Name} = GN) -> +register_name({global, Name} = GN) -> case global:register_name(Name, self()) of yes -> true; no -> {false, where(GN)} end; -name_register({via, Module, Name} = GN) -> +register_name({via, Module, Name} = GN) -> case Module:register_name(Name, self()) of yes -> true; @@ -317,34 +318,108 @@ name_register({via, Module, Name} = GN) -> {false, where(GN)} end. +name({local,Name}) -> Name; +name({global,Name}) -> Name; +name({via,_, Name}) -> Name; +name(Pid) when is_pid(Pid) -> Pid. + +unregister_name({local,Name}) -> + try unregister(Name) of + _ -> ok + catch + _:_ -> ok + end; +unregister_name({global,Name}) -> + _ = global:unregister_name(Name), + ok; +unregister_name({via, Mod, Name}) -> + _ = Mod:unregister_name(Name), + ok; +unregister_name(Pid) when is_pid(Pid) -> + ok. + +get_proc_name(Pid) when is_pid(Pid) -> + Pid; +get_proc_name({local, Name}) -> + case process_info(self(), registered_name) of + {registered_name, Name} -> + Name; + {registered_name, _Name} -> + exit(process_not_registered); + [] -> + exit(process_not_registered) + end; +get_proc_name({global, Name}) -> + case global:whereis_name(Name) of + undefined -> + exit(process_not_registered_globally); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit(process_not_registered_globally) + end; +get_proc_name({via, Mod, Name}) -> + case Mod:whereis_name(Name) of + undefined -> + exit({process_not_registered_via, Mod}); + Pid when Pid =:= self() -> + Name; + _Pid -> + exit({process_not_registered_via, Mod}) + end. + +get_parent() -> + case get('$ancestors') of + [Parent | _] when is_pid(Parent) -> + Parent; + [Parent | _] when is_atom(Parent) -> + name_to_pid(Parent); + _ -> + exit(process_was_not_started_by_proc_lib) + end. + +name_to_pid(Name) -> + case whereis(Name) of + undefined -> + case global:whereis_name(Name) of + undefined -> + exit(could_not_find_registered_name); + Pid -> + Pid + end; + Pid -> + Pid + end. + timeout(Options) -> - case opt(timeout, Options) of - {ok, Time} -> + case lists:keyfind(timeout, 1, Options) of + {_,Time} -> Time; - _ -> + false -> infinity end. spawn_opts(Options) -> - case opt(spawn_opt, Options) of - {ok, Opts} -> + case lists:keyfind(spawn_opt, 1, Options) of + {_,Opts} -> Opts; - _ -> + false -> [] end. -opt(Op, [{Op, Value}|_]) -> - {ok, Value}; -opt(Op, [_|Options]) -> - opt(Op, Options); -opt(_, []) -> - false. - -debug_options(Opts) -> - case opt(debug, Opts) of - {ok, Options} -> sys:debug_options(Options); - _ -> [] +debug_options(Name, Opts) -> + case lists:keyfind(debug, 1, Opts) of + {_,Options} -> + try sys:debug_options(Options) + catch _:_ -> + error_logger:format( + "~p: ignoring erroneous debug options - ~p~n", + [Name,Options]), + [] + end; + false -> + [] end. format_status_header(TagLine, Pid) when is_pid(Pid) -> diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index 1303036168..ccacf658e9 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -147,16 +147,11 @@ init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, _, _, Options) -> process_flag(trap_exit, true), - Debug = gen:debug_options(Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), proc_lib:init_ack(Starter, {ok, self()}), - Name = name(Name0), loop(Parent, Name, [], Debug, false). -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -spec add_handler(emgr_ref(), handler(), term()) -> term(). add_handler(M, Handler, Args) -> rpc(M, {add_handler, Handler, Args}). diff --git a/lib/stdlib/src/gen_fsm.erl b/lib/stdlib/src/gen_fsm.erl index 2b4b76105e..6e7528fd98 100644 --- a/lib/stdlib/src/gen_fsm.erl +++ b/lib/stdlib/src/gen_fsm.erl @@ -305,64 +305,11 @@ enter_loop(Mod, Options, StateName, StateData, Timeout) -> enter_loop(Mod, Options, StateName, StateData, self(), Timeout). enter_loop(Mod, Options, StateName, StateData, ServerName, Timeout) -> - Name = get_proc_name(ServerName), - Parent = get_parent(), - Debug = gen:debug_options(Options), + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug). -get_proc_name(Pid) when is_pid(Pid) -> - Pid; -get_proc_name({local, Name}) -> - case process_info(self(), registered_name) of - {registered_name, Name} -> - Name; - {registered_name, _Name} -> - exit(process_not_registered); - [] -> - exit(process_not_registered) - end; -get_proc_name({global, Name}) -> - case global:whereis_name(Name) of - undefined -> - exit(process_not_registered_globally); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit(process_not_registered_globally) - end; -get_proc_name({via, Mod, Name}) -> - case Mod:whereis_name(Name) of - undefined -> - exit({process_not_registered_via, Mod}); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit({process_not_registered_via, Mod}) - end. - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid(Parent) -> - Parent; - [Parent | _] when is_atom(Parent) -> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> - case global:whereis_name(Name) of - undefined -> - exit(could_not_find_registered_name); - Pid -> - Pid - end; - Pid -> - Pid - end. - %%% --------------------------------------------------- %%% Initiate the new process. %%% Register the name using the Rfunc function @@ -373,8 +320,8 @@ name_to_pid(Name) -> init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> - Name = name(Name0), - Debug = gen:debug_options(Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), case catch Mod:init(Args) of {ok, StateName, StateData} -> proc_lib:init_ack(Starter, {ok, self()}), @@ -383,15 +330,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> proc_lib:init_ack(Starter, {ok, self()}), loop(Parent, Name, StateName, StateData, Mod, Timeout, Debug); {stop, Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> @@ -400,20 +347,6 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> exit(Error) end. -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -unregister_name({local,Name}) -> - _ = (catch unregister(Name)); -unregister_name({global,Name}) -> - _ = global:unregister_name(Name); -unregister_name({via, Mod, Name}) -> - _ = Mod:unregister_name(Name); -unregister_name(Pid) when is_pid(Pid) -> - Pid. - %%----------------------------------------------------------------- %% The MAIN loop %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index fa6fff6dca..5800aca66f 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -304,9 +304,9 @@ enter_loop(Mod, Options, State, Timeout) -> enter_loop(Mod, Options, State, self(), Timeout). enter_loop(Mod, Options, State, ServerName, Timeout) -> - Name = get_proc_name(ServerName), - Parent = get_parent(), - Debug = debug_options(Name, Options), + Name = gen:get_proc_name(ServerName), + Parent = gen:get_parent(), + Debug = gen:debug_options(Name, Options), loop(Parent, Name, State, Mod, Timeout, Debug). %%%======================================================================== @@ -323,8 +323,8 @@ enter_loop(Mod, Options, State, ServerName, Timeout) -> init_it(Starter, self, Name, Mod, Args, Options) -> init_it(Starter, self(), Name, Mod, Args, Options); init_it(Starter, Parent, Name0, Mod, Args, Options) -> - Name = name(Name0), - Debug = debug_options(Name, Options), + Name = gen:name(Name0), + Debug = gen:debug_options(Name, Options), case catch Mod:init(Args) of {ok, State} -> proc_lib:init_ack(Starter, {ok, self()}), @@ -339,15 +339,15 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> %% (Otherwise, the parent process could get %% an 'already_started' error if it immediately %% tried starting the process again.) - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); ignore -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, ignore), exit(normal); {'EXIT', Reason} -> - unregister_name(Name0), + gen:unregister_name(Name0), proc_lib:init_ack(Starter, {error, Reason}), exit(Reason); Else -> @@ -356,20 +356,6 @@ init_it(Starter, Parent, Name0, Mod, Args, Options) -> exit(Error) end. -name({local,Name}) -> Name; -name({global,Name}) -> Name; -name({via,_, Name}) -> Name; -name(Pid) when is_pid(Pid) -> Pid. - -unregister_name({local,Name}) -> - _ = (catch unregister(Name)); -unregister_name({global,Name}) -> - _ = global:unregister_name(Name); -unregister_name({via, Mod, Name}) -> - _ = Mod:unregister_name(Name); -unregister_name(Pid) when is_pid(Pid) -> - Pid. - %%%======================================================================== %%% Internal functions %%%======================================================================== @@ -858,86 +844,6 @@ error_info(Reason, Name, Msg, State, Debug) -> sys:print_log(Debug), ok. -%%% --------------------------------------------------- -%%% Misc. functions. -%%% --------------------------------------------------- - -opt(Op, [{Op, Value}|_]) -> - {ok, Value}; -opt(Op, [_|Options]) -> - opt(Op, Options); -opt(_, []) -> - false. - -debug_options(Name, Opts) -> - case opt(debug, Opts) of - {ok, Options} -> dbg_opts(Name, Options); - _ -> [] - end. - -dbg_opts(Name, Opts) -> - case catch sys:debug_options(Opts) of - {'EXIT',_} -> - format("~p: ignoring erroneous debug options - ~p~n", - [Name, Opts]), - []; - Dbg -> - Dbg - end. - -get_proc_name(Pid) when is_pid(Pid) -> - Pid; -get_proc_name({local, Name}) -> - case process_info(self(), registered_name) of - {registered_name, Name} -> - Name; - {registered_name, _Name} -> - exit(process_not_registered); - [] -> - exit(process_not_registered) - end; -get_proc_name({global, Name}) -> - case global:whereis_name(Name) of - undefined -> - exit(process_not_registered_globally); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit(process_not_registered_globally) - end; -get_proc_name({via, Mod, Name}) -> - case Mod:whereis_name(Name) of - undefined -> - exit({process_not_registered_via, Mod}); - Pid when Pid =:= self() -> - Name; - _Pid -> - exit({process_not_registered_via, Mod}) - end. - -get_parent() -> - case get('$ancestors') of - [Parent | _] when is_pid(Parent)-> - Parent; - [Parent | _] when is_atom(Parent)-> - name_to_pid(Parent); - _ -> - exit(process_was_not_started_by_proc_lib) - end. - -name_to_pid(Name) -> - case whereis(Name) of - undefined -> - case global:whereis_name(Name) of - undefined -> - exit(could_not_find_registered_name); - Pid -> - Pid - end; - Pid -> - Pid - end. - %%----------------------------------------------------------------- %% Status information %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/gen_statem.erl b/lib/stdlib/src/gen_statem.erl new file mode 100644 index 0000000000..f9e2e5f7d2 --- /dev/null +++ b/lib/stdlib/src/gen_statem.erl @@ -0,0 +1,1267 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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(gen_statem). + +%% API +-export( + [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, + reply/1,reply/2]). + +%% gen callbacks +-export( + [init_it/6]). + +%% sys callbacks +-export( + [system_continue/3, + system_terminate/4, + system_code_change/4, + system_get_state/1, + system_replace_state/2, + format_status/2]). + +%% Internal callbacks +-export( + [wakeup_from_hibernate/3]). + +%% Type exports for templates +-export_type( + [event_type/0, + callback_mode/0, + state_function_result/0, + handle_event_result/0, + action/0]). + +%% Fix problem for doc build +-export_type([transition_option/0]). + +%%%========================================================================== +%%% Interface functions. +%%%========================================================================== + +-type from() :: + {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 + +-type state_name() :: atom(). + +-type data() :: term(). + +-type event_type() :: + {'call',From :: from()} | 'cast' | + 'info' | 'timeout' | 'internal'. + +-type callback_mode() :: 'state_functions' | 'handle_event_function'. + +-type transition_option() :: + postpone() | hibernate() | event_timeout(). +-type postpone() :: + %% If 'true' postpone the current event + %% and retry it when the state changes (=/=) + boolean(). +-type hibernate() :: + %% If 'true' hibernate the server instead of going into receive + boolean(). +-type event_timeout() :: + %% Generate a ('timeout', EventContent, ...) event after Time + %% unless some other event is delivered + Time :: timeout(). + +-type action() :: + %% During a state change: + %% * NextState and NewData are set. + %% * All action()s are executed in order of apperance. + %% * Postponing the current event is performed + %% iff 'postpone' is 'true'. + %% * A state timer is started iff 'timeout' is set. + %% * Pending events are processed or if there are + %% no pending events the server goes into receive + %% or hibernate (iff 'hibernate' is 'true') + %% + %% These action()s are executed in order of appearence + %% in the containing list. The ones that set options + %% will override any previous so the last of each kind wins. + %% + 'postpone' | % Set the postpone option + {'postpone', Postpone :: postpone()} | + %% + 'hibernate' | % Set the hibernate option + {'hibernate', Hibernate :: hibernate()} | + %% + (Timeout :: event_timeout()) | % {timeout,Timeout} + {'timeout', % Set the event timeout option + Time :: event_timeout(), EventContent :: term()} | + %% + reply_action() | + %% + %% All 'next_event' events are kept in a list and then + %% inserted at state changes so the first in the + %% action() list is the first to be delivered. + {'next_event', % Insert event as the next to handle + EventType :: event_type(), + EventContent :: term()}. +-type reply_action() :: + {'reply', % Reply to a caller + From :: from(), Reply :: term()}. + +-type state_function_result() :: + {'next_state', % {next_state,NextStateName,NewData,[]} + NextStateName :: state_name(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextStateName :: state_name(), + NewData :: data(), + Actions :: [action()] | action()} | + common_state_callback_result(). +-type handle_event_result() :: + {'next_state', % {next_state,NextState,NewData,[]} + NextState :: state(), + NewData :: data()} | + {'next_state', % State transition, maybe to the same state + NextState :: state(), + NewData :: data(), + Actions :: [action()] | action()} | + common_state_callback_result(). +-type common_state_callback_result() :: + 'stop' | % {stop,normal} + {'stop', % Stop the server + Reason :: term()} | + {'stop', % Stop the server + Reason :: term(), + NewData :: data()} | + {'stop_and_reply', % Reply then stop the server + Reason :: term(), + Replies :: [reply_action()] | reply_action()} | + {'stop_and_reply', % Reply then stop the server + Reason :: term(), + Replies :: [reply_action()] | reply_action(), + NewData :: data()} | + {'keep_state', % {keep_state,NewData,[]} + NewData :: data()} | + {'keep_state', % Keep state, change data + NewData :: data(), + Actions :: [action()] | action()} | + 'keep_state_and_data' | % {keep_state_and_data,[]} + {'keep_state_and_data', % Keep state and data -> only actions + Actions :: [action()] | action()}. + + +%% The state machine init function. It is called only once and +%% the server is not running until this function has returned +%% 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()} | + 'ignore' | + {'stop', Reason :: term()}. + +%% Example state callback for callback_mode() =:= state_functions +%% state name 'state_name'. +%% +%% In this mode all states has to be type state_name() i.e atom(). +%% +%% Note that state callbacks and only state callbacks have arity 5 +%% and that is intended. +-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. +-callback handle_event( + event_type(), + EventContent :: term(), + State :: state(), % Current state + Data :: data()) -> + handle_event_result(). + +%% Clean up before the server terminates. +-callback terminate( + Reason :: 'normal' | 'shutdown' | {'shutdown', term()} + | term(), + State :: state(), + Data :: data()) -> + any(). + +%% Note that the new code can expect to get an OldState from +%% the old code version not only in code_change/4 but in the first +%% state callback function called thereafter +-callback code_change( + OldVsn :: term() | {'down', term()}, + OldState :: state(), + OldData :: data(), + Extra :: term()) -> + {NewCallbackMode :: callback_mode(), + NewState :: state(), + NewData :: data()}. + +%% Format the callback module state in some sensible that is +%% often condensed way. For StatusOption =:= 'normal' the perferred +%% return term is [{data,[{"State",FormattedState}]}], and for +%% StatusOption =:= 'terminate' it is just FormattedState. +-callback format_status( + StatusOption, + [ [{Key :: term(), Value :: term()}] | + state() | + data()]) -> + Status :: term() when + StatusOption :: 'normal' | 'terminate'. + +-optional_callbacks( + [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. + %% + handle_event/4]). % For callback_mode =:= handle_event_function + +%% Type validation functions +callback_mode(CallbackMode) -> + case CallbackMode of + state_functions -> + true; + handle_event_function -> + true; + _ -> + false + end. +%% +from({Pid,_}) when is_pid(Pid) -> + true; +from(_) -> + false. +%% +event_type({call,From}) -> + from(From); +event_type(Type) -> + case Type of + cast -> + true; + info -> + true; + timeout -> + true; + internal -> + true; + _ -> + false + end. + + + +-define( + STACKTRACE(), + try throw(ok) catch _ -> erlang:get_stacktrace() end). + +-define( + TERMINATE(Class, Reason, Debug, S, Q), + terminate( + begin Class end, + begin Reason end, + ?STACKTRACE(), + begin Debug end, + begin S end, + begin Q end)). + +%%%========================================================================== +%%% API + +-type server_name() :: + {'global', GlobalName :: term()} + | {'via', RegMod :: module(), Name :: term()} + | {'local', atom()}. +-type server_ref() :: + {'global', GlobalName :: term()} + | {'via', RegMod :: module(), ViaName :: term()} + | (LocalName :: atom()) + | {Name :: atom(), Node :: atom()} + | pid(). +-type debug_opt() :: + {'debug', + Dbgs :: + ['trace' | 'log' | 'statistics' | 'debug' + | {'logfile', string()}]}. +-type start_opt() :: + debug_opt() + | {'timeout', Time :: timeout()} + | {'spawn_opt', [proc_lib:spawn_option()]}. +-type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}. + + + +%% Start a state machine +-spec start( + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start(Module, Args, Opts) -> + gen:start(?MODULE, nolink, Module, Args, Opts). +%% +-spec start( + ServerName :: server_name(), + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start(ServerName, Module, Args, Opts) -> + gen:start(?MODULE, nolink, ServerName, Module, Args, Opts). + +%% Start and link to a state machine +-spec start_link( + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start_link(Module, Args, Opts) -> + gen:start(?MODULE, link, Module, Args, Opts). +%% +-spec start_link( + ServerName :: server_name(), + Module :: module(), Args :: term(), Opts :: [start_opt()]) -> + start_ret(). +start_link(ServerName, Module, Args, Opts) -> + gen:start(?MODULE, link, ServerName, Module, Args, Opts). + +%% Stop a state machine +-spec stop(ServerRef :: server_ref()) -> ok. +stop(ServerRef) -> + gen:stop(ServerRef). +%% +-spec stop( + ServerRef :: server_ref(), + Reason :: term(), + Timeout :: timeout()) -> ok. +stop(ServerRef, Reason, Timeout) -> + gen:stop(ServerRef, Reason, Timeout). + +%% Send an event to a state machine that arrives with type 'event' +-spec cast(ServerRef :: server_ref(), Msg :: term()) -> ok. +cast({global,Name}, Msg) -> + try global:send(Name, wrap_cast(Msg)) of + _ -> ok + catch + _:_ -> ok + end; +cast({via,RegMod,Name}, Msg) -> + try RegMod:send(Name, wrap_cast(Msg)) of + _ -> ok + catch + _:_ -> ok + end; +cast({Name,Node} = ServerRef, Msg) when is_atom(Name), is_atom(Node) -> + send(ServerRef, wrap_cast(Msg)); +cast(ServerRef, Msg) when is_atom(ServerRef) -> + send(ServerRef, wrap_cast(Msg)); +cast(ServerRef, Msg) when is_pid(ServerRef) -> + send(ServerRef, wrap_cast(Msg)). + +%% Call a state machine (synchronous; a reply is expected) that +%% arrives with type {call,From} +-spec call(ServerRef :: server_ref(), Request :: term()) -> Reply :: term(). +call(ServerRef, Request) -> + call(ServerRef, Request, infinity). +%% +-spec call( + ServerRef :: server_ref(), + Request :: term(), + Timeout :: timeout()) -> + Reply :: term(). +call(ServerRef, Request, infinity) -> + try gen:call(ServerRef, '$gen_call', Request, infinity) of + {ok,Reply} -> + Reply + catch + Class:Reason -> + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,infinity]}}, + erlang:get_stacktrace()) + end; +call(ServerRef, Request, Timeout) -> + %% Call server through proxy process to dodge any late reply + Ref = make_ref(), + Self = self(), + Pid = spawn( + fun () -> + Self ! + try gen:call( + ServerRef, '$gen_call', Request, Timeout) of + Result -> + {Ref,Result} + catch Class:Reason -> + {Ref,Class,Reason,erlang:get_stacktrace()} + end + end), + Mref = monitor(process, Pid), + receive + {Ref,Result} -> + demonitor(Mref, [flush]), + case Result of + {ok,Reply} -> + Reply + end; + {Ref,Class,Reason,Stacktrace} -> + demonitor(Mref, [flush]), + erlang:raise( + Class, + {Reason,{?MODULE,call,[ServerRef,Request,Timeout]}}, + Stacktrace); + {'DOWN',Mref,_,_,Reason} -> + %% There is a theoretical possibility that the + %% proxy process gets killed between try--of and ! + %% so this clause is in case of that + exit(Reason) + end. + +%% Reply from a state machine callback to whom awaits in call/2 +-spec reply([reply_action()] | reply_action()) -> ok. +reply({reply,From,Reply}) -> + reply(From, Reply); +reply(Replies) when is_list(Replies) -> + replies(Replies). +%% +-spec reply(From :: from(), Reply :: term()) -> ok. +reply({To,Tag}, Reply) when is_pid(To) -> + Msg = {Tag,Reply}, + try To ! Msg of + _ -> + ok + catch + _:_ -> ok + end. + +%% Instead of starting the state machine through start/3,4 +%% or start_link/3,4 turn the current process presumably +%% started by proc_lib into a state machine using +%% 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()). +%% +-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) -> + if + is_list(Server_or_Actions) -> + enter_loop( + Module, Opts, CallbackMode, State, Data, + self(), Server_or_Actions); + true -> + enter_loop( + Module, Opts, CallbackMode, 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) -> + 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). + +%%--------------------------------------------------------------------------- +%% API helpers + +wrap_cast(Event) -> + {'$gen_cast',Event}. + +replies([{reply,From,Reply}|Replies]) -> + reply(From, Reply), + replies(Replies); +replies([]) -> + ok. + +%% Might actually not send the message in case of caught exception +send(Proc, Msg) -> + try erlang:send(Proc, Msg, [noconnect]) of + noconnect -> + _ = spawn(erlang, send, [Proc,Msg]), + ok; + ok -> + ok + catch + _:_ -> + ok + end. + +%% Here init_it/6 and enter_loop/5,6,7 functions converge +enter(Module, Opts, CallbackMode, 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), + PrevState = make_ref(), % Will be discarded by loop_event_actions/9 + NewActions = + if + is_list(Actions) -> + Actions ++ [{postpone,false}]; + true -> + [Actions,{postpone,false}] + end, + S = #{ + callback_mode => CallbackMode, + module => Module, + name => Name, + state => PrevState, + data => Data, + timer => undefined, + postponed => [], + hibernate => false}, + loop_event_actions( + Parent, Debug, S, [], + {event,undefined}, % Will be discarded thanks to {postpone,false} + PrevState, State, Data, NewActions). + +%%%========================================================================== +%%% gen callbacks + +init_it(Starter, self, ServerRef, Module, Args, Opts) -> + init_it(Starter, self(), ServerRef, Module, Args, Opts); +init_it(Starter, Parent, ServerRef, Module, Args, Opts) -> + try Module:init(Args) of + Result -> + init_result(Starter, Parent, ServerRef, Module, Result, Opts) + catch + Result -> + init_result(Starter, Parent, ServerRef, Module, Result, Opts); + Class:Reason -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, {error,Reason}), + erlang:raise(Class, Reason, erlang:get_stacktrace()) + end. + +%%--------------------------------------------------------------------------- +%% gen callbacks helpers + +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; + {stop,Reason} -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, {error,Reason}), + exit(Reason); + ignore -> + gen:unregister_name(ServerRef), + proc_lib:init_ack(Starter, ignore), + exit(normal); + _ -> + Error = {bad_return_value,Result}, + proc_lib:init_ack(Starter, {error,Error}), + exit(Error) + end. + +%%%========================================================================== +%%% sys callbacks + +system_continue(Parent, Debug, S) -> + loop(Parent, Debug, S). + +system_terminate(Reason, _Parent, Debug, S) -> + ?TERMINATE(exit, Reason, Debug, S, []). + +system_code_change( + #{module := Module, + state := State, + data := Data} = S, + _Mod, OldVsn, Extra) -> + case + try Module:code_change(OldVsn, State, Data, Extra) + catch + Result -> Result + end + of + {NewCallbackMode,NewState,NewData} -> + callback_mode(NewCallbackMode) orelse + error({callback_mode,NewCallbackMode}), + {ok,S#{state := NewState, data := NewData}}; + {ok,_} = Error -> + error({case_clause,Error}); + Error -> + Error + end. + +system_get_state(#{state := State, data := Data}) -> + {ok,{State,Data}}. + +system_replace_state( + StateFun, + #{state := State, + data := Data} = S) -> + {NewState,NewData} = Result = StateFun({State,Data}), + {ok,Result,S#{state := NewState, data := NewData}}. + +format_status( + Opt, + [PDict,SysState,Parent,Debug, + #{name := Name, postponed := P} = S]) -> + Header = gen:format_status_header("Status for state machine", Name), + Log = sys:get_debug(log, Debug, []), + [{header,Header}, + {data, + [{"Status",SysState}, + {"Parent",Parent}, + {"Logged Events",Log}, + {"Postponed",P}]} | + case format_status(Opt, PDict, S) of + L when is_list(L) -> L; + T -> [T] + end]. + +%%--------------------------------------------------------------------------- +%% Format debug messages. Print them as the call-back module sees +%% them, not as the real erlang messages. Use trace for that. +%%--------------------------------------------------------------------------- + +print_event(Dev, {in,Event}, #{name := Name}) -> + io:format( + Dev, "*DBG* ~p received ~s~n", + [Name,event_string(Event)]); +print_event(Dev, {out,Reply,{To,_Tag}}, #{name := Name}) -> + io:format( + Dev, "*DBG* ~p sent ~p to ~p~n", + [Name,Reply,To]); +print_event(Dev, {Tag,Event,NewState}, #{name := Name, state := State}) -> + StateString = + case NewState of + State -> + io_lib:format("~p", [State]); + _ -> + io_lib:format("~p => ~p", [State,NewState]) + end, + io:format( + Dev, "*DBG* ~p ~w ~s in state ~s~n", + [Name,Tag,event_string(Event),StateString]). + +event_string(Event) -> + case Event of + {{call,{Pid,_Tag}},Request} -> + io_lib:format("call ~p from ~w", [Request,Pid]); + {Tag,Content} -> + io_lib:format("~w ~p", [Tag,Content]) + end. + +sys_debug(Debug, S, Entry) -> + case Debug of + [] -> + Debug; + _ -> + sys:handle_debug(Debug, fun print_event/3, S, Entry) + end. + +%%%========================================================================== +%%% Internal callbacks + +wakeup_from_hibernate(Parent, Debug, S) -> + %% It is a new message that woke us up so we have to receive it now + loop_receive(Parent, Debug, S). + +%%%========================================================================== +%%% State Machine engine implementation of proc_lib/gen server + +%% Server loop, consists of all loop* functions +%% and some detours through sys and proc_lib + +%% Entry point for system_continue/3 +loop(Parent, Debug, #{hibernate := Hibernate} = S) -> + case Hibernate of + true -> + %% Does not return but restarts process at + %% wakeup_from_hibernate/3 that jumps to loop_receive/3 + proc_lib:hibernate( + ?MODULE, wakeup_from_hibernate, [Parent,Debug,S]), + error( + {should_not_have_arrived_here_but_instead_in, + {wakeup_from_hibernate,3}}); + false -> + loop_receive(Parent, Debug, S) + end. + +%% Entry point for wakeup_from_hibernate/3 +loop_receive(Parent, Debug, #{timer := Timer} = S) -> + receive + Msg -> + case Msg of + {system,Pid,Req} -> + #{hibernate := Hibernate} = S, + %% Does not return but tail recursively calls + %% system_continue/3 that jumps to loop/3 + sys:handle_system_msg( + Req, Pid, Parent, ?MODULE, Debug, S, Hibernate); + {'EXIT',Parent,Reason} = EXIT -> + %% EXIT is not a 2-tuple and therefore + %% not an event and has no event_type(), + %% but this will stand out in the crash report... + ?TERMINATE(exit, Reason, Debug, S, [EXIT]); + {timeout,Timer,Content} when Timer =/= undefined -> + loop_event(Parent, Debug, S, {timeout,Content}); + _ -> + %% Cancel Timer if running + case Timer of + undefined -> + ok; + _ -> + case erlang:cancel_timer(Timer) of + TimeLeft when is_integer(TimeLeft) -> + ok; + false -> + receive + {timeout,Timer,_} -> + ok + after 0 -> + ok + end + end + end, + Event = + case Msg of + {'$gen_call',From,Request} -> + {{call,From},Request}; + {'$gen_cast',E} -> + {cast,E}; + _ -> + {info,Msg} + end, + loop_event(Parent, Debug, S, Event) + end + end. + +loop_event(Parent, Debug, S, Event) -> + %% The timer field and the hibernate flag in S + %% are now invalid and ignored until we get back to loop/3 + NewDebug = sys_debug(Debug, S, {in,Event}), + %% Here the queue of not yet processed events is created + loop_events(Parent, NewDebug, S, [Event], false). + +%% Process first the event queue, or if it is empty +%% loop back to receive a new event +loop_events(Parent, Debug, S, [], _Hibernate) -> + loop(Parent, Debug, S); +loop_events( + Parent, Debug, + #{callback_mode := CallbackMode, + module := Module, + state := State, + data := Data} = S, + [{Type,Content} = Event|Events] = Q, + Hibernate) -> + %% If the Hibernate flag is true here it can only be + %% because it was set from an event action + %% and we did not go into hibernation since there + %% were events in queue, so we do what the user + %% might depend on i.e collect garbage which + %% would have happened if we actually hibernated + %% and immediately was awakened + Hibernate andalso garbage_collect(), + try + case CallbackMode of + state_functions -> + Module:State(Type, Content, Data); + handle_event_function -> + Module:handle_event(Type, Content, State, Data) + end of + Result -> + loop_event_result( + Parent, Debug, S, Events, Event, Result) + catch + Result -> + loop_event_result( + Parent, Debug, S, Events, Event, Result); + error:badarg when CallbackMode =:= state_functions -> + case erlang:get_stacktrace() of + [{erlang,apply,[Module,State,_],_}|Stacktrace] -> + Args = [Type,Content,Data], + terminate( + error, + {undef_state_function,{Module,State,Args}}, + Stacktrace, + Debug, S, Q); + Stacktrace -> + terminate(error, badarg, Stacktrace, Debug, S, Q) + end; + error:undef -> + %% Process an undef to check for the simple mistake + %% of calling a nonexistent state function + case erlang:get_stacktrace() of + [{Module,State, + [Type,Content,Data]=Args, + _} + |Stacktrace] + when CallbackMode =:= state_functions -> + terminate( + error, + {undef_state_function,{Module,State,Args}}, + Stacktrace, + Debug, S, Q); + [{Module,handle_event, + [Type,Content,State,Data]=Args, + _} + |Stacktrace] + when CallbackMode =:= handle_event_function -> + terminate( + error, + {undef_state_function, + {Module,handle_event,Args}}, + Stacktrace, + Debug, S, Q); + Stacktrace -> + terminate(error, undef, Stacktrace, Debug, S, Q) + end; + Class:Reason -> + Stacktrace = erlang:get_stacktrace(), + terminate(Class, Reason, Stacktrace, Debug, S, Q) + end. + +%% Interpret all callback return variants +loop_event_result( + Parent, Debug, + #{state := State, data := Data} = S, + Events, Event, Result) -> + %% From now until we loop back to the loop_events/4 + %% the state and data fields in S are old + case Result of + stop -> + ?TERMINATE(exit, normal, Debug, S, [Event|Events]); + {stop,Reason} -> + ?TERMINATE(exit, Reason, Debug, S, [Event|Events]); + {stop,Reason,NewData} -> + NewS = S#{data := NewData}, + Q = [Event|Events], + ?TERMINATE(exit, Reason, Debug, NewS, Q); + {stop_and_reply,Reason,Replies} -> + Q = [Event|Events], + [Class,NewReason,Stacktrace,NewDebug] = + reply_then_terminate( + exit, Reason, ?STACKTRACE(), Debug, S, Q, Replies), + %% Since we got back here Replies was bad + terminate(Class, NewReason, Stacktrace, NewDebug, S, Q); + {stop_and_reply,Reason,Replies,NewData} -> + NewS = S#{data := NewData}, + Q = [Event|Events], + [Class,NewReason,Stacktrace,NewDebug] = + reply_then_terminate( + exit, Reason, ?STACKTRACE(), Debug, NewS, Q, Replies), + %% Since we got back here Replies was bad + terminate(Class, NewReason, Stacktrace, NewDebug, NewS, Q); + {next_state,NextState,NewData} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, []); + {next_state,NextState,NewData,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions); + {keep_state,NewData} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, NewData, []); + {keep_state,NewData,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, NewData, Actions); + keep_state_and_data -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, Data, []); + {keep_state_and_data,Actions} -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, State, Data, Actions); + _ -> + ?TERMINATE( + error, {bad_return_value,Result}, Debug, S, [Event|Events]) + end. + +loop_event_actions( + Parent, Debug, S, Events, Event, State, NextState, NewData, Actions) -> + Postpone = false, % Shall we postpone this event, true or false + Hibernate = false, + Timeout = undefined, + NextEvents = [], + loop_event_actions( + Parent, Debug, S, Events, Event, State, NextState, NewData, + if + is_list(Actions) -> + Actions; + true -> + [Actions] + end, + Postpone, Hibernate, Timeout, NextEvents). +%% +%% Process all action()s +loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, [Action|Actions], + Postpone, Hibernate, Timeout, NextEvents) -> + case Action of + %% Actual actions + {reply,From,Reply} -> + case from(From) of + true -> + NewDebug = do_reply(Debug, S, From, Reply), + loop_event_actions( + Parent, NewDebug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, Timeout, NextEvents); + false -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; + {next_event,Type,Content} -> + case event_type(Type) of + true -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, Timeout, + [{Type,Content}|NextEvents]); + false -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; + %% Actions that set options + {postpone,NewPostpone} when is_boolean(NewPostpone) -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + NewPostpone, Hibernate, Timeout, NextEvents); + {postpone,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + postpone -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + true, Hibernate, Timeout, NextEvents); + {hibernate,NewHibernate} when is_boolean(NewHibernate) -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, NewHibernate, Timeout, NextEvents); + {hibernate,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + hibernate -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, true, Timeout, NextEvents); + {timeout,infinity,_} -> % Clear timer - it will never trigger + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, undefined, NextEvents); + {timeout,Time,_} = NewTimeout when is_integer(Time), Time >= 0 -> + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, NewTimeout, NextEvents); + {timeout,_,_} -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]); + infinity -> % Clear timer - it will never trigger + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, undefined, NextEvents); + Time when is_integer(Time), Time >= 0 -> + NewTimeout = {timeout,Time,Time}, + loop_event_actions( + Parent, Debug, S, Events, Event, + State, NextState, NewData, Actions, + Postpone, Hibernate, NewTimeout, NextEvents); + _ -> + ?TERMINATE( + error, {bad_action,Action}, Debug, S, [Event|Events]) + end; +%% +%% End of actions list +loop_event_actions( + Parent, Debug, #{postponed := P0} = S, Events, Event, + State, NextState, NewData, [], + Postpone, Hibernate, Timeout, NextEvents) -> + %% + %% All options have been collected and next_events are buffered. + %% Do the actual state transition. + %% + P1 = % Move current event to postponed if Postpone + case Postpone of + true -> + [Event|P0]; + false -> + P0 + end, + {Q2,P} = % Move all postponed events to queue if state change + if + NextState =:= State -> + {Events,P1}; + true -> + {lists:reverse(P1, Events),[]} + end, + %% Place next events first in queue + Q3 = lists:reverse(NextEvents, Q2), + %% + NewDebug = + sys_debug( + Debug, S, + case Postpone of + true -> + {postpone,Event,NextState}; + false -> + {consume,Event,NextState} + end), + %% Have a peek on the event queue so we can avoid starting + %% the state timer unless we have to + {Q,Timer} = + case Timeout of + undefined -> + %% No state timeout has been requested + {Q3,undefined}; + {timeout,Time,EventContent} -> + %% A state timeout has been requested + case Q3 of + [] when Time =:= 0 -> + %% Immediate timeout - simulate it + %% so we do not get the timeout message + %% after any received event + {[{timeout,EventContent}],undefined}; + [] -> + %% Actually start a timer + {Q3,erlang:start_timer(Time, self(), EventContent)}; + _ -> + %% Do not start a timer since any queued + %% event cancels the state timer so we pretend + %% that the timer has been started and cancelled + {Q3,undefined} + end + end, + %% Loop to top of event queue loop; process next event + loop_events( + Parent, NewDebug, + S#{ + state := NextState, + data := NewData, + timer := Timer, + postponed := P, + hibernate := Hibernate}, + Q, Hibernate). + +%%--------------------------------------------------------------------------- +%% Server helpers + +reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, Replies) -> + if + is_list(Replies) -> + do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, Replies); + true -> + do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, [Replies]) + end. +%% +do_reply_then_terminate(Class, Reason, Stacktrace, Debug, S, Q, []) -> + terminate(Class, Reason, Stacktrace, Debug, S, Q); +do_reply_then_terminate( + Class, Reason, Stacktrace, Debug, S, Q, [R|Rs]) -> + case R of + {reply,{_To,_Tag}=From,Reply} -> + NewDebug = do_reply(Debug, S, From, Reply), + do_reply_then_terminate( + Class, Reason, Stacktrace, NewDebug, S, Q, Rs); + _ -> + [error,{bad_action,R},?STACKTRACE(),Debug] + end. + +do_reply(Debug, S, From, Reply) -> + reply(From, Reply), + sys_debug(Debug, S, {out,Reply,From}). + + +terminate( + Class, Reason, Stacktrace, Debug, + #{module := Module, + state := State, data := Data} = S, + Q) -> + try Module:terminate(Reason, State, Data) of + _ -> ok + catch + _ -> ok; + C:R -> + ST = erlang:get_stacktrace(), + error_info( + C, R, ST, Debug, S, Q, + format_status(terminate, get(), S)), + erlang:raise(C, R, ST) + end, + case Reason of + normal -> ok; + shutdown -> ok; + {shutdown,_} -> ok; + _ -> + error_info( + Class, Reason, Stacktrace, Debug, S, Q, + format_status(terminate, get(), S)) + end, + case Stacktrace of + [] -> + erlang:Class(Reason); + _ -> + erlang:raise(Class, Reason, Stacktrace) + end. + +error_info( + Class, Reason, Stacktrace, Debug, + #{name := Name, callback_mode := CallbackMode, + state := State, postponed := P}, + Q, FmtData) -> + {FixedReason,FixedStacktrace} = + case Stacktrace of + [{M,F,Args,_}|ST] + when Class =:= error, Reason =:= undef -> + case code:is_loaded(M) of + false -> + {{'module could not be loaded',M},ST}; + _ -> + Arity = + if + is_list(Args) -> + length(Args); + is_integer(Args) -> + Args + end, + case erlang:function_exported(M, F, Arity) of + true -> + {Reason,Stacktrace}; + false -> + {{'function not exported',{M,F,Arity}}, + ST} + end + end; + _ -> {Reason,Stacktrace} + end, + error_logger:format( + "** State machine ~p terminating~n" ++ + case Q of + [] -> + ""; + _ -> + "** Last event = ~p~n" + end ++ + "** When Server state = ~p~n" ++ + "** Reason for termination = ~w:~p~n" ++ + "** State = ~p~n" ++ + "** Callback mode = ~p~n" ++ + "** Queued/Postponed = ~w/~w~n" ++ + case FixedStacktrace of + [] -> + ""; + _ -> + "** Stacktrace =~n" + "** ~p~n" + end, + [Name | + case Q of + [] -> + []; + [Event|_] -> + [Event] + end] ++ + [FmtData,Class,FixedReason, + State,CallbackMode,length(Q),length(P)] ++ + case FixedStacktrace of + [] -> + []; + _ -> + [FixedStacktrace] + end), + sys:print_log(Debug), + ok. + + +%% Call Module:format_status/2 or return a default value +format_status( + Opt, PDict, + #{module := Module, state := State, data := Data}) -> + case erlang:function_exported(Module, format_status, 2) of + true -> + try Module:format_status(Opt, [PDict,State,Data]) + catch + Result -> Result; + _:_ -> + format_status_default( + Opt, State, + "Module:format_status/2 crashed") + end; + false -> + format_status_default(Opt, State, Data) + end. + +%% The default Module:format_status/2 +format_status_default(Opt, State, Data) -> + SSD = {State,Data}, + case Opt of + terminate -> + SSD; + _ -> + [{data,[{"State",SSD}]}] + end. diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 052dffdbfd..7a59523f06 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -58,7 +58,12 @@ obsolete_1(erlang, now, 0) -> obsolete_1(calendar, local_time_to_universal_time, 1) -> {deprecated, {calendar, local_time_to_universal_time_dst, 1}}; -%% *** CRYPTO add in R16B01 *** +%% *** CRYPTO added in OTP 19 *** + +obsolete_1(crypto, rand_bytes, 1) -> + {deprecated, {crypto, strong_rand_bytes, 1}}; + +%% *** CRYPTO added in R16B01 *** obsolete_1(crypto, md4, 1) -> {deprecated, {crypto, hash, 2}}; diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index c47d30c8b8..3f79ed0f87 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -472,13 +472,15 @@ trans_init(gen,init_it,[gen_server,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; trans_init(gen,init_it,[gen_server,_,_,_,supervisor_bridge,[Module|_],_]) -> {supervisor_bridge,Module,1}; -trans_init(gen,init_it,[gen_server,_,_,Module,_,_]) -> +trans_init(gen,init_it,[GenMod,_,_,Module,_,_]) + when GenMod =:= gen_server; + GenMod =:= gen_statem; + GenMod =:= gen_fsm -> {Module,init,1}; -trans_init(gen,init_it,[gen_server,_,_,_,Module|_]) -> - {Module,init,1}; -trans_init(gen,init_it,[gen_fsm,_,_,Module,_,_]) -> - {Module,init,1}; -trans_init(gen,init_it,[gen_fsm,_,_,_,Module|_]) -> +trans_init(gen,init_it,[GenMod,_,_,_,Module|_]) + when GenMod =:= gen_server; + GenMod =:= gen_statem; + GenMod =:= gen_fsm -> {Module,init,1}; trans_init(gen,init_it,[gen_event|_]) -> {gen_event,init_it,6}; diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index e3950ee795..32bcdc4f2a 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -65,6 +65,7 @@ gen_event, gen_fsm, gen_server, + gen_statem, io, io_lib, io_lib_format, diff --git a/lib/stdlib/test/Makefile b/lib/stdlib/test/Makefile index 287f63b2be..28c35aed55 100644 --- a/lib/stdlib/test/Makefile +++ b/lib/stdlib/test/Makefile @@ -46,6 +46,7 @@ MODULES= \ gen_event_SUITE \ gen_fsm_SUITE \ gen_server_SUITE \ + gen_statem_SUITE \ id_transform_SUITE \ io_SUITE \ io_proto_SUITE \ diff --git a/lib/stdlib/test/error_logger_forwarder.erl b/lib/stdlib/test/error_logger_forwarder.erl index 0aecd41ba9..6bbf180585 100644 --- a/lib/stdlib/test/error_logger_forwarder.erl +++ b/lib/stdlib/test/error_logger_forwarder.erl @@ -20,7 +20,7 @@ -module(error_logger_forwarder). %% API. --export([register/0]). +-export([register/0, unregister/0]). %% Internal export for error_logger. -export([init/1, @@ -33,6 +33,10 @@ register() -> error_logger:add_report_handler(?MODULE, self()). +unregister() -> + Self = self(), + Self = error_logger:delete_report_handler(?MODULE). + init(Tester) -> {ok,Tester}. diff --git a/lib/stdlib/test/gen_statem_SUITE.erl b/lib/stdlib/test/gen_statem_SUITE.erl new file mode 100644 index 0000000000..3deb5fd986 --- /dev/null +++ b/lib/stdlib/test/gen_statem_SUITE.erl @@ -0,0 +1,1578 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 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(gen_statem_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +-compile(export_all). +-behaviour(gen_statem). + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +suite() -> + [{ct_hooks,[ts_install_cth]}, + {timetrap,{minutes,1}}]. + +all() -> + [{group, start}, + {group, start_handle_event}, + {group, stop}, + {group, stop_handle_event}, + {group, abnormal}, + {group, abnormal_handle_event}, + shutdown, stop_and_reply, event_order, + {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]}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_group(GroupName, Config) + when GroupName =:= start_handle_event; + GroupName =:= stop_handle_event; + GroupName =:= abnormal_handle_event; + GroupName =:= sys_handle_event -> + [{callback_mode,handle_event_function}|Config]; +init_per_group(_GroupName, Config) -> + Config. + +end_per_group(_GroupName, Config) -> + Config. + +init_per_testcase(_CaseName, Config) -> + flush(), +%%% dbg:tracer(), +%%% dbg:p(all, c), +%%% dbg:tpl(gen_statem, cx), +%%% dbg:tpl(proc_lib, cx), +%%% dbg:tpl(gen, cx), +%%% dbg:tpl(sys, cx), + Config. + +end_per_testcase(_CaseName, Config) -> +%%% dbg:stop(), + Config. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-define(EXPECT_FAILURE(Code, Reason), + try begin Code end of + Reason -> + ct:fail({unexpected,Reason}) + catch + error:Reason -> Reason; + exit:Reason -> Reason + end). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%% anonymous +start1(Config) -> + %%OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stop_it(Pid0), +%% stopped = gen_statem:call(Pid0, stop), +%% timeout = +%% ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous w. shutdown +start2(Config) -> + %% Dont link when shutdown + {ok,Pid0} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stopped = gen_statem:call(Pid0, {stop,shutdown}), + check_stopped(Pid0), + ok = verify_empty_msgq(). + +%% anonymous with timeout +start3(Config) -> + %%OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = + gen_statem:start(?MODULE, start_arg(Config, []), [{timeout,5}]), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stop_it(Pid0), + + {error,timeout} = + gen_statem:start( + ?MODULE, start_arg(Config, sleep), [{timeout,5}]), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous with ignore +start4(Config) -> + OldFl = process_flag(trap_exit, true), + + ignore = gen_statem:start(?MODULE, start_arg(Config, ignore), []), + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous with stop +start5(Config) -> + OldFl = process_flag(trap_exit, true), + + {error,stopped} = gen_statem:start(?MODULE, start_arg(Config, stop), []), + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% anonymous linked +start6(Config) -> + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + stop_it(Pid), + + ok = verify_empty_msgq(). + +%% global register linked +start7(Config) -> + STM = {global,my_stm}, + + {ok,Pid} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(STM), + ok = do_sync_func_test(STM), + stop_it(STM), + + ok = verify_empty_msgq(). + + +%% local register +start8(Config) -> + %%OldFl = process_flag(trap_exit, true), + Name = my_stm, + STM = {local,Name}, + + {ok,Pid} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(Name), + ok = do_sync_func_test(Name), + stop_it(Pid), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% local register linked +start9(Config) -> + %%OldFl = process_flag(trap_exit, true), + Name = my_stm, + STM = {local,Name}, + + {ok,Pid} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(Name), + ok = do_sync_func_test(Name), + stop_it(Pid), + + %%process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +%% global register +start10(Config) -> + STM = {global,my_stm}, + + {ok,Pid} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(STM, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(STM), + ok = do_sync_func_test(STM), + stop_it(STM), + + ok = verify_empty_msgq(). + +%% Stop registered processes +start11(Config) -> + Name = my_stm, + LocalSTM = {local,Name}, + GlobalSTM = {global,Name}, + + {ok,Pid} = + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Pid), + + {ok,_Pid1} = + gen_statem:start_link(LocalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Name), + + {ok,Pid2} = + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), + stop_it(Pid2), + receive after 1 -> true end, + Result = + gen_statem:start(GlobalSTM, ?MODULE, start_arg(Config, []), []), + ct:log("Result = ~p~n",[Result]), + {ok,_Pid3} = Result, + stop_it(GlobalSTM), + + ok = verify_empty_msgq(). + +%% Via register linked +start12(Config) -> + dummy_via:reset(), + VIA = {via,dummy_via,my_stm}, + + {ok,Pid} = + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start_link(VIA, ?MODULE, start_arg(Config, []), []), + {error,{already_started,Pid}} = + gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), + + ok = do_func_test(Pid), + ok = do_sync_func_test(Pid), + ok = do_func_test(VIA), + ok = do_sync_func_test(VIA), + stop_it(VIA), + + ok = verify_empty_msgq(). + + +%% Anonymous, reason 'normal' +stop1(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). + +%% Anonymous, other reason +stop2(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(Pid, other_reason, infinity), + false = erlang:is_process_alive(Pid), + ok. + +%% Anonymous, invalid timeout +stop3(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + _ = + ?EXPECT_FAILURE( + gen_statem:stop(Pid, other_reason, invalid_timeout), + Reason), + true = erlang:is_process_alive(Pid), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + ok. + +%% Registered name +stop4(Config) -> + {ok,Pid} = + gen_statem:start( + {local,to_stop},?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(to_stop), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(to_stop), Reason), + ok. + +%% Registered name and local node +stop5(Config) -> + Name = to_stop, + {ok,Pid} = + gen_statem:start( + {local,Name},?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop({Name,node()}), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop({Name,node()}), Reason), + ok. + +%% Globally registered name +stop6(Config) -> + STM = {global,to_stop}, + {ok,Pid} = gen_statem:start(STM, ?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(STM), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason), + ok. + +%% 'via' registered name +stop7(Config) -> + VIA = {via,dummy_via,to_stop}, + dummy_via:reset(), + {ok,Pid} = gen_statem:start(VIA, ?MODULE, start_arg(Config, []), []), + ok = gen_statem:stop(VIA), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(VIA), Reason), + ok. + +%% Anonymous on remote node +stop8(Config) -> + Node = gen_statem_stop8, + {ok,NodeName} = ct_slave:start(Node), + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem,start, + [?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(Pid), + false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + {{nodedown,NodeName},{sys,terminate,_}} = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason2), + ok. + +%% Registered name on remote node +stop9(Config) -> + Name = to_stop, + LocalSTM = {local,Name}, + Node = gen_statem__stop9, + {ok,NodeName} = ct_slave:start(Node), + STM = {Name,NodeName}, + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName, code, add_path, [Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem, start, + [LocalSTM,?MODULE,start_arg(Config, []),[]]), + ok = gen_statem:stop(STM), + undefined = rpc:call(NodeName,erlang,whereis,[Name]), + false = rpc:call(NodeName,erlang,is_process_alive,[Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + {{nodedown,NodeName},{sys,terminate,_}} = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), + ok. + +%% Globally registered name on remote node +stop10(Config) -> + Node = gen_statem_stop10, + STM = {global,to_stop}, + {ok,NodeName} = ct_slave:start(Node), + Dir = filename:dirname(code:which(?MODULE)), + rpc:call(NodeName,code,add_path,[Dir]), + {ok,Pid} = + rpc:call( + NodeName, gen_statem, start, + [STM,?MODULE,start_arg(Config, []),[]]), + global:sync(), + ok = gen_statem:stop(STM), + false = rpc:call(NodeName, erlang, is_process_alive, [Pid]), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason1), + {ok,NodeName} = ct_slave:stop(Node), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(STM), Reason2), + ok. + +%% Check that time outs in calls work +abnormal1(Config) -> + Name = abnormal1, + LocalSTM = {local,Name}, + + {ok, _Pid} = + gen_statem:start(LocalSTM, ?MODULE, start_arg(Config, []), []), + + %% timeout call. + delayed = gen_statem:call(Name, {delayed_answer,1}, 100), + {timeout,_} = + ?EXPECT_FAILURE( + gen_statem:call(Name, {delayed_answer,1000}, 10), + Reason), + ok = gen_statem:stop(Name), + ok = verify_empty_msgq(). + +%% Check that bad return values makes the stm crash. Note that we must +%% trap exit since we must link to get the real bad_return_ error +abnormal2(Config) -> + OldFl = process_flag(trap_exit, true), + {ok,Pid} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + + %% bad return value in the gen_statem loop + {{bad_return_value,badreturn},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + receive + {'EXIT',Pid,{bad_return_value,badreturn}} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + process_flag(trap_exit, OldFl), + ok = verify_empty_msgq(). + +shutdown(Config) -> + process_flag(trap_exit, true), + + {ok,Pid0} = gen_statem:start_link(?MODULE, start_arg(Config, []), []), + ok = do_func_test(Pid0), + ok = do_sync_func_test(Pid0), + stopped = gen_statem:call(Pid0, {stop,{shutdown,reason}}), + receive {'EXIT',Pid0,{shutdown,reason}} -> ok end, + process_flag(trap_exit, false), + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(Pid0, hej), Reason), + + receive + Any -> + ct:log("Unexpected: ~p", [Any]), + ct:fail({unexpected,Any}) + after 500 -> + ok + end. + + + +stop_and_reply(_Config) -> + process_flag(trap_exit, true), + + Machine = + %% Abusing the internal format of From... + #{init => + fun () -> + {ok,start,undefined} + end, + start => + fun (cast, {echo,From1,Reply1}, undefined) -> + {next_state,wait,{reply,From1,Reply1}} + end, + wait => + fun (cast, {stop_and_reply,Reason,From2,Reply2},R1) -> + {stop_and_reply,Reason, + [R1,{reply,From2,Reply2}]} + end}, + {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + + Self = self(), + Tag1 = make_ref(), + gen_statem:cast(STM, {echo,{Self,Tag1},reply1}), + Tag2 = make_ref(), + gen_statem:cast(STM, {stop_and_reply,reason,{Self,Tag2},reply2}), + case flush() of + [{Tag1,reply1},{Tag2,reply2},{'EXIT',STM,reason}] -> + ok; + Other1 -> + ct:fail({unexpected,Other1}) + end, + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), + case flush() of + [] -> + ok; + Other2 -> + ct:fail({unexpected,Other2}) + end. + + + +event_order(_Config) -> + process_flag(trap_exit, true), + + Machine = + %% Abusing the internal format of From... + #{init => + fun () -> + {ok,start,undefined} + end, + start => + fun (cast, _, _) -> + {keep_state_and_data,postpone}; %% Handled in 'buffer' + ({call,From}, {buffer,Pid,[Tag3,Tag4,Tag5]}, + undefined) -> + {next_state,buffer,[], + [{next_event,internal,{reply,{Pid,Tag3},ok3}}, + {next_event,internal,{reply,{Pid,Tag4},ok4}}, + {timeout,0,{reply,{Pid,Tag5},ok5}}, + %% The timeout should not happen since there + %% are events that cancel it i.e next_event + %% and postponed + {reply,From,ok}]} + end, + buffer => + fun (internal, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + (timeout, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + (cast, Reply, Replies) -> + {keep_state,[Reply|Replies]}; + ({call,From}, {stop,Reason}, Replies) -> + {next_state,stop,undefined, + lists:reverse( + Replies, + [{reply,From,ok}, + {next_event,internal,{stop,Reason}}])} + end, + stop => + fun (internal, Result, undefined) -> + Result + end}, + + {ok,STM} = gen_statem:start_link(?MODULE, {map_statem,Machine}, []), + Self = self(), + Tag1 = make_ref(), + gen_statem:cast(STM, {reply,{Self,Tag1},ok1}), + Tag2 = make_ref(), + gen_statem:cast(STM, {reply,{Self,Tag2},ok2}), + Tag3 = make_ref(), + Tag4 = make_ref(), + Tag5 = make_ref(), + ok = gen_statem:call(STM, {buffer,Self,[Tag3,Tag4,Tag5]}), + ok = gen_statem:call(STM, {stop,reason}), + case flush() of + [{Tag3,ok3},{Tag4,ok4},{Tag1,ok1},{Tag2,ok2}, + {'EXIT',STM,reason}] -> + ok; + Other1 -> + ct:fail({unexpected,Other1}) + end, + + {noproc,_} = + ?EXPECT_FAILURE(gen_statem:call(STM, hej), Reason), + case flush() of + [] -> + ok; + Other2 -> + ct:fail({unexpected,Other2}) + end. + + + +sys1(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {status, Pid, {module,gen_statem}, _} = sys:get_status(Pid), + sys:suspend(Pid), + Parent = self(), + Tag = make_ref(), + Caller = + spawn( + fun () -> + Parent ! {Tag,gen_statem:call(Pid, hej)} + end), + receive + {Tag,_} -> + ct:fail(should_be_suspended) + after 3000 -> + exit(Caller, ok) + end, + + %% {timeout,_} = + %% ?EXPECT_FAILURE(gen_statem:call(Pid, hej), Reason), + sys:resume(Pid), + stop_it(Pid). + +code_change(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + {idle,data} = sys:get_state(Pid), + sys:suspend(Pid), + sys:change_code(Pid, ?MODULE, old_vsn, state_functions), + sys:resume(Pid), + {idle,{old_vsn,data,state_functions}} = sys:get_state(Pid), + stop_it(Pid). + +call_format_status(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + Status = sys:get_status(Pid), + {status,Pid,_Mod,[_PDict,running,_,_, Data]} = Status, + [format_status_called|_] = lists:reverse(Data), + stop_it(Pid), + + %% check that format_status can handle a name being an atom (pid is + %% already checked by the previous test) + {ok, Pid2} = + gen_statem:start( + {local, gstm}, ?MODULE, start_arg(Config, []), []), + Status2 = sys:get_status(gstm), + {status,Pid2,_Mod,[_PDict2,running,_,_,Data2]} = Status2, + [format_status_called|_] = lists:reverse(Data2), + stop_it(Pid2), + + %% check that format_status can handle a name being a term other than a + %% pid or atom + GlobalName1 = {global,"CallFormatStatus"}, + {ok,Pid3} = + gen_statem:start( + GlobalName1, ?MODULE, start_arg(Config, []), []), + Status3 = sys:get_status(GlobalName1), + {status,Pid3,_Mod,[_PDict3,running,_,_,Data3]} = Status3, + [format_status_called|_] = lists:reverse(Data3), + stop_it(Pid3), + GlobalName2 = {global,{name, "term"}}, + {ok,Pid4} = + gen_statem:start( + GlobalName2, ?MODULE, start_arg(Config, []), []), + Status4 = sys:get_status(GlobalName2), + {status,Pid4,_Mod,[_PDict4,running,_,_, Data4]} = Status4, + [format_status_called|_] = lists:reverse(Data4), + stop_it(Pid4), + + %% check that format_status can handle a name being a term other than a + %% pid or atom + dummy_via:reset(), + ViaName1 = {via,dummy_via,"CallFormatStatus"}, + {ok,Pid5} = gen_statem:start(ViaName1, ?MODULE, start_arg(Config, []), []), + Status5 = sys:get_status(ViaName1), + {status,Pid5,_Mod, [_PDict5,running,_,_, Data5]} = Status5, + [format_status_called|_] = lists:reverse(Data5), + stop_it(Pid5), + ViaName2 = {via,dummy_via,{name,"term"}}, + {ok, Pid6} = + gen_statem:start( + ViaName2, ?MODULE, start_arg(Config, []), []), + Status6 = sys:get_status(ViaName2), + {status,Pid6,_Mod,[_PDict6,running,_,_,Data6]} = Status6, + [format_status_called|_] = lists:reverse(Data6), + stop_it(Pid6). + + + +error_format_status(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + Data = "called format_status", + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,Data}), []), + %% bad return value in the gen_statem loop + {{bad_return_value,badreturn},_} = + ?EXPECT_FAILURE(gen_statem:call(Pid, badreturn), Reason), + receive + {error,_, + {Pid, + "** State machine"++_, + [Pid,{{call,_},badreturn}, + {formatted,idle,Data}, + error,{bad_return_value,badreturn}|_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + error_logger_forwarder:unregister(), + ct:fail({unexpected,Other}) + after 1000 -> + error_logger_forwarder:unregister(), + ct:fail(timeout) + end, + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister(), + receive + %% Comes with SASL + {error_report,_,{Pid,crash_report,_}} -> + ok + after 500 -> + ok + end, + ok = verify_empty_msgq(). + +terminate_crash_format(Config) -> + error_logger_forwarder:register(), + OldFl = process_flag(trap_exit, true), + Data = crash_terminate, + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,Data}), []), + stop_it(Pid), + Self = self(), + receive + {error,_GroupLeader, + {Pid, + "** State machine"++_, + [Pid, + {{call,{Self,_}},stop}, + {formatted,idle,Data}, + exit,{crash,terminate}|_]}} -> + ok; + Other when is_tuple(Other), element(1, Other) =:= error -> + error_logger_forwarder:unregister(), + ct:fail({unexpected,Other}) + after 1000 -> + error_logger_forwarder:unregister(), + ct:fail(timeout) + end, + process_flag(trap_exit, OldFl), + error_logger_forwarder:unregister(), + receive + %% Comes with SASL + {error_report,_,{Pid,crash_report,_}} -> + ok + after 500 -> + ok + end, + ok = verify_empty_msgq(). + + +get_state(Config) -> + State = self(), + {ok,Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid), + {idle,State} = sys:get_state(Pid, 5000), + stop_it(Pid), + + %% check that get_state can handle a name being an atom (pid is + %% already checked by the previous test) + {ok,Pid2} = + gen_statem:start( + {local,gstm}, ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(gstm), + {idle,State} = sys:get_state(gstm, 5000), + stop_it(Pid2), + + %% check that get_state works when pid is sys suspended + {ok,Pid3} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid3), + ok = sys:suspend(Pid3), + {idle,State} = sys:get_state(Pid3, 5000), + ok = sys:resume(Pid3), + stop_it(Pid3), + ok = verify_empty_msgq(). + +replace_state(Config) -> + State = self(), + {ok, Pid} = + gen_statem:start( + ?MODULE, start_arg(Config, {data,State}), []), + {idle,State} = sys:get_state(Pid), + NState1 = "replaced", + Replace1 = fun({StateName, _}) -> {StateName,NState1} end, + {idle,NState1} = sys:replace_state(Pid, Replace1), + {idle,NState1} = sys:get_state(Pid), + NState2 = "replaced again", + Replace2 = fun({idle, _}) -> {state0,NState2} end, + {state0,NState2} = sys:replace_state(Pid, Replace2, 5000), + {state0,NState2} = sys:get_state(Pid), + %% verify no change in state if replace function crashes + Replace3 = fun(_) -> error(fail) end, + {callback_failed, + {gen_statem,system_replace_state},{error,fail}} = + ?EXPECT_FAILURE(sys:replace_state(Pid, Replace3), Reason), + {state0, NState2} = sys:get_state(Pid), + %% verify state replaced if process sys suspended + ok = sys:suspend(Pid), + Suffix2 = " and again", + NState3 = NState2 ++ Suffix2, + Replace4 = fun({StateName, _}) -> {StateName, NState3} end, + {state0,NState3} = sys:replace_state(Pid, Replace4), + ok = sys:resume(Pid), + {state0,NState3} = sys:get_state(Pid, 5000), + stop_it(Pid), + ok = verify_empty_msgq(). + +%% Hibernation +hibernate(Config) -> + OldFl = process_flag(trap_exit, true), + + {ok,Pid0} = + gen_statem:start_link( + ?MODULE, start_arg(Config, hiber_now), []), + is_in_erlang_hibernate(Pid0), + stop_it(Pid0), + receive + {'EXIT',Pid0,normal} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + {ok,Pid} = + gen_statem:start_link(?MODULE, start_arg(Config, hiber), []), + true = ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid,current_function)), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + please_just_five_more = gen_statem:call(Pid, snooze_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, snooze_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + + Pid ! hibernate_later, + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + is_in_erlang_hibernate(Pid), + + 'alive!' = gen_statem:call(Pid, 'alive?'), + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + Pid ! hibernate_now, + is_in_erlang_hibernate(Pid), + + 'alive!' = gen_statem:call(Pid, 'alive?'), + true = + ({current_function,{erlang,hibernate,3}} =/= + erlang:process_info(Pid, current_function)), + + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + please_just_five_more = gen_statem:call(Pid, snooze_sync), + is_in_erlang_hibernate(Pid), + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, hibernate_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, snooze_async), + is_in_erlang_hibernate(Pid), + ok = gen_statem:cast(Pid, wakeup_async), + is_not_in_erlang_hibernate(Pid), + + hibernating = gen_statem:call(Pid, hibernate_sync), + is_in_erlang_hibernate(Pid), + sys:suspend(Pid), + is_in_erlang_hibernate(Pid), + sys:resume(Pid), + is_in_erlang_hibernate(Pid), + receive after 1000 -> ok end, + is_in_erlang_hibernate(Pid), + + good_morning = gen_statem:call(Pid, wakeup_sync), + is_not_in_erlang_hibernate(Pid), + stop_it(Pid), + process_flag(trap_exit, OldFl), + receive + {'EXIT',Pid,normal} -> ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + ok = verify_empty_msgq(). + +is_in_erlang_hibernate(Pid) -> + receive after 1 -> ok end, + is_in_erlang_hibernate_1(200, Pid). + +is_in_erlang_hibernate_1(0, Pid) -> + ct:log("~p\n", [erlang:process_info(Pid, current_function)]), + ct:fail(not_in_erlang_hibernate_3); +is_in_erlang_hibernate_1(N, Pid) -> + {current_function,MFA} = erlang:process_info(Pid, current_function), + case MFA of + {erlang,hibernate,3} -> + ok; + _ -> + receive after 10 -> ok end, + is_in_erlang_hibernate_1(N-1, Pid) + end. + +is_not_in_erlang_hibernate(Pid) -> + receive after 1 -> ok end, + is_not_in_erlang_hibernate_1(200, Pid). + +is_not_in_erlang_hibernate_1(0, Pid) -> + ct:log("~p\n", [erlang:process_info(Pid, current_function)]), + ct:fail(not_in_erlang_hibernate_3); +is_not_in_erlang_hibernate_1(N, Pid) -> + {current_function,MFA} = erlang:process_info(Pid, current_function), + case MFA of + {erlang,hibernate,3} -> + receive after 10 -> ok end, + is_not_in_erlang_hibernate_1(N-1, Pid); + _ -> + ok + end. + + +enter_loop(_Config) -> + OldFlag = process_flag(trap_exit, true), + + dummy_via:reset(), + + %% Locally registered process + {local,Name} + {ok,Pid1a} = + proc_lib:start_link(?MODULE, enter_loop, [local,local]), + yes = gen_statem:call(Pid1a, 'alive?'), + stopped = gen_statem:call(Pid1a, stop), + receive + {'EXIT',Pid1a,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + {local,Name} + {ok,Pid1b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,local]), + receive + {'EXIT',Pid1b,process_not_registered} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Globally registered process + {global,Name} + {ok,Pid2a} = + proc_lib:start_link(?MODULE, enter_loop, [global,global]), + yes = gen_statem:call(Pid2a, 'alive?'), + stopped = gen_statem:call(Pid2a, stop), + receive + {'EXIT',Pid2a,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + {global,Name} + {ok,Pid2b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,global]), + receive + {'EXIT',Pid2b,process_not_registered_globally} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Unregistered process + no name + {ok,Pid3} = + proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), + yes = gen_statem:call(Pid3, 'alive?'), + stopped = gen_statem:call(Pid3, stop), + receive + {'EXIT',Pid3,normal} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Process not started using proc_lib + CallbackMode = state_functions, + Pid4 = + spawn_link( + gen_statem, enter_loop, + [?MODULE,[],CallbackMode,state0,[]]), + receive + {'EXIT',Pid4,process_was_not_started_by_proc_lib} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Make sure I am the parent, ie that ordering a shutdown will + %% result in the process terminating with Reason==shutdown + {ok,Pid5} = + proc_lib:start_link(?MODULE, enter_loop, [anon,anon]), + yes = gen_statem:call(Pid5, 'alive?'), + exit(Pid5, shutdown), + receive + {'EXIT',Pid5,shutdown} -> + ok + after 5000 -> + ct:fail(gen_statem_did_not_die) + end, + + %% Make sure gen_statem:enter_loop does not accept {local,Name} + %% when it's another process than the calling one which is + %% registered under that name + register(armitage, self()), + {ok,Pid6a} = + proc_lib:start_link(?MODULE, enter_loop, [anon,local]), + receive + {'EXIT',Pid6a,process_not_registered} -> + ok + after 1000 -> + ct:fail(gen_statem_started) + end, + unregister(armitage), + + %% Make sure gen_statem:enter_loop does not accept {global,Name} + %% when it's another process than the calling one which is + %% registered under that name + global:register_name(armitage, self()), + {ok,Pid6b} = + proc_lib:start_link(?MODULE, enter_loop, [anon,global]), + receive + {'EXIT',Pid6b,process_not_registered_globally} -> + ok + after 1000 -> + ct:fail(gen_statem_started) + end, + global:unregister_name(armitage), + + dummy_via:register_name(armitage, self()), + {ok,Pid6c} = + proc_lib:start_link(?MODULE, enter_loop, [anon,via]), + receive + {'EXIT',Pid6c,{process_not_registered_via,dummy_via}} -> + ok + after 1000 -> + ct:fail( + {gen_statem_started, + process_info(self(), messages)}) + end, + dummy_via:unregister_name(armitage), + + process_flag(trap_exit, OldFlag), + ok = verify_empty_msgq(). + +enter_loop(Reg1, Reg2) -> + process_flag(trap_exit, true), + case Reg1 of + local -> register(armitage, self()); + global -> global:register_name(armitage, self()); + via -> dummy_via:register_name(armitage, self()); + 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}); + global -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], {global,armitage}); + via -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, [], + {via, dummy_via, armitage}); + anon -> + gen_statem:enter_loop( + ?MODULE, [], CallbackMode, state0, []) + end. + + +%% Test the order for multiple {next_event,T,C} +next_events(Config) -> + {ok,Pid} = gen_statem:start(?MODULE, start_arg(Config, []), []), + ok = gen_statem:cast(Pid, next_event), + {state,next_events,[]} = gen_statem:call(Pid, get), + ok = gen_statem:stop(Pid), + false = erlang:is_process_alive(Pid), + noproc = + ?EXPECT_FAILURE(gen_statem:stop(Pid), Reason). + + +%% +%% Functionality check +%% + +wfor(Msg) -> + receive + Msg -> ok + after 5000 -> + error(timeout) + end. + + +stop_it(STM) -> + stopped = gen_statem:call(STM, stop), + check_stopped(STM). + + +check_stopped(STM) -> + Call = there_you_are, + {_,{gen_statem,call,[_,Call,infinity]}} = + ?EXPECT_FAILURE(gen_statem:call(STM, Call), Reason), + ok. + + +do_func_test(STM) -> + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok = do_connect(STM), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ?t:do_times(3, ?MODULE, do_msg, [STM]), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok = do_disconnect(STM), + ok = gen_statem:cast(STM, {'alive?',self()}), + wfor(yes), + ok. + + +do_connect(STM) -> + check_state(STM, idle), + gen_statem:cast(STM, {connect,self()}), + wfor(accept), + check_state(STM, wfor_conf), + Tag = make_ref(), + gen_statem:cast(STM, {ping,self(),Tag}), + gen_statem:cast(STM, confirm), + wfor({pong,Tag}), + check_state(STM, connected), + ok. + +do_msg(STM) -> + check_state(STM, connected), + R = make_ref(), + ok = gen_statem:cast(STM, {msg,self(),R}), + wfor({ack,R}). + + +do_disconnect(STM) -> + ok = gen_statem:cast(STM, disconnect), + check_state(STM, idle). + +check_state(STM, State) -> + case gen_statem:call(STM, get) of + {state, State, _} -> ok + end. + +do_sync_func_test(STM) -> + yes = gen_statem:call(STM, 'alive?'), + ok = do_sync_connect(STM), + yes = gen_statem:call(STM, 'alive?'), + ?t:do_times(3, ?MODULE, do_sync_msg, [STM]), + yes = gen_statem:call(STM, 'alive?'), + ok = do_sync_disconnect(STM), + yes = gen_statem:call(STM, 'alive?'), + check_state(STM, idle), + ok = gen_statem:call(STM, {timeout,200}), + yes = gen_statem:call(STM, 'alive?'), + check_state(STM, idle), + ok. + + +do_sync_connect(STM) -> + check_state(STM, idle), + accept = gen_statem:call(STM, connect), + check_state(STM, wfor_conf), + Tag = make_ref(), + gen_statem:cast(STM, {ping,self(),Tag}), + yes = gen_statem:call(STM, confirm), + wfor({pong,Tag}), + check_state(STM, connected), + ok. + +do_sync_msg(STM) -> + check_state(STM, connected), + R = make_ref(), + {ack,R} = gen_statem:call(STM, {msg,R}), + ok. + +do_sync_disconnect(STM) -> + yes = gen_statem:call(STM, disconnect), + check_state(STM, idle). + + +verify_empty_msgq() -> + [] = flush(), + ok. + +start_arg(Config, Arg) -> + case lists:keyfind(callback_mode, 1, Config) of + {_,CallbackMode} -> + {callback_mode,CallbackMode,Arg}; + false -> + Arg + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%% +%% The State Machine +%% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +init(ignore) -> + ignore; +init(stop) -> + {stop,stopped}; +init(stop_shutdown) -> + {stop,shutdown}; +init(sleep) -> + ?t:sleep(1000), + {state_functions,idle,data}; +init(hiber) -> + {state_functions,hiber_idle,[]}; +init(hiber_now) -> + {state_functions,hiber_idle,[],[hibernate]}; +init({data, Data}) -> + {state_functions,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; +init({map_statem,#{init := Init}=Machine}) -> + case Init() of + {ok,State,Data,Ops} -> + {handle_event_function,State,[Data|Machine],Ops}; + {ok,State,Data} -> + {handle_event_function,State,[Data|Machine]}; + Other -> + Other + end; +init([]) -> + {state_functions,idle,data}. + +terminate(_, _State, crash_terminate) -> + exit({crash,terminate}); +terminate({From,stopped}, State, _Data) -> + From ! {self(),{stopped,State}}, + ok; +terminate(_Reason, _State, _Data) -> + ok. + + +%% State functions + +idle(cast, {connect,Pid}, Data) -> + Pid ! accept, + {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API +idle({call,From}, connect, Data) -> + gen_statem:reply(From, accept), + {next_state,wfor_conf,Data,infinity}; % NoOp timeout just to test API +idle(cast, badreturn, _Data) -> + badreturn; +idle({call,_From}, badreturn, _Data) -> + badreturn; +idle({call,From}, {delayed_answer,T}, Data) -> + receive + after T -> + gen_statem:reply({reply,From,delayed}), + throw({keep_state,Data}) + end; +idle({call,From}, {timeout,Time}, _Data) -> + {next_state,timeout,{From,Time}, + {timeout,Time,idle}}; +idle(cast, next_event, _Data) -> + {next_state,next_events,[a,b,c], + [{next_event,internal,a}, + {next_event,internal,b}, + {next_event,internal,c}]}; +idle(Type, Content, Data) -> + case handle_common_events(Type, Content, idle, Data) of + undefined -> + case Type of + {call,From} -> + throw({keep_state,Data,[{reply,From,'eh?'}]}); + _ -> + throw( + {stop,{unexpected,idle,Type,Content}}) + end; + Result -> + Result + end. + +timeout(timeout, idle, {From,Time}) -> + TRef = erlang:start_timer(Time, self(), ok), + {keep_state,{From,TRef},0}; % Immediate timeout 0 +timeout(timeout, 0, {From,TRef}) -> + {next_state,timeout2,{From,TRef}, + [{timeout,1,should_be_cancelled}, + postpone]}; % Should cancel state timeout +timeout(_, _, _) -> + keep_state_and_data. + +timeout2(timeout, 0, _) -> + keep_state_and_data; +timeout2(timeout, Reason, _) -> + {stop,Reason}; +timeout2(info, {timeout,TRef,Result}, {From,TRef}) -> + gen_statem:reply([{reply,From,Result}]), + {next_state,idle,state}; +timeout2(_, _, _) -> + {keep_state_and_data,[]}. + +wfor_conf({call,From}, confirm, Data) -> + {next_state,connected,Data, + {reply,From,yes}}; +wfor_conf(cast, {ping,_,_}, _) -> + {keep_state_and_data,[postpone]}; +wfor_conf(cast, confirm, Data) -> + {next_state,connected,Data}; +wfor_conf(Type, Content, Data) -> + case handle_common_events(Type, Content, wfor_conf, Data) of + undefined -> + case Type of + {call,From} -> + {next_state,idle,Data, + [{reply,From,'eh?'}]}; + _ -> + throw(keep_state_and_data) + end; + Result -> + Result + end. + +connected({call,From}, {msg,Ref}, Data) -> + {keep_state,Data, + {reply,From,{ack,Ref}}}; +connected(cast, {msg,From,Ref}, Data) -> + From ! {ack,Ref}, + {keep_state,Data}; +connected({call,From}, disconnect, Data) -> + {next_state,idle,Data, + [{reply,From,yes}]}; +connected(cast, disconnect, Data) -> + {next_state,idle,Data}; +connected(cast, {ping,Pid,Tag}, Data) -> + Pid ! {pong,Tag}, + {keep_state,Data}; +connected(Type, Content, Data) -> + case handle_common_events(Type, Content, connected, Data) of + undefined -> + case Type of + {call,From} -> + {keep_state,Data, + [{reply,From,'eh?'}]}; + _ -> + {keep_state,Data} + end; + Result -> + Result + end. + +state0({call,From}, stop, Data) -> + {stop_and_reply,normal,[{reply,From,stopped}],Data}; +state0(Type, Content, Data) -> + case handle_common_events(Type, Content, state0, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +hiber_idle({call,From}, 'alive?', Data) -> + {keep_state,Data, + [{reply,From,'alive!'}]}; +hiber_idle({call,From}, hibernate_sync, Data) -> + {next_state,hiber_wakeup,Data, + [{reply,From,hibernating}, + hibernate]}; +hiber_idle(info, hibernate_later, _) -> + Tref = erlang:start_timer(1000, self(), hibernate), + {keep_state,Tref}; +hiber_idle(info, hibernate_now, Data) -> + {keep_state,Data, + [hibernate]}; +hiber_idle(info, {timeout,Tref,hibernate}, Tref) -> + {keep_state,[], + [hibernate]}; +hiber_idle(cast, hibernate_async, Data) -> + {next_state,hiber_wakeup,Data, + [hibernate]}; +hiber_idle(Type, Content, Data) -> + case handle_common_events(Type, Content, hiber_idle, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +hiber_wakeup({call,From}, wakeup_sync, Data) -> + {next_state,hiber_idle,Data, + [{reply,From,good_morning}]}; +hiber_wakeup({call,From}, snooze_sync, Data) -> + {keep_state,Data, + [{reply,From,please_just_five_more}, + hibernate]}; +hiber_wakeup(cast, wakeup_async, Data) -> + {next_state,hiber_idle,Data}; +hiber_wakeup(cast, snooze_async, Data) -> + {keep_state,Data, + [hibernate]}; +hiber_wakeup(Type, Content, Data) -> + case handle_common_events(Type, Content, hiber_wakeup, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + +next_events(internal, Msg, [Msg|Msgs]) -> + {keep_state,Msgs}; +next_events(Type, Content, Data) -> + case handle_common_events(Type, Content, next_events, Data) of + undefined -> + {keep_state,Data}; + Result -> + Result + end. + + +handle_common_events({call,From}, get, State, Data) -> + {keep_state,Data, + [{reply,From,{state,State,Data}}]}; +handle_common_events(cast, {get,Pid}, State, Data) -> + Pid ! {state,State,Data}, + {keep_state,Data}; +handle_common_events({call,From}, stop, _, Data) -> + {stop_and_reply,normal,[{reply,From,stopped}],Data}; +handle_common_events(cast, stop, _, _) -> + stop; +handle_common_events({call,From}, {stop,Reason}, _, Data) -> + {stop_and_reply,Reason,{reply,From,stopped},Data}; +handle_common_events(cast, {stop,Reason}, _, _) -> + {stop,Reason}; +handle_common_events({call,From}, 'alive?', _, Data) -> + {keep_state,Data, + [{reply,From,yes}]}; +handle_common_events(cast, {'alive?',Pid}, _, Data) -> + Pid ! yes, + {keep_state,Data}; +handle_common_events(_, _, _, _) -> + undefined. + +%% Wrapper state machine that uses a map state machine spec +handle_event( + Type, Event, State, [Data|Machine]) + when is_map(Machine) -> + #{State := HandleEvent} = Machine, + case + try HandleEvent(Type, Event, Data) of + Result -> + Result + catch + Result -> + Result + end of + {stop,Reason,NewData} -> + {stop,Reason,[NewData|Machine]}; + {next_state,NewState,NewData} -> + {next_state,NewState,[NewData|Machine]}; + {next_state,NewState,NewData,Ops} -> + {next_state,NewState,[NewData|Machine],Ops}; + {keep_state,NewData} -> + {keep_state,[NewData|Machine]}; + {keep_state,NewData,Ops} -> + {keep_state,[NewData|Machine],Ops}; + Other -> + Other + end; +%% +%% Dispatcher to test callback_mode handle_event_function +%% +%% Wrap the state in a 1 element list just to test non-atom +%% states. Note that the state from init/1 is not wrapped +%% so both atom and non-atom states are tested. +handle_event(Type, Event, State, Data) -> + StateName = unwrap_state(State), + try ?MODULE:StateName(Type, Event, Data) of + Result -> + wrap_result(Result) + catch + throw:Result -> + erlang:raise( + throw, wrap_result(Result), erlang:get_stacktrace()) + end. + +unwrap_state([State]) -> + State; +unwrap_state(State) -> + State. + +wrap_result(Result) -> + case Result of + {next_state,NewState,NewData} -> + {next_state,[NewState],NewData}; + {next_state,NewState,NewData,StateOps} -> + {next_state,[NewState],NewData,StateOps}; + Other -> + Other + end. + + + +code_change(OldVsn, State, Data, CallbackMode) -> + {CallbackMode,State,{OldVsn,Data,CallbackMode}}. + +format_status(terminate, [_Pdict,State,Data]) -> + {formatted,State,Data}; +format_status(normal, [_Pdict,_State,_Data]) -> + [format_status_called]. + +flush() -> + receive + Msg -> + [Msg|flush()] + after 500 -> + [] + end. diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl index 3fd5ed4ccf..1bcdc3ccd0 100644 --- a/lib/stdlib/test/rand_SUITE.erl +++ b/lib/stdlib/test/rand_SUITE.erl @@ -377,7 +377,7 @@ crypto_seed() -> crypto_next(<<Num:64, Bin/binary>>) -> {Num, Bin}; crypto_next(_) -> - crypto_next(crypto:rand_bytes((64 div 8)*100)). + crypto_next(crypto:strong_rand_bytes((64 div 8)*100)). crypto_uniform({Api, Data0}) -> {Int, Data} = crypto_next(Data0), diff --git a/lib/tools/doc/src/erlang_mode.xml b/lib/tools/doc/src/erlang_mode.xml index 0ab272fb2f..00cf5196b4 100644 --- a/lib/tools/doc/src/erlang_mode.xml +++ b/lib/tools/doc/src/erlang_mode.xml @@ -252,6 +252,7 @@ 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>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/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index 3bb1dda6db..ce26c83295 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -56,6 +56,8 @@ 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) ("wx_object" "wx-object" erlang-skel-wx-object erlang-skel-header) ("Library module" "gen-lib" @@ -858,6 +860,122 @@ 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 + '((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([init/1, terminate/3, code_change/4])." n + "-export([state_name/3])." 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 + "%% 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> + "[gen_statem:action()] | gen_statem:action()} |" n> + "ignore |" n> + "{stop, Reason :: term()}." n + "init([]) ->" n> + "{state_functions, 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 + (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 + "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 + (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 + "handle_event({call,From}, _Msg, State, Data) ->" n> + "{next_state, State, Data, [{reply,From,ok}]}." n + n + (erlang-skel-separator-start 2) + "%% @private" n + "%% @doc" 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 + "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. +Please see the function `tempo-define-template'.") + (defvar erlang-skel-wx-object '((erlang-skel-include erlang-skel-large-header) "-behaviour(wx_object)." n n diff --git a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src index 08fef1c2ff..b9cb4f08cc 100644 --- a/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src +++ b/lib/wx/api_gen/wx_extra/wxEvtHandler.c_src @@ -43,7 +43,7 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { if(recurse_level > 1) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) diff --git a/lib/wx/api_gen/wx_gen_cpp.erl b/lib/wx/api_gen/wx_gen_cpp.erl index 07486e801b..55c179142d 100644 --- a/lib/wx/api_gen/wx_gen_cpp.erl +++ b/lib/wx/api_gen/wx_gen_cpp.erl @@ -195,11 +195,13 @@ gen_funcs(Defs) -> w("void WxeApp::wxe_dispatch(wxeCommand& Ecmd)~n{~n"), w(" char * bp = Ecmd.buffer;~n"), + w(" int op = Ecmd.op;~n"), + w(" Ecmd.op = -1;~n"), w(" wxeMemEnv *memenv = getMemEnv(Ecmd.port);~n"), %% w(" wxMBConvUTF32 UTFconverter;~n"), - w(" wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true);~n"), + w(" wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true);~n"), w(" try {~n"), - w(" switch (Ecmd.op)~n{~n"), + w(" switch (op)~n{~n"), %% w(" case WXE_CREATE_PORT:~n", []), %% w(" { newMemEnv(Ecmd.port); } break;~n", []), %% w(" case WXE_REMOVE_PORT:~n", []), @@ -209,7 +211,7 @@ gen_funcs(Defs) -> w(" wxeRefData *refd = getRefData(This);~n"), w(" if(This && refd) {~n"), w(" if(recurse_level > 1 && refd->type != 4) {~n"), - w(" delayed_delete->Append(Ecmd.Save());~n"), + w(" delayed_delete->Append(Ecmd.Save(op));~n"), w(" } else {~n"), w(" delete_object(This, refd);~n"), w(" ((WxeApp *) wxTheApp)->clearPtr(This);}~n"), @@ -228,7 +230,7 @@ gen_funcs(Defs) -> w(" default: {~n"), w(" wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false);"), w(" error.addAtom(\"_wxe_error_\");~n"), - w(" error.addInt((int) Ecmd.op);~n"), + w(" error.addInt((int) op);~n"), w(" error.addAtom(\"not_supported\");~n"), w(" error.addTupleCount(3);~n"), w(" error.send();~n"), @@ -239,7 +241,7 @@ gen_funcs(Defs) -> w("} catch (wxe_badarg badarg) { // try~n"), w(" wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false);"), w(" error.addAtom(\"_wxe_error_\");~n"), - w(" error.addInt((int) Ecmd.op);~n"), + w(" error.addInt((int) op);~n"), w(" error.addAtom(\"badarg\");~n"), w(" error.addInt((int) badarg.ref);~n"), w(" error.addTupleCount(2);~n"), diff --git a/lib/wx/c_src/gen/wxe_funcs.cpp b/lib/wx/c_src/gen/wxe_funcs.cpp index 059cee59f4..283e97f4e2 100644 --- a/lib/wx/c_src/gen/wxe_funcs.cpp +++ b/lib/wx/c_src/gen/wxe_funcs.cpp @@ -40,17 +40,19 @@ void WxeApp::wxe_dispatch(wxeCommand& Ecmd) { char * bp = Ecmd.buffer; + int op = Ecmd.op; + Ecmd.op = -1; wxeMemEnv *memenv = getMemEnv(Ecmd.port); - wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true); + wxeReturn rt = wxeReturn(WXE_DRV_PORT, Ecmd.caller, true); try { - switch (Ecmd.op) + switch (op) { case DESTROY_OBJECT: { void *This = getPtr(bp,memenv); wxeRefData *refd = getRefData(This); if(This && refd) { if(recurse_level > 1 && refd->type != 4) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { delete_object(This, refd); ((WxeApp *) wxTheApp)->clearPtr(This);} @@ -114,7 +116,7 @@ case 101: { // wxEvtHandler::Disconnect int eventType = wxeEventTypeFromAtom(bp); bp += *eventTypeLen; if(eventType > 0) { if(recurse_level > 1) { - delayed_delete->Append(Ecmd.Save()); + delayed_delete->Append(Ecmd.Save(op)); } else { bool Result = This->Disconnect((int) *winid,(int) *lastId,eventType, (wxObjectEventFunction)(wxEventFunction) @@ -32077,7 +32079,7 @@ case wxDCOverlay_Clear: { // wxDCOverlay::Clear } default: { wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false); error.addAtom("_wxe_error_"); - error.addInt((int) Ecmd.op); + error.addInt((int) op); error.addAtom("not_supported"); error.addTupleCount(3); error.send(); @@ -32087,7 +32089,7 @@ case wxDCOverlay_Clear: { // wxDCOverlay::Clear rt.send(); } catch (wxe_badarg badarg) { // try wxeReturn error = wxeReturn(WXE_DRV_PORT, Ecmd.caller, false); error.addAtom("_wxe_error_"); - error.addInt((int) Ecmd.op); + error.addInt((int) op); error.addAtom("badarg"); error.addInt((int) badarg.ref); error.addTupleCount(2); diff --git a/lib/wx/c_src/wxe_helpers.cpp b/lib/wx/c_src/wxe_helpers.cpp index 76958a346f..4798e605e8 100644 --- a/lib/wx/c_src/wxe_helpers.cpp +++ b/lib/wx/c_src/wxe_helpers.cpp @@ -47,8 +47,8 @@ void wxeCommand::Delete() if(len > 64) driver_free(buffer); buffer = NULL; - op = -1; } + op = -1; } /* **************************************************************************** @@ -226,7 +226,7 @@ unsigned int wxeFifo::Cleanup(unsigned int def) // Realloced we need to start from the beginning return 0; } else { - return def; + return def < cb_start? def : cb_start; } } diff --git a/lib/wx/c_src/wxe_helpers.h b/lib/wx/c_src/wxe_helpers.h index 3f66b6d97a..70ffccdc13 100644 --- a/lib/wx/c_src/wxe_helpers.h +++ b/lib/wx/c_src/wxe_helpers.h @@ -46,7 +46,7 @@ class wxeCommand wxeCommand(); virtual ~wxeCommand(); // Use Delete() - wxeCommand * Save() { return this; }; + wxeCommand * Save(int Op) { op = Op; return this; }; void Delete(); ErlDrvTermData caller; diff --git a/lib/wx/c_src/wxe_impl.cpp b/lib/wx/c_src/wxe_impl.cpp index f899839782..175bcfce54 100644 --- a/lib/wx/c_src/wxe_impl.cpp +++ b/lib/wx/c_src/wxe_impl.cpp @@ -238,9 +238,10 @@ void WxeApp::dispatch_cmds() if(wxe_status != WXE_INITIATED) return; recurse_level++; - // fprintf(stderr, "\r\ndispatch_normal %d\r\n", level);fflush(stderr); + // fprintf(stderr, "\r\ndispatch_normal %d\r\n", recurse_level);fflush(stderr); + wxe_queue->cb_start = 0; dispatch(wxe_queue); - // fprintf(stderr, "\r\ndispatch_done \r\n");fflush(stderr); + // fprintf(stderr, "\r\ndispatch_done %d\r\n", recurse_level);fflush(stderr); recurse_level--; // Cleanup old memenv's and deleted objects diff --git a/lib/wx/src/wx.erl b/lib/wx/src/wx.erl index a1a9344316..6498b58cda 100644 --- a/lib/wx/src/wx.erl +++ b/lib/wx/src/wx.erl @@ -66,7 +66,7 @@ get_env/0,set_env/1, debug/1, batch/1,foreach/2,map/2,foldl/3,foldr/3, getObjectType/1, typeCast/2, - null/0, is_null/1]). + null/0, is_null/1, equal/2]). -export([create_memory/1, get_memory_bin/1, retain_memory/1, release_memory/1]). @@ -153,6 +153,10 @@ null() -> -spec is_null(wx_object()) -> boolean(). is_null(#wx_ref{ref=NULL}) -> NULL =:= 0. +%% @doc Returns true if both arguments references the same object, false otherwise +-spec equal(wx_object(), wx_object()) -> boolean(). +equal(#wx_ref{ref=Ref1}, #wx_ref{ref=Ref2}) -> Ref1 =:= Ref2. + %% @doc Returns the object type -spec getObjectType(wx_object()) -> atom(). getObjectType(#wx_ref{type=Type}) -> diff --git a/lib/wx/src/wx_object.erl b/lib/wx/src/wx_object.erl index a2acbb0a63..c22a083eb9 100644 --- a/lib/wx/src/wx_object.erl +++ b/lib/wx/src/wx_object.erl @@ -107,7 +107,8 @@ call/2, call/3, cast/2, reply/2, - get_pid/1 + get_pid/1, + set_pid/2 ]). %% -export([behaviour_info/1]). @@ -306,6 +307,11 @@ cast(Name, Request) when is_atom(Name) orelse is_pid(Name) -> 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. +set_pid(#wx_ref{}=R, Pid) when is_pid(Pid) -> + R#wx_ref{state=Pid}. + %% ----------------------------------------------------------------- %% Send a reply to the client. %% ----------------------------------------------------------------- diff --git a/lib/wx/test/wx_app_SUITE.erl b/lib/wx/test/wx_app_SUITE.erl index 0b885a78b8..3fd5bf689d 100644 --- a/lib/wx/test/wx_app_SUITE.erl +++ b/lib/wx/test/wx_app_SUITE.erl @@ -49,7 +49,7 @@ end_per_testcase(Func,Config) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,5}}]. all() -> [fields, modules, exportall, app_depend, undef_funcs, appup]. @@ -221,12 +221,10 @@ check_apps([App|Apps]) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -undef_funcs(suite) -> - []; -undef_funcs(doc) -> - []; +undef_funcs() -> + [{timetrap,{minutes,10}}]. + undef_funcs(Config) when is_list(Config) -> - catch test_server:timetrap(timer:minutes(10)), App = wx, AppFile = key1search(app_file, Config), Mods = key1search(modules, AppFile), diff --git a/lib/wx/test/wx_basic_SUITE.erl b/lib/wx/test/wx_basic_SUITE.erl index 5dffdea6be..f89f25274a 100644 --- a/lib/wx/test/wx_basic_SUITE.erl +++ b/lib/wx/test/wx_basic_SUITE.erl @@ -45,7 +45,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [silent_start, create_window, several_apps, wx_api, wx_misc, @@ -344,13 +344,13 @@ data_types(_Config) -> ImgRGB = ?mt(wxImage, wxImage:new(128, 64, Colors)), ?m(true, wxImage:ok(ImgRGB)), ?m(false, wxImage:hasAlpha(ImgRGB)), - ?m(Colors, wxImage:getData(ImgRGB)), + ?m(ok, case wxImage:getData(ImgRGB) of Colors -> ok; Other -> Other end), ImgRGBA = ?mt(wxImage, wxImage:new(128, 64, Colors, Alpha)), ?m(true, wxImage:ok(ImgRGBA)), ?m(true, wxImage:hasAlpha(ImgRGBA)), - ?m(Colors, wxImage:getData(ImgRGBA)), - ?m(Alpha, wxImage:getAlpha(ImgRGBA)), + ?m(ok, case wxImage:getData(ImgRGBA) of Colors -> ok; Other -> Other end), + ?m(ok, case wxImage:getAlpha(ImgRGBA) of Alpha -> ok; Other -> Other end), wxClientDC:destroy(CDC), %%wx_test_lib:wx_destroy(Frame,Config). @@ -361,7 +361,8 @@ wx_object(Config) -> wx:new(), Me = self(), Init = fun() -> - Frame = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame0 = wxFrame:new(wx:null(), ?wxID_ANY, "Test wx_object", [{size, {500, 400}}]), + Frame = wx_object:set_pid(Frame0, self()), Sz = wxBoxSizer:new(?wxHORIZONTAL), Panel = wxPanel:new(Frame), wxSizer:add(Sz, Panel, [{flag, ?wxEXPAND}, {proportion, 1}]), @@ -371,6 +372,7 @@ wx_object(Config) -> {Frame, {Frame, Panel}} end, Frame = ?mt(wxFrame, wx_obj_test:start([{init, Init}])), + timer:sleep(500), ?m(ok, check_events(flush())), @@ -378,6 +380,11 @@ wx_object(Config) -> ?m({call, foobar, {Me, _}}, wx_object:call(Frame, foobar)), ?m(ok, wx_object:cast(Frame, foobar2)), ?m([{cast, foobar2}|_], flush()), + + ?m(Frame, wx_obj_test:who_are_you(Frame)), + {call, {Frame,Panel}, _} = wx_object:call(Frame, fun(US) -> US end), + ?m(false, wxWindow:getParent(Panel) =:= Frame), + ?m(true, wx:equal(wxWindow:getParent(Panel),Frame)), FramePid = wx_object:get_pid(Frame), io:format("wx_object pid ~p~n",[FramePid]), FramePid ! foo3, diff --git a/lib/wx/test/wx_class_SUITE.erl b/lib/wx/test/wx_class_SUITE.erl index e23fd901f5..f88c10f987 100644 --- a/lib/wx/test/wx_class_SUITE.erl +++ b/lib/wx/test/wx_class_SUITE.erl @@ -46,12 +46,12 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [calendarCtrl, treeCtrl, notebook, staticBoxSizer, clipboard, helpFrame, htmlWindow, listCtrlSort, listCtrlVirtual, - radioBox, systemSettings, taskBarIcon, toolbar, popup]. + radioBox, systemSettings, taskBarIcon, toolbar, popup, modal]. groups() -> []. @@ -621,3 +621,67 @@ lang_env() -> format_env({match, List}) -> [io:format(" ~ts~n",[L]) || L <- List]; format_env(nomatch) -> ok. + +%% Add a testcase that tests that we can recurse in showModal +%% because it hangs in observer if object are not destroyed correctly +%% when popping the stack + +modal(Config) -> + Wx = wx:new(), + case {?wxMAJOR_VERSION, ?wxMINOR_VERSION, ?wxRELEASE_NUMBER} of + {2, Min, Rel} when Min < 8 orelse (Min =:= 8 andalso Rel < 11) -> + {skip, "old wxWidgets version"}; + _ -> + Frame = wxFrame:new(Wx, -1, "Test Modal windows"), + wxFrame:show(Frame), + Env = wx:get_env(), + Tester = self(), + ets:new(test_state, [named_table, public]), + Upd = wxUpdateUIEvent:getUpdateInterval(), + wxUpdateUIEvent:setUpdateInterval(500), + _Pid = spawn(fun() -> + wx:set_env(Env), + modal_dialog(Frame, 1, Tester) + end), + receive {dialog, M1, 1} -> timer:sleep(200), ets:insert(test_state, {M1, ready}) end, + receive {dialog, M2, 2} -> timer:sleep(200), ets:insert(test_state, {M2, ready}) end, + + receive done -> ok end, + receive {dialog_done, M2, 2} -> M2 end, + receive {dialog_done, M1, 1} -> M1 end, + + wxUpdateUIEvent:setUpdateInterval(Upd), + wx_test_lib:wx_destroy(Frame,Config) + end. + +modal_dialog(Parent, Level, Tester) when Level < 3 -> + M1 = wxTextEntryDialog:new(Parent, "Dialog " ++ integer_to_list(Level)), + io:format("Creating dialog ~p ~p~n",[Level, M1]), + wxDialog:connect(M1, show, [{callback, fun(#wx{event=Ev},_) -> + case Ev of + #wxShow{show=true} -> + Tester ! {dialog, M1, Level}; + _ -> ignore + end + end}]), + DoOnce = fun(_,_) -> + case ets:take(test_state, M1) of + [] -> ignore; + [_] -> modal_dialog(M1, Level+1, Tester) + end + end, + wxDialog:connect(M1, update_ui, [{callback, DoOnce}]), + ?wxID_OK = wxDialog:showModal(M1), + wxDialog:destroy(M1), + case Level > 1 of + true -> + io:format("~p: End dialog ~p ~p~n",[?LINE, Level-1, Parent]), + wxDialog:endModal(Parent, ?wxID_OK); + false -> ok + end, + Tester ! {dialog_done, M1, Level}, + ok; +modal_dialog(Parent, Level, Tester) -> + io:format("~p: End dialog ~p ~p~n",[?LINE, Level-1, Parent]), + wxDialog:endModal(Parent, ?wxID_OK), + Tester ! done. diff --git a/lib/wx/test/wx_event_SUITE.erl b/lib/wx/test/wx_event_SUITE.erl index 62fcf44033..6512cedaf2 100644 --- a/lib/wx/test/wx_event_SUITE.erl +++ b/lib/wx/test/wx_event_SUITE.erl @@ -44,7 +44,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [connect, disconnect, disconnect_cb, connect_msg_20, connect_cb_20, diff --git a/lib/wx/test/wx_obj_test.erl b/lib/wx/test/wx_obj_test.erl index cf99728c1a..23142e28b2 100644 --- a/lib/wx/test/wx_obj_test.erl +++ b/lib/wx/test/wx_obj_test.erl @@ -19,13 +19,13 @@ -module(wx_obj_test). -include_lib("wx/include/wx.hrl"). --export([start/1, stop/1]). +-export([start/1, stop/1, who_are_you/1]). %% wx_object callbacks -export([init/1, handle_info/2, terminate/2, code_change/3, handle_call/3, handle_sync_event/3, handle_event/2, handle_cast/2]). --record(state, {parent, opts, user_state}). +-record(state, {parent, me, opts, user_state}). start(Opts) -> wx_object:start_link(?MODULE, [{parent, self()}| Opts], []). @@ -33,12 +33,15 @@ start(Opts) -> stop(Object) -> wx_object:stop(Object). +who_are_you(Object) -> + wx_object:call(Object, who_are_you). + init(Opts) -> Parent = proplists:get_value(parent, Opts), put(parent_pid, Parent), Init = proplists:get_value(init, Opts), {Obj, UserState} = Init(), - {Obj, #state{parent=Parent, opts=Opts, user_state=UserState}}. + {Obj, #state{me=Obj, parent=Parent, opts=Opts, user_state=UserState}}. handle_sync_event(Event = #wx{obj=Panel, event=#wxPaint{}}, WxEvent, #state{parent=Parent, user_state=US, opts=Opts}) -> @@ -59,6 +62,8 @@ handle_event(Event, State = #state{parent=Parent}) -> Parent ! {event, Event}, {noreply, State}. +handle_call(who_are_you, _From, State = #state{me=Me}) -> + {reply, Me, State}; handle_call(What, From, State = #state{user_state=US}) when is_function(What) -> Result = What(US), {reply, {call, Result, From}, State}; diff --git a/lib/wx/test/wx_opengl_SUITE.erl b/lib/wx/test/wx_opengl_SUITE.erl index 5162078dbf..643a0df6a3 100644 --- a/lib/wx/test/wx_opengl_SUITE.erl +++ b/lib/wx/test/wx_opengl_SUITE.erl @@ -52,7 +52,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [canvas, glu_tesselation]. diff --git a/lib/wx/test/wx_xtra_SUITE.erl b/lib/wx/test/wx_xtra_SUITE.erl index 7aba17ee74..c6268a7f46 100644 --- a/lib/wx/test/wx_xtra_SUITE.erl +++ b/lib/wx/test/wx_xtra_SUITE.erl @@ -45,7 +45,7 @@ end_per_testcase(Func,Config) -> wx_test_lib:end_per_testcase(Func,Config). %% SUITE specification -suite() -> [{ct_hooks,[ts_install_cth]}]. +suite() -> [{ct_hooks,[ts_install_cth]}, {timetrap,{minutes,2}}]. all() -> [destroy_app, multiple_add_in_sizer, app_dies, diff --git a/lib/wx/test/wxt.erl b/lib/wx/test/wxt.erl index fc828e47e8..265cd5c981 100644 --- a/lib/wx/test/wxt.erl +++ b/lib/wx/test/wxt.erl @@ -16,13 +16,9 @@ %% limitations under the License. %% %% %CopyrightEnd% -%%%------------------------------------------------------------------- -%%% File : wxt.erl -%%% Author : Dan Gudmundsson <[email protected]> -%%% Description : Shortcuts for starting test with wx internal test_server -%%% -%%% Created : 4 Nov 2008 by Dan Gudmundsson <[email protected]> -%%%------------------------------------------------------------------- +%% +%% Description : Shortcuts for running tests with wx internal test_server +%%------------------------------------------------------------------- -module(wxt). -compile(export_all). @@ -40,7 +36,7 @@ t(Mod, TC) when is_atom(Mod), is_atom(TC) -> t({Mod,TC}, []); t(all, Config) when is_list(Config) -> Fs = filelib:wildcard("wx_*_SUITE.erl"), - t([list_to_atom(filename:rootname(File)) || File <- Fs], Config); + t([list_to_atom(filename:rootname(File)) || File <- Fs, File =/= "wx_app_SUITE.erl"], Config); t(Test,Config) when is_list(Config) -> Tests = resolve(Test), write_test_case(Test), |